diff --git a/newsfragments/320.misc.rst b/newsfragments/320.misc.rst
new file mode 100644
index 00000000..3f799b9e
--- /dev/null
+++ b/newsfragments/320.misc.rst
@@ -0,0 +1,4 @@
+Entering a cancel scope whose deadline is in the past now immediately
+cancels it, so :exc:`~trio.Cancelled` will be raised by the first
+checkpoint in the cancel scope rather than the second one.
+This also affects constructs like ``with trio.move_on_after(0):``.
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index e00295a1..6faeced1 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -201,6 +201,8 @@ class CancelScope:
             )
         self._scope_task = task
         self.cancelled_caught = False
+        if current_time() >= self._deadline:
+            self.cancel_called = True
         with self._might_change_effective_deadline():
             self._add_task(task)
         return self

Maybe move_on_after(0)/fail_after(0) should immediately cancel their scope
It's tempting to think that you can fake a "check once, but don't block" semantics by setting up a 0-timeout cancel scope:

```python
async def fake_accept_nowait(self):
    with fail_after(0):
        await self.accept()
```

This is not how Trio's cancellation semantics are supposed to work – there's no guarantee that `accept` won't raise `Cancelled` before it even attempts the operation.

@sorcio points out in chat that this actually works surprisingly well though. In fact, it probably succeeds deterministically right now (!), because `fail_after(0)` schedules its cancel scope to be cancelled "immediately", but since it uses a timeout to do this it doesn't actually happen until Trio processes timeouts, which it doesn't do until after this task yields. And if you look at `trio._socket._SocketType.accept`... it doesn't yield until after trying a non-blocking `accept`. So right now things work by accident.

This is dangerous, because this is a tempting thing to try, and the sort of thing where people might end up depending on it because it seems to work. I've been tempted and stopped b/c I thought it through and realized it wouldn't work – but we can't expect everyone to have my level of familiarity with the subtleties of Trio's semantics :-) – and @sorcio's tried it and found it worked...

So, if we want to keep the current semantics, maybe we should special-case `move_on_after(0)` and `fail_after(0)` to *immediately* cancel the scope they create, instead of going through the normal timeout machinery. Of course it would still be possible to get a similar effect with `move_on_after(1e-12)`, but then at least it's obvious that you're doing something potentially race-y.

Alternatively, could we change our semantics to make this work? The obvious problem would be IOCP on Windows. If you use IOCP to implement `accept`, then the underlying operation is actually "hey Windows, please start an `accept` running in the background", and then blocking and going through the event loop to check if it's done. In practice, if an `accept` is ready immediately, then it'll probably complete either synchronously (IOCP operations are allowed to do that) or at least before we notice the timeout and call `CancelIoEx`. But it makes me nervous? I dunno, maybe this would actually be fine.

The bigger problem is that the semantics of "don't deliver cancellations until a task actually *blocks* rather than just checkpoints" are fundamentally problematic: if you have some heavy computational work to do in the background, then the standard way is to go ahead and do it and just make sure to checkpoint frequently so other tasks can run. If we start delaying cancellations in general, then it becomes impossible to cancel a task like this.

So if we were going to do this, it would have to a specific thing the user had to request, I think – and then it would be the user's job to make sure that you only use it for operations that either succeed or block quickly, like `accept`. `with trio.move_on_when_blocks(): ...`. That's... not *totally* implausible. Certainly it'd be possible to implement, so long as we wrote down a clear definition of the difference between a checkpoint and blocking – which I'm a bit reluctant to do, because adding more distinctions adds complexity. Though we've already opened that door somewhat with `trio.testing.wait_all_tasks_blocked`… but that's in `trio.testing` plus it lets you specify a slop factor exactly to handle things like IOCP `accept` needing a few microseconds to be processed.

Probably the immediate thing to do is to add the special-case to make zero timeouts actually instantaneous, to keep our options open, and then continue to think about this.

Cross-ref: #242; in some sense this and that are alternatives, since they both potentially provide more general ways to handle `XX_nowait`.
diff --git a/trio/_core/tests/test_run.py b/trio/_core/tests/test_run.py
index e03f6da0..c6d3298b 100644
--- a/trio/_core/tests/test_run.py
+++ b/trio/_core/tests/test_run.py
@@ -407,12 +407,18 @@ def test_instruments(recwarn):
 
     _core.run(main, instruments=[r1, r2])
 
