Skip to content

Sync bridge: request_sync/send_sync from plain-def handlers, with the sync-vs-async rationale documented - #18

Merged
acn-ericlaw merged 4 commits into
mainfrom
feature/sync-bridge
Aug 24, 2026
Merged

acn-ericlaw merged 4 commits into
mainfrom
feature/sync-bridge

Conversation

@acn-ericlaw

Copy link
Copy Markdown
Collaborator

What

  1. Sync bridge — PostOffice.request_sync() / send_sync(): a plain def handler
    (which runs on an executor thread with no event loop of its own) can now call sibling
    or remote functions. The bus stamps the host loop into the handler's context at
    dispatch; the bridge submits the same coroutines onto that loop via
    run_coroutine_threadsafe, blocking only the handler's worker thread — never the
    event loop. The caller's trace context is re-established inside the submitted task
    (contextvars do not cross run_coroutine_threadsafe on their own), so the trace
    chain is unbroken end to end. Misuse teaches: calling the bridge on the event loop →
    "await request()/send() instead"; calling it outside a hosted function → points at
    asyncio.run(po.request(...)). Errors stay envelope-shaped (404/408/202), identical
    to the async path.
  2. The sync-vs-async handler rationale is now documented (README function contract +
    the registry module docstring): plain def handlers are first-class because Python's
    synchronous ecosystem — requests, NumPy/pandas and most ML inference stacks,
    database drivers — must be wrappable without stalling the event loop that hosts every
    other function; the executor dispatch is the Python analog of the Java engine's
    virtual threads. async def serves asyncio-native I/O and composition. Detection
    stays automatic; a rule of thumb closes the section.
  3. Demo: hello.sync.chain (plain def → request_sync → private sibling);
    PostOffice hoisted to the demo's module-level import.

Why

The design review ratified supporting both handler styles, but sync handlers had a
composition gap: PostOffice.request() is a coroutine and an executor thread cannot
await it — so the synchronous half of the ecosystem could wrap libraries yet not call
sibling functions. The bridge closes that gap with the same envelope contract, keeping
the two styles fully symmetric for developers.

Verification

  • 54/54 pytest — 4 new pins: sync entry → request_sync → private helper with the full
    trace/cid chain; refused-on-event-loop teaching error; off-host teaching error;
    404/202 envelope shaping + send_sync delivery. ruff clean, basedpyright 0.
  • Live wire proof: hello.sync.chain driven over /api/event →
    {'text': 'polyglot!', 'language': 'python'}; re-verified after each follow-up commit.
  • Follow-ups from the IDE review folded: _run_sync static, demo import hoist,
    unshadowed test locals.

Co-Authored-By: Claude Code noreply@anthropic.com

🤖 Generated with Claude Code

acn-ericlaw and others added 4 commits August 23, 2026 19:47
Python's synchronous ecosystem (requests, NumPy/ML inference, database
drivers) is why plain def handlers are first-class; they run in the
executor so blocking work never stalls the event loop - the analog of
the Java engine's virtual threads. This adds the missing composition
piece: PostOffice.request_sync()/send_sync() submit the same calls onto
the host loop (stamped by the bus into the handler's context) while
blocking only the worker thread; the caller's trace context is
re-established inside the submitted task so the chain is unbroken.
Calling the bridge on the event loop, or off-host, raises a teaching
error. Rationale documented in README and the registry module;
hello.sync.chain added to the demo.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@acn-ericlaw
acn-ericlaw merged commit 1888a48 into main Aug 24, 2026
2 checks passed
@acn-ericlaw
acn-ericlaw deleted the feature/sync-bridge branch August 24, 2026 01:03
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