Repository navigation
net: SocketBackend rejects a private bind made before its socket connects; QtWebSocketBackend queues it #902
Description
What
A non-blocking bind (bindModel) on morph::net::SocketBackend made before its socket has connected is rejected with DisconnectedError. It is not queued. QtWebSocketBackend queues the same bind and sends it in FIFO order once connected fires, as docs/spec/core/backend.md ("Queueing before the first connect") documents for that transport. As a result, the two transports behind IBackend behave differently for the same call sequence.
SocketBackend::Core::fileControl, include/morph/net/socket_backend.hpp:771 on master @ d2e828fc:
void fileControl(::morph::wire::Envelope env, PendingControl pending) {
MORPH_ZONE("SocketBackend::fileControl");
note("SocketBackend::bindModel");
if (!connected.load() || !conn) {
pending.reject(std::make_exception_ptr(::morph::backend::DisconnectedError{}));
return;
}Both constructors call start() and return while the connect is still pending (socket_backend.hpp:106, :119). A caller that binds straight after construction, without first calling waitForConnected (:154), therefore races the connect.
Why it matters
The declarative UI program binds a handler as soon as a screen declares its model, which can happen before the socket is up. Spec 4 (docs/superpowers/specs/2026-10-04-examples-migration-design.md, "The transport binds early") requires: "Both transports queue a bind made before the first connect and send it once connected; SocketBackend gains this queue, which QtWebSocketBackend already has." Over the raw-socket transport (the terminal client's path), every such call fails today.
A single-threaded client cannot fall back on waitForConnected either, because it blocks.
Verification status
- Inferred from reading the code at the lines above, on
d2e828fc. Not reproduced: no test was written or run. - Not checked: whether any test already pins the current rejection as intended behaviour.
docs/spec/core/backend.mddocuments the queue only forQtWebSocketBackendand says nothing aboutSocketBackendeither way. So this may be an undocumented divergence rather than a broken documented contract. - First noted in a comment on docs: declarative UI program — design and implementation plans #882.
Suggested shape
Mirror QtWebSocketBackend. Queue a private bind filed before the first connect and drain it in FIFO order from onConnected. Reject queued entries exactly once if the backend is torn down before ever connecting. Keep keyed binds rejected with disconnected, as Qt does. Then make the "Queueing before the first connect" paragraph in docs/spec/core/backend.md name both transports.
What would change the verdict
Close this as intended if the project decides SocketBackend callers must waitForConnected first. In that case spec 4 §3 needs correcting instead. It is fixed when a test binds on a fresh SocketBackend before connect and the bind settles with a model id once the server accepts. The test must fail with the queue removed.
Activity
Yaraslaut commentedon Oct 12, 2026
Triage: valid (area: core). Premise re-checked on master
1a4d444e. This is from reading the code, not a test run.SocketBackend::Core::fileControl(include/morph/net/socket_backend.hpp:771) still rejects withDisconnectedErrorwhen!connected.docs/spec/core/backend.md"Queueing before the first connect" (line 233) documents the queue forQtWebSocketBackendonly, and says nothing aboutSocketBackendeither way.tests/net/test_socket_backend.cpppins the rejection as intended.So this is an undocumented divergence between the two transports behind one interface. The issue's "what would change the verdict" still holds: spec 4's "the transport binds early" requires the queue. The suggested shape, mirroring Qt's FIFO queue with keyed binds still rejected, fits the architecture. The test must fail with the queue removed.
net: SocketBackend queues a private bind made before its socket connects