-    # It sleeps 5 times, so it runs 6 times
+    # It sleeps 5 times, so it runs 6 times.  Note that checkpoint()
+    # reschedules the task immediately upon yielding, before the
+    # after_task_step event fires.
     expected = (
-        [("before_run",)] +
-        6 * [("schedule", task),
+        [("before_run",),
+         ("schedule", task)] +
+        [("before", task),
+         ("schedule", task),
+         ("after", task)] * 5 + [
              ("before", task),
-             ("after", task)] + [("after_run",)]
+             ("after", task), ("after_run",)
+         ]
     )
     assert len(r1.record) > len(r2.record) > len(r3.record)
     assert r1.record == r2.record + r3.record
@@ -444,15 +450,15 @@ def test_instruments_interleave():
         ("schedule", tasks["t2"]),
         {
             ("before", tasks["t1"]),
+            ("schedule", tasks["t1"]),
             ("after", tasks["t1"]),
             ("before", tasks["t2"]),
+            ("schedule", tasks["t2"]),
             ("after", tasks["t2"])
         },
         {
-            ("schedule", tasks["t1"]),
             ("before", tasks["t1"]),
             ("after", tasks["t1"]),
-            ("schedule", tasks["t2"]),
             ("before", tasks["t2"]),
             ("after", tasks["t2"])
         },
diff --git a/trio/_core/tests/tutil.py b/trio/_core/tests/tutil.py
index dc93794b..d5d22d52 100644
--- a/trio/_core/tests/tutil.py
+++ b/trio/_core/tests/tutil.py
@@ -52,7 +52,7 @@ def gc_collect_harder():
 
 
 # template is like:
-#   [1, {2.1, 2.2}, 3] -> matches [1, 2.1, 3] or [1, 2.2, 3]
+#   [1, {2.1, 2.2}, 3] -> matches [1, 2.1, 2.2, 3] or [1, 2.2, 2.1, 3]
 def check_sequence_matches(seq, template):
     i = 0
     for pattern in template:

pip install -e .[dev]
parse_log_pytest
pytest
pytest pytest-cov pytest-faulthandler
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
async-generator==1.10
attrs==25.3.0
coverage==7.8.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
idna==3.10
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
outcome==1.3.0.post0
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
pytest-cov==6.0.0
pytest-faulthandler==2.0.1
sniffio==1.3.1
sortedcontainers==2.4.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
-e git+https://github.com/python-trio/trio.git@ccb6023c3ca33db81f8a85582173e81168010641#egg=trio

name: trio
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - async-generator==1.10
      - attrs==25.3.0
      - coverage==7.8.0
      - idna==3.10
      - outcome==1.3.0.post0
      - pytest-cov==6.0.0
      - pytest-faulthandler==2.0.1
      - sniffio==1.3.1
      - sortedcontainers==2.4.0
      - trio==0.10.0+dev
prefix: /opt/conda/envs/trio

trio/_core/tests/test_run.py::test_instruments
trio/_core/tests/test_run.py::test_instruments_crash trio/_core/tests/test_run.py::test_broken_abort trio/_core/tests/test_run.py::test_error_in_run_loop
trio/_core/tests/test_run.py::test_basic trio/_core/tests/test_run.py::test_initial_task_error trio/_core/tests/test_run.py::test_run_nesting trio/_core/tests/test_run.py::test_nursery_warn_use_async_with trio/_core/tests/test_run.py::test_nursery_main_block_error_basic trio/_core/tests/test_run.py::test_child_crash_basic trio/_core/tests/test_run.py::test_basic_interleave trio/_core/tests/test_run.py::test_task_crash_propagation trio/_core/tests/test_run.py::test_main_and_task_both_crash trio/_core/tests/test_run.py::test_two_child_crashes trio/_core/tests/test_run.py::test_child_crash_wakes_parent trio/_core/tests/test_run.py::test_reschedule trio/_core/tests/test_run.py::test_current_time trio/_core/tests/test_run.py::test_current_time_with_mock_clock trio/_core/tests/test_run.py::test_current_clock trio/_core/tests/test_run.py::test_current_task trio/_core/tests/test_run.py::test_root_task trio/_core/tests/test_run.py::test_out_of_context trio/_core/tests/test_run.py::test_current_statistics trio/_core/tests/test_run.py::test_instruments_interleave trio/_core/tests/test_run.py::test_null_instrument trio/_core/tests/test_run.py::test_instrument_before_after_run trio/_core/tests/test_run.py::test_instrument_task_spawn_exit trio/_core/tests/test_run.py::test_cancel_scope_repr trio/_core/tests/test_run.py::test_cancel_points trio/_core/tests/test_run.py::test_cancel_edge_cases trio/_core/tests/test_run.py::test_cancel_scope_multierror_filtering trio/_core/tests/test_run.py::test_precancelled_task trio/_core/tests/test_run.py::test_cancel_shielding trio/_core/tests/test_run.py::test_cancel_inheritance trio/_core/tests/test_run.py::test_cancel_shield_abort trio/_core/tests/test_run.py::test_basic_timeout trio/_core/tests/test_run.py::test_cancel_scope_deprecated trio/_core/tests/test_run.py::test_cancel_scope_nesting trio/_core/tests/test_run.py::test_cancel_unbound trio/_core/tests/test_run.py::test_timekeeping trio/_core/tests/test_run.py::test_failed_abort trio/_core/tests/test_run.py::test_spawn_system_task trio/_core/tests/test_run.py::test_system_task_crash trio/_core/tests/test_run.py::test_system_task_crash_MultiError trio/_core/tests/test_run.py::test_system_task_crash_plus_Cancelled trio/_core/tests/test_run.py::test_system_task_crash_KeyboardInterrupt trio/_core/tests/test_run.py::test_yield_briefly_checks_for_timeout trio/_core/tests/test_run.py::test_exc_info trio/_core/tests/test_run.py::test_exc_info_after_yield_error trio/_core/tests/test_run.py::test_exception_chaining_after_yield_error trio/_core/tests/test_run.py::test_nursery_exception_chaining_doesnt_make_context_loops trio/_core/tests/test_run.py::test_TrioToken_identity trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_basic trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_too_late trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_idempotent trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_idempotent_requeue trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_after_main_crash trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_crashes trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_FIFO trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_starvation_resistance trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_threaded_stress_test trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_massive_queue trio/_core/tests/test_run.py::test_slow_abort_basic trio/_core/tests/test_run.py::test_slow_abort_edge_cases trio/_core/tests/test_run.py::test_task_tree_introspection trio/_core/tests/test_run.py::test_nursery_closure trio/_core/tests/test_run.py::test_spawn_name trio/_core/tests/test_run.py::test_current_effective_deadline trio/_core/tests/test_run.py::test_nice_error_on_bad_calls_to_run_or_spawn trio/_core/tests/test_run.py::test_calling_asyncio_function_gives_nice_error trio/_core/tests/test_run.py::test_trivial_yields trio/_core/tests/test_run.py::test_nursery_start trio/_core/tests/test_run.py::test_task_nursery_stack trio/_core/tests/test_run.py::test_nursery_start_with_cancelled_nursery trio/_core/tests/test_run.py::test_nursery_start_keeps_nursery_open trio/_core/tests/test_run.py::test_nursery_explicit_exception trio/_core/tests/test_run.py::test_nursery_stop_iteration trio/_core/tests/test_run.py::test_nursery_stop_async_iteration trio/_core/tests/test_run.py::test_traceback_frame_removal trio/_core/tests/test_run.py::test_contextvar_support trio/_core/tests/test_run.py::test_contextvar_multitask trio/_core/tests/test_run.py::test_system_task_contexts trio/_core/tests/test_run.py::test_Cancelled_init trio/_core/tests/test_run.py::test_sniffio_integration trio/_core/tests/test_run.py::test_Task_custom_sleep_data trio/_core/tests/test_run.py::test_permanently_detach_coroutine_object trio/_core/tests/test_run.py::test_detach_and_reattach_coroutine_object trio/_core/tests/test_run.py::test_detached_coroutine_cancellation
newsfragments/320.misc.rst trio/_core/_run.py
newsfragments/320.misc.rst trio/_core/_run.py