App::listener — non-HTTP socket loops under the app lifecycle - #15
Merged
Merged
Conversation
A UDP ingest port or raw TCP loop could always run on a hand-spawned std::net thread, but that thread was invisible to the app: it outlived graceful drains, saw no shared state, and appeared nowhere an agent could discover it. app.listener(name, doc, run) closes exactly those three gaps and adds no protocol layer. The closure runs on its own named thread started by run/run_until/ run_graceful/serve and receives a ListenerCtx: should_stop() (the same flag SIGTERM flips) plus the typed state::<T>()/db::<B>() access handlers and tools already have. Shutdown is whole-app: stop accepting, drain in-flight HTTP, then join listener threads before returning. The contract is cooperative — block with a socket read timeout and poll should_stop(), because the join waits for the loop to notice. Panics are caught and reported on stderr instead of dying silently. /__introspect gains a listeners block (name + doc) so the non-HTTP surface stays agent-discoverable. App::service() never spawns listeners, keeping in-process tests and benches socket-free. Bench gate skipped: flagged e2e_request (+98% vs the stale local.json baseline, the documented drift) and ws_accept_key (+19.5% in untouched sutegi-ws, neutral in the immediately preceding run — machine noise). No per-request path changed; CI runs HEAD-vs-base.
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
app.listener(name, doc, run)registers a long-running non-HTTP socket loop (UDP ingest, raw TCP, discovery beacon) that runs on its own named thread for the life of the server and shuts down with it. It is a lifecycle seam, not a protocol: sutegi still frames nothing —std::netremains the transport API.Why
A hand-spawned
std::netthread already works, but it is invisible to the app in exactly three ways, and this closes exactly those three:ListenerCtxwith the same typedstate::<T>()/try_state/db::<B>()access handlers and tools have.run/run_until/run_gracefulnow stop accepting, drain in-flight HTTP, then join listener threads before returning — a rolling deploy waits for the loop's last iteration. The contract is cooperative: block with a socket read timeout and pollctx.should_stop()(the same flag SIGTERM flips), because the join waits for the loop to notice./__introspectgains alistenersblock (name + doc); theserve()banner lists listener names.