Skip to content

Parent-owned TCP listener seam — 2026-07-11

Status: accepted as the implementation seam for EDGE-011. This is not a supervisor implementation.

Decision

TCP bind/listen policy now lives in servery._listener. A future parent process can call bind_tcp_listener() once, retain that socket, and give the threaded or ASGI runtime an already-bound listener. Both runtimes validate that the socket is an open, listening TCP stream with the address family implied by the configured host.

Adoption duplicates the descriptor. The caller owns and closes the original; the runtime owns and closes only its duplicate. Consequently, closing or failing one runtime cannot invalidate the parent's handle, and the parent may close its copy after a worker has adopted it without stopping that worker. POSIX duplicate descriptors still share kernel file-status flags; the ownership guarantee is about descriptor lifetime, not independent blocking-mode state.

TLS remains worker-local. The threaded runtime wraps only its adopted duplicate, and the ASGI event loop receives only its duplicate. The parent's retained listener is never replaced with an SSLSocket and owns no worker TLS context.

Compatibility

make_server(config) remains the direct bind-and-activate API. Internally it uses the same binder and transfers an adopted descriptor to the existing server object. Port zero still delegates ephemeral selection directly to the kernel. For a busy nonzero port, only address-in-use triggers the existing bounded forward scan, and the selected port remains reflected in the server's effective configuration. Other bind errors still surface immediately.

make_server(config, listener=socket) and asgi.serve_forever(config, listener=socket) are the explicit adoption seams. Supplying a listener disables runtime bind and port scanning: the socket's bound address is authoritative. Existing callers that omit it retain their prior APIs and behavior.

Evidence

Deterministic threaded and ASGI tests cover both sides of ownership:

  • runtime shutdown leaves the caller's listening descriptor valid;
  • caller shutdown after adoption leaves the runtime able to serve requests;
  • two independently constructed threaded generations adopt the same parent listener, serve distinct roots in sequence, and close independently;
  • TLS wraps the threaded runtime's duplicate while the parent descriptor stays a raw listening socket;
  • a bound but non-listening socket is rejected without being closed;
  • direct ephemeral binding and busy-port scanning remain covered; and
  • ASGI listener-validation failure runs lifespan shutdown and preserves the caller's invalid socket for caller-owned cleanup.

The seam intentionally does not define worker generation, fork/spawn descriptor transfer, readiness, restart, or reload policy. Those belong to the supervisor tasks that consume this boundary.