Skip to content

update io.pilot.plainweb v1.0.1: exits with its daemon, keeps a live instance's socket - #113

Draft
TeoSlayer wants to merge 1 commit into
mainfrom
fix/plainweb-lifecycle
Draft

TeoSlayer wants to merge 1 commit into
mainfrom
fix/plainweb-lifecycle

Conversation

@TeoSlayer

@TeoSlayer TeoSlayer commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Draft. Do not merge yet. Merging this runs publish-rich-from-r2.sh, which builds the catalogue entry from whatever is on R2 under bundles/io.pilot.plainweb/1.0.1/. Merge only after all of these:

  1. fix(scaffold): generated adapters exit with their daemon and drop abandoned calls #111 is merged (the template fix this version exists to ship).
  2. fix(publish): rich republish keeps the listing's categories, license and source_url #112 is merged. Without it, the catalogue entry this publish generates drops plainweb's categories and license and changes its source_url.
  3. The four 1.0.1 bundles are built from that main with publish.BuildBundle, signed with plainweb's publisher key (ed25519:9oZGhTSuJJ5xaePW89I9QSOnyp8p83igvGj0jEUuLoE=), uploaded to R2 and checked over their public URLs (docs/UPDATING-BUNDLES.md).

What this is

A version bump only (1.0.0 → 1.0.1). The plainweb adapter is generated from the template, so the fix is in #111 and this PR ships it. Nothing in plainweb's spec changes.

Problems in the published 1.0.0 bundles (all four platforms)

Measured on published 1.0.0
Orphan The daemon puts each app in its own process group. When the daemon dies without stopping the app, the app is reparented to pid 1 and keeps answering IPC. This happens on macOS, which has no Pdeathsig. On Linux it happens under every released daemon: v1.13.9 pins app-store v1.0.2, which sets no Pdeathsig. During the audit, the Mac it ran on had one: a plainweb pid with ppid=1 next to the daemon's own copy, both on the same app.sock path. It was gone after the daemon restarted.
Socket hijack Closing the listener unlinks app.sock by name. If a newer instance has since bound that path, the older instance's exit deletes the newer one's socket and takes it offline.

Verified on 1.0.1 rebuilt from #111

Built from 80d19b2 (#111 head) with publish.BuildBundle, go1.26.8, and a TEST-ONLY key. Nothing was uploaded or signed with a real key. The binary sha256 was identical across two builds on all four platforms.

Harness results. darwin/amd64 ran under Rosetta, Linux ran in docker --init, and --network none was used for the offline runs:

darwin/arm64 darwin/amd64 linux/arm64 linux/amd64
harness verify (sha, signature, pin, format) OK, native OK, rosetta OK, ELF arm64 OK, ELF x86-64
pilot-app verify OK OK OK OK
plainweb.help ×200 200/200, fd 7→7 200/200, fd 7→7 200/200, fd 7→7 200/200, fd 17→17
plainweb.fetch(https://example.com) ×200 200/200, fd 10→10 200/200, fd 10→10 200/200, fd 8→8 200/200, fd 17→18
offline help ×200 200/200 200/200 200/200 200/200
orphan, no Pdeathsig (1.0.0: ORPHAN) SELF-EXIT 216–308 ms SELF-EXIT 314–378 ms SELF-EXIT 243–310 ms SELF-EXIT 210–314 ms
orphan, Pdeathsig n/a n/a PASS 0–4 ms PASS 0–3 ms
SIGTERM 2–5 ms, code 0, socket removed 5–14 ms, code 0, socket removed 1–35 ms, code 0, socket removed 4–10 ms, code 0, socket removed
  • RSS: 5,000 help calls on darwin/arm64 level off at 18.8 MB (17.9 MB at call 250, 18.9 MB at call 5,000). fd stayed at 7 throughout.
  • Socket hijack with the real binaries: instance A serves, app.sock is replaced by instance B at the same path, then A gets SIGTERM. On 1.0.0: SOCKET GONE, and B is unreachable (connect: no such file or directory). On 1.0.1 (arm64 and amd64): B's socket keeps the same inode and B still answers.
  • Orphan plus respawn: A's parent is SIGKILLed and B starts on the same path. On 1.0.0, A is still alive after 2 s with ppid=1. On 1.0.1, A logs parent pid N is gone; shutting down, exits, and B keeps its socket.
  • pilot-app verify-submission on this submission: VERIFY OK, 4 platforms. pilot-app verify-update: UPDATE GATE OK (1.0.1 ≥ 1.0.0).

Not fixed by this PR

app.sock is still left on disk after a supervisor stop (daemon shutdown, upgrade, uninstall). The supervisor stops apps with SIGKILL, and an app can't clean up after that. Measured with the real supervisor spawn plus a ctx cancel: app-store v1.0.3 gives code=-1, app.sock left, for both 1.0.0 and 1.0.1. A SIGTERM-first stop (stacked on pilot-protocol/app-store#39, not yet opened) gives code=0 in about 21 ms with app.sock removed. That fix belongs in app-store plus a daemon release. Until then, both the adapter and the supervisor delete a stale socket before Listen.

🤖 Generated with Claude Code

…eps a live instance's socket

Version bump only. 1.0.1 is 1.0.0 rebuilt from the template fix in
#111; nothing in plainweb's spec changes.
The published 1.0.0 adapter, on all four platforms:

- keeps running after its daemon dies without stopping it (reparented to
  pid 1 and still answering IPC): always on macOS, and on Linux under
  every released daemon (v1.13.9 pins app-store v1.0.2, which sets no
  Pdeathsig);
- on SIGTERM, unlinks app.sock by name, which deletes the socket of a
  newer instance started at the same path and takes it offline.

Rebuilt from #111 (go1.26.8, TEST-ONLY key), the orphan exits by itself
210-380 ms after its parent dies on darwin/arm64, darwin/amd64
(Rosetta), linux/arm64 and linux/amd64, and the newer instance keeps
its socket and keeps serving.

Do not merge before #111 is merged and the four 1.0.1 bundles, built
from that main and signed with plainweb's publisher key, are on R2:
merging runs publish-rich-from-r2.sh, which builds the catalogue entry
from whatever is on R2 for 1.0.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants