fix(photon): run the Spectrum patch spawn off the gateway event loop - #66940
Closed
Frowtek wants to merge 1 commit into
Closed
fix(photon): run the Spectrum patch spawn off the gateway event loop#66940Frowtek wants to merge 1 commit into
Frowtek wants to merge 1 commit into
Conversation
`PhotonAdapter._start_sidecar` is `async`, but it ran the Spectrum
mixed-attachment patch script with a bare `subprocess.run(...)`: it spawns
node and *waits* for it, with `timeout=10`. Executed inline that holds the
shared gateway event loop for the whole window, so no other platform's
messages, heartbeats, or sessions are serviced until it returns.
The same function already establishes this exact invariant twenty lines
above, where the stale-dependency reinstall hops to a worker thread:
# Runs off the event loop so a cold install can't freeze every other
# platform's traffic.
if _sidecar_deps_stale():
await asyncio.to_thread(_reinstall_sidecar_deps)
The patch spawn never got the same treatment. It is not startup-only
either — `_start_sidecar` is called from `connect()`, which takes
`is_reconnect`, so an ordinary Photon reconnect (network blip, sidecar
death) re-runs it and stalls a live gateway that is actively serving
Discord/Telegram/Slack traffic.
Dispatch it via `asyncio.to_thread` like its sibling. Same off-the-loop
class as the inbound-image decision (NousResearch#66688) and the cron-fire verifier.
Adds a regression test asserting the spawn executes on a worker thread
rather than the loop thread.
13 tasks
teknium1
reviewed
Jul 19, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for targeting a verified blocking path: current plugins/platforms/photon/adapter.py:958-971 calls subprocess.run(..., timeout=10) inline from async _start_sidecar, and the proposed asyncio.to_thread at PR line 964 preserves the existing subprocess behavior.
Problems
tests/plugins/platforms/photon/test_sidecar_lifecycle.py:246awaits startup with an always-live fake process but no mockedhttpx.AsyncClient. This reaches the real readiness loop inplugins/platforms/photon/adapter.py:1001-1022, can probe localhost for up to 15 seconds, and its broadexcept Exceptionhides the timeout.
Suggested changes
- Mock the health probe to return 200, following the existing test at
tests/plugins/platforms/photon/test_sidecar_lifecycle.py:175-184, and remove the broad exception handler so the test completes deterministically.
Automated hermes-sweeper review.
| ) | ||
|
|
||
| try: | ||
| await adapter._start_sidecar() |
Contributor
There was a problem hiding this comment.
This awaits the full readiness path with an always-live fake process but no mocked httpx.AsyncClient, so it can probe localhost and sleep through the 15-second deadline before this broad handler hides the timeout. Please mock a 200 health response as the existing lifecycle test does above, then await normally.
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
PhotonAdapter._start_sidecarisasync, but it ran the Spectrum mixed-attachment patch script with a baresubprocess.run(...): it spawns node and waits for it, withtimeout=10. Executed inline that holds the shared gateway event loop for the whole window, so no other platform's messages, heartbeats, or sessions are serviced until it returns.The same function already establishes this exact invariant twenty lines above, where the stale-dependency reinstall hops to a worker thread:
The patch spawn never got the same treatment.
It is not startup-only, either:
_start_sidecaris called fromconnect(), which takesis_reconnect, so an ordinary Photon reconnect (network blip, sidecar death) re-runs it and stalls a gateway that is actively serving Discord/Telegram/Slack traffic.Same off-the-loop class as the inbound-image decision (#66688) and the cron-fire verifier.
Related Issue
No separate issue — an event-loop-blocking gateway path.
Type of Change
Changes Made
plugins/platforms/photon/adapter.py— in_start_sidecar, dispatch the Spectrum patchsubprocess.runviaasyncio.to_threadso the node spawn/wait runs on a worker thread, mirroring the dep-reinstall hop directly above it. Behavior, arguments, timeout, and Windows console-hiding flags are unchanged.tests/plugins/platforms/photon/test_sidecar_lifecycle.py— addtest_spectrum_patch_runs_off_the_event_loop, asserting the spawn executes on a worker thread rather than the loop thread.How to Test
Run:
The new test passes with the fix and fails without it (it records
threading.current_thread()inside a fakedsubprocess.runand asserts it is not the loop thread).Full photon suite is unchanged apart from the added test: baseline
4 failed, 107 passed→4 failed, 108 passed(the 4 failures are pre-existing onmainin this environment and unrelated to this change).Checklist
Code
fix(photon):)pytest tests/plugins/platforms/photon/ -qand verified no regressions against a cleanmainbaselineDocumentation & Housekeeping
cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Awindows_hide_flags()is still applied; only the dispatch changes