transport: probe Windows named pipes without a Tokio reactor - #1525
gdamprint-cmyk wants to merge 2 commits into
Conversation
`is_socket_path` and `remove_socket` are synchronous by contract, but both opened the pipe with `ClientOptions::new().open()`. Tokio's named-pipe client registers the pipe handle with the ambient reactor, so calling either from a thread that has no runtime context panics: ``` thread '<unnamed>' panicked at tokio-1.49.0/src/net/windows/named_pipe.rs:1005 there is no reactor running, must be called from the context of a Tokio 1.x runtime ``` Jcode Desktop drives the SDK from plain `std::thread`s, so its bridge processes take this path and abort. The panic happens on the `open` call itself, before any success or failure is known, so it is not specific to a missing endpoint. Both probes now go through a small `probe_pipe` helper built on `std::fs::OpenOptions`, the same approach `SyncStream::connect` already uses. `ERROR_PIPE_BUSY` still maps to "a server is listening", so the busy-pipe behaviour is unchanged. Test: `sync_pipe_probes_do_not_panic_without_a_tokio_reactor` binds a real listener inside a `#[tokio::test]` runtime, then probes it from a `std::thread` that deliberately has no runtime. That split is the situation in the bug: the server owns a runtime, the synchronous probes do not. The test is load-bearing, verified rather than assumed. Two things had to be got right for it to mean anything: - the pipe must actually exist. An earlier version probed a path that was never bound, and it passed with and without this fix, because the open fails before tokio reaches the reactor registration; - the listener must be bound under a runtime. An earlier version used a plain `#[test]`, which then panicked inside `Listener::bind` instead. With `probe_pipe` reverted to `ClientOptions::open`, the spawned thread panics with the message above and this test fails; the other six tests in the crate pass in both states, so it isolates this defect.
|
This comment has been minimized.
This comment has been minimized.
Reported as P2 on 1jehuang#1525: the regression test called `is_socket_path` and then `remove_socket` on the same single-instance listener. Probing consumes the only available instance of a Windows named pipe, so the second open can return ERROR_PIPE_BUSY and return an error without ever reaching the code under test. A reverted `ClientOptions::open` in `remove_socket` could therefore have slipped through while the test still passed. The test now binds a second, separate pipe for `remove_socket`, so each probe hits a freshly available instance and each is independently covered. Load-bearing proof repeated after the change, since the previous proof was for the previous version of the test: with `probe_pipe` reverted to `ClientOptions::open`, `sync_pipe_probes_do_not_panic_without_a_tokio_reactor` is the only one of the seven that fails, and it fails with the reactor panic in the spawned thread.
|
Correct, and the test was weaker than it claimed.
Fixed in The load-bearing proof is repeated after the change rather than reused, because in the spawned thread, not in the setup. With the fix in place all seven pass |
Closes #1524
Problem
is_socket_pathandremove_socketare synchronous by contract, so a caller canask "is a daemon already listening?" without owning a runtime. Both nevertheless
opened the pipe through tokio's named-pipe client, which registers the handle
with the ambient reactor:
The panic happens on the
opencall itself, before any result is known, so itis not specific to a missing or busy endpoint. Any embedder that drives the SDK
from plain
std::threads takes this path, which is what Jcode Desktop's bridgeprocesses do.
It also cannot be caught off Windows:
windows.rscompiles only on Windows, andthe path is only reached from a reactor-less thread.
Fix
Both probes go through one
probe_pipehelper built onstd::fs::OpenOptions,the same approach
SyncStream::connectin this file already uses:ERROR_PIPE_BUSYstill maps to "a server is listening", sobusy_pipe_is_reported_as_a_live_socket_pathis unchanged and keeps passing.One file, no API change, no behavioural change on the success and busy paths.
Test
sync_pipe_probes_do_not_panic_without_a_tokio_reactorbinds a real listenerinside a
#[tokio::test]runtime and then probes it from astd::threadthatdeliberately has no runtime. That split is the situation in the bug: the server
owns a runtime, the synchronous probes do not.
Edge cases and the two ways this test can lie
Both were hit while writing it, and both are worth stating because either one
yields a test that passes with and without the fix:
before tokio reaches the reactor registration, so nothing panics. The first
version of this test did exactly that and passed against the unfixed code.
#[test]cannot callListener::bindat all, so the panic then happens during setup rather than inthe code under test, which makes the test fail for the wrong reason.
With the fix reverted, the failure is the reactor panic in the spawned
thread (
thread '<unnamed>'), not in the setup, and the other six tests inthe crate pass in both states. That is the evidence the test isolates this
defect rather than merely noticing that something changed.
cargo test -p jcode-transport --libis 7 passed, 0 failed.cargo fmt --checkis clean for the crate.