Skip to content

Run executor submissions inline instead of fencing them - #25

Merged
dhruvl merged 2 commits into
mainfrom
inline-executor
Aug 4, 2026
Merged

Run executor submissions inline instead of fencing them#25
dhruvl merged 2 commits into
mainfrom
inline-executor

Conversation

@dhruvl

@dhruvl dhruvl commented Aug 4, 2026

Copy link
Copy Markdown
Owner

run_in_executor no longer fences. The submitted function runs synchronously at an ordinary scheduled step — ordered by the seeded draw, labelled executor:<function> in the trace, costing no virtual time — and its result or exception lands on the returned future the way an executor worker would land it. asyncio.to_thread reaches the loop through this call, so it now works under simulation.

What stays honest about it:

  • The executor argument is never used. There is no pool and nothing runs concurrently, and a test proves the object is never touched. set_default_executor still fences: a pool that would never run anything is refused rather than accepted.
  • call_soon_threadsafe from the loop's own thread is now call_soon, which is all it ever was without a second thread. From any other thread it still fences — a real thread's timing is outside the simulation — and a test drives that from an actual second thread.
  • anyio.to_thread is not thereby supported, and the docs say why precisely: its worker threads are real ones spawned through no loop API, so no fence can catch the escape at the source, and a real thread racing a virtual clock ends in the cross-thread fence, a hang, or the caller's own timeout. (Measured, not guessed — the timeout-first race was observed while writing the docs.)

design.md keeps its argument against passthrough to a real pool and marks this as the one place the line moved without crossing it. The probe-harness tests lean on add_reader as their canonical fence now that their old one stopped fencing.

Determinism is untouched where it is promised: the submission is a ready-queue entry like any other, a cancelled future means the function never runs, and a trace-hash test pins two runs of the same seed to the same hash.

dhruvl added 2 commits August 4, 2026 13:21
run_in_executor no longer fences: the function runs synchronously at an
ordinary ready-queue entry labelled executor:<func>, so the seeded draw
orders it against everything else and asyncio.to_thread works under
simulation. The executor argument is never used, and a future cancelled
before its step runs means the function never runs at all.

call_soon_threadsafe from the loop's own thread is call_soon, which is
all it ever was without a second thread; from any other thread it still
fences, since a real thread's timing is outside the simulation. The
probe-harness tests lean on add_reader as their canonical fence now.
The contract pages now carry the inline executor: supported-api gains
rows for run_in_executor and same-thread call_soon_threadsafe, the
fenced list keeps set_default_executor and the cross-thread call, and
the README's honest-limits paragraph says executor submissions stay
inside the line. design.md keeps its argument against passthrough to a
real pool and marks inline execution as the one place the line moved
without crossing it. anyio.to_thread is called out for what it is:
real worker threads spawned through no loop API, where a fence, a hang
or the caller's own timeout is a race between a real thread and a
virtual clock.
@dhruvl
dhruvl merged commit fdf1c14 into main Aug 4, 2026
9 checks passed
@dhruvl
dhruvl deleted the inline-executor branch August 4, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant