http: graceful sticky drain + access-log session_id/session_action#11
Merged
Conversation
|
|
ec11d3f to
726173c
Compare
Operator-facing ``vgi_rpc.http.drain_handle(app)`` returns a ``DrainHandle(drain, shutdown, is_draining)`` for sticky-enabled apps; ``serve_http(enable_sticky=True)`` auto-installs SIGTERM/SIGINT handlers that flip drain → wait ``drain_grace_seconds`` (default 30) → invoke ``state.close()`` on every live session → exit. Pre-fork servers (gunicorn) wire equivalent shutdown hooks against ``drain_handle(app)`` in ``worker_exit``. Access log gains ``session_id`` (24-char hex) and ``session_action`` (none/open/resume/close) on sticky-touching records; absent on non-sticky servers. Canonical conformance gains ``TestSticky::test_drain_rejects_new_opens``, capability-gated on the conformance server exposing ``POST/DELETE /__test_drain__``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
e06b255 to
c5af449
Compare
rustyconover
added a commit
that referenced
this pull request
May 18, 2026
API Reference entries for surfaces introduced in #9/#10/#11: * http.md gains ``serve_http`` (was missing), ``DrainHandle``, ``drain_handle``, and the ``vgi_rpc.http.fly`` quickstart helpers. * core.md "Errors" gains a "Typed marker errors" subsection covering ``MethodNotImplementedError`` (pre-existing miss), ``SessionLostError``, and ``ServerDrainingError`` — the three classes carrying ``error_kind`` for wire-side pattern matching. mkdocs --strict builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rustyconover
added a commit
that referenced
this pull request
May 18, 2026
Feature release. Adds opt-in sticky sessions for the HTTP transport (PRs #9, #10, #11). Non-sticky wire path byte-identical to 0.16.1; existing callers see no behaviour change. * ctx.open_session / ctx.close_session runtime API on CallContext * with conn.with_session_token() as sess: client view * VGI-Session header transport with AEAD-sealed token, principal-bound AAD * DELETE /vgi/__session__ idempotent teardown endpoint * sticky_echo_headers — server-issued headers the client replays (Fly.io quickstart helpers in vgi_rpc.http.fly) * drain_handle(app) operator API + serve_http SIGTERM graceful drain * Access-log session_id + session_action fields * Typed errors SessionLostError + ServerDrainingError * Canonical TestSticky cross-language conformance group (11 tests, capability-gated) * Full spec at docs/sticky-sessions-spec.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Operator API
`drain_handle(app)`
```python
from vgi_rpc.http import drain_handle, make_wsgi_app
app = make_wsgi_app(server, enable_sticky=True)
handle = drain_handle(app) # → DrainHandle | None
if handle is not None:
handle.drain() # subsequent ctx.open_session raises ServerDrainingError
# ... wait for in-flight calls ...
handle.shutdown() # state.close() on every live session, registry cleared
```
Returns `None` for non-sticky apps so operator code can branch with `if (h := drain_handle(app)) is not None: ...`.
Pre-fork servers
```python
gunicorn config (gunicorn.conf.py)
import time
from vgi_rpc.http import drain_handle
def worker_exit(server, worker):
if (h := drain_handle(worker.app.callable)) is not None:
h.drain()
time.sleep(30) # operator-tuned grace period
h.shutdown()
```
`serve_http` built-in SIGTERM handling
```python
from vgi_rpc.http import serve_http
serve_http(
server,
enable_sticky=True,
drain_grace_seconds=30.0, # default
# install_signal_handlers=False to opt out (rare)
)
```
First SIGTERM/SIGINT: flip drain, schedule shutdown after grace, log it. Second signal during grace: skip grace, exit immediately.
Access-log fields
Both fields documented in `docs/access-log-spec.md` §4.7 and in the JSON schema. Known gap: middleware-short-circuit cases (token validation failed) currently don't produce access-log records — the typed error on the wire is the operator-facing signal.
Test plan
What's NOT in this PR
🤖 Generated with Claude Code