Skip to content

net: SocketBackend rejects a private bind made before its socket connects; QtWebSocketBackend queues it #902

Closed

Description

@Yaraslaut

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.md documents the queue only for QtWebSocketBackend and says nothing about SocketBackend either 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

  1. Yaraslaut commented on Oct 12, 2026

    @Yaraslaut
    MemberAuthor

    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 with DisconnectedError when !connected.
    • docs/spec/core/backend.md "Queueing before the first connect" (line 233) documents the queue for QtWebSocketBackend only, and says nothing about SocketBackend either way.
    • No test in tests/net/test_socket_backend.cpp pins 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.

  2. added a commit that references this issue on Oct 12, 2026
    ac31a6f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreSubsystem: coreenhancementNew feature or requesttriage: validWell-framed; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions