feat(p2p): follow grants onto the Endpoint, and withdraw a listener on close - #79
feat(p2p): follow grants onto the Endpoint, and withdraw a listener on close#79masa-koz wants to merge 2 commits into
Conversation
…n close The server side of this is ISEKAI-link-server #203: a grant is now keyed on (owner_endpoint, allowed_endpoint, protocol) rather than on a listener, so it survives the app being restarted. This is the client half, and it has to ship before that is deployed — the old routes are gone. The proxy client moves to /v1/peer/grants and /v1/peer/pairing-codes with the caller as the owner, `Grant` loses `listener_id`, `PairingCode` names the owner Endpoint it lets someone in to, and `ReachableListener` carries the owner Endpoint — which is the field that identifies a camera across a restart, where `listener_id` does not. `show_pairing_code` sends the session's protocol now that no listener is there to imply it. Closing a listener session deletes the listener rather than leaving it to lapse. Until it lapsed it kept appearing in every paired peer's list as something that looks connectable and is not — which is exactly what was seen on hardware. This was not safe to do before: the delete used to cascade the grants away, so withdrawing a listener would have thrown away every pairing. A failure is logged, not returned; the lease ends it within the hour anyway and a caller on the way out can do nothing with the error. camera-client shows one row per camera rather than one per listener. A grant reaches every listener its owner is running, and a camera that crashed without withdrawing its old one runs two, only one of which connects. The row kept is the listener with the later deadline — both were leased for the same span, so the later deadline is the later start — with the id as a tie-break so the selected camera does not move under the operator between refreshes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
概要ISEKAI-link-server #203(Grant を Listener から Endpoint へ移す)のクライアント側。ルート移行、 対応関係はきれいだと思います。
指摘1. アプリ終了時に DELETE が本当に飛ぶかは、プロセスが生き残るかに依存している(要確認)経路を追うと:
一方 PR で「未実施」とされている確認項目のうち「閉じたときに古い Listener が一覧から消えるか」は、まさにここが効く部分です。実機で見るときは 対処としては、 2.
|
Cancelling the session only starts the withdrawal. The delete that follows is an HTTP request over the same msquic registration the process is about to drain, and nothing was waiting for it — so whether the listener came down was a race between that request and the drain, which is exactly the outcome this PR set out to guarantee. `ServerHandle` now hands back the command loop's join handle, camera-server parks it where `main` can reach it after the window closes, and `main` awaits it with a bound before draining msquic. The handle is shared rather than owned because the app is dropped inside `run_native` and what has to be awaited outlives it. The withdrawal itself is bounded too. Without a timeout, closing the window while the proxy is unreachable would keep the process alive until the transport gave up on a request whose only purpose is tidiness. Three seconds inside `close`, six around the whole shutdown, so a slow proxy is decided by the inner bound rather than by the outer one racing it. Also documents what `one_per_camera` is resting on, since the listing does not carry enough to check either assumption: that every listener of one camera is leased for the same span — vary the TTL and a long-leased dead listener outranks a short-leased live one — and that `expires_at` keeps arriving in the one format whose text order is time order. And on `ListenerSession`, that the pairing-code and grant methods act on the Endpoint rather than on the session they hang off, while `close` is the one that is about this listener. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks — you were right that the DELETE was a race, and it was not firing reliably. 1. The withdrawal was not actually awaited. Fixed rather than only noted. 2. No timeout on the delete. Three seconds inside 3. 4. Release order. The premise you asked for is now explicit in the PR body: there is no distributed build of these apps and no other consumer of the proxy, so the switch is "rebuild both, then deploy" rather than a staged rollout. That is why #203 removed the old routes outright. The window is however long a rebuild takes and affects nobody else — which is the same reasoning that settled the aliases question on #203. 5.
On the hardware check: I will confirm |
The server side of this is ISEKAI-link-server #203: a grant is now keyed on
(owner_endpoint, allowed_endpoint, protocol) rather than on a listener, so it
survives the app being restarted. This is the client half.
The proxy client moves to /v1/peer/grants and /v1/peer/pairing-codes with the
caller as the owner,
Grantloseslistener_id,PairingCodenames theowner Endpoint it lets someone in to, and
ReachableListenercarries theowner Endpoint — which is the field that identifies a camera across a restart,
where
listener_iddoes not.show_pairing_codesends the session's protocolnow that no listener is there to imply it.
Closing a listener session deletes the listener rather than leaving it to
lapse. Until it lapsed it kept appearing in every paired peer's list as
something that looks connectable and is not — which is exactly what was seen
on hardware. This was not safe to do before: the delete used to cascade the
grants away, so withdrawing a listener would have thrown away every pairing.
Cancelling the session only starts that withdrawal, though. The delete is an
HTTP request over the same msquic registration the process is about to drain,
so
ServerHandlehands back the command loop's join handle, camera-serverparks it where
maincan reach it after the window closes, andmainawaitsit with a bound before draining. The withdrawal has its own three-second bound
inside
close, six around the whole shutdown, so a proxy that is unreachableat exit is decided by the inner bound rather than keeping the process alive
until the transport gives up.
camera-client shows one row per camera rather than one per listener. A grant
reaches every listener its owner is running, and a camera that crashed without
withdrawing its old one runs two, only one of which connects. The row kept is
the listener with the later deadline — both were leased for the same span, so
the later deadline is the later start — with the id as a tie-break so the
selected camera does not move under the operator between refreshes. Both
things that rests on are written down at the function, because the listing
does not carry enough to check either: that the TTL is uniform, and that
expires_atkeeps arriving in the one format whose text order is time order.Verification
LIBCLANG_PATH=/usr/lib/llvm-18/lib RUSTFLAGS="-L /usr/lib/llvm-18/lib" cargo test --manifest-path rust/Cargo.toml --workspace— all passing, including new tests pinning the grant and pairing-code routes and request bodies, that a listing carriesowner_endpoint, and the one-row-per-camera collapsecargo clippyon the touched crates introduces no new warningscargo fmton the touched files only; these crates are not rustfmt-clean on mainRelease order
There is no distributed build of these apps and no other consumer of the
proxy: both sides are built from source by the same person who runs the
server, so the switch is "rebuild both, then deploy", not a staged rollout.
That is why #203 removed the old routes outright instead of keeping aliases
for a release. Between merging this and deploying #203, whichever order, an
app built from the other side's
mainwill get 404s on pairing and grantmanagement — the window is however long the rebuild takes, and it affects
nobody else.
#203 is already merged. Merge this and rebuild before deploying the
server.
Not yet done on hardware
Pair, connect, close the camera-server, re-open it, and confirm the viewer's
Refresh shows the camera again with the pairing intact — the case that started
this. Also that the old listener disappears from the list at close rather than
lingering for the rest of its lease; for that one, checking that
DELETE /v1/peer-listeners/...reaches the proxy's log is what separates "didnot send" from "sent and failed". I will run both once #203 is deployed and
report what happens.
🤖 Generated with Claude Code