summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-02 00:49:43 -0300
committerGabriel Schneider <[email protected]>2026-10-02 00:49:43 -0300
commit0cc61452575589df50d607e0c45222efe87e7e8b (patch)
tree9bb2c07c4d5a90838d3c9f0e43489b534c43ac0c
parentb70ef491f1553929920b9b653ad3240466478e3e (diff)
downloadpardes-0cc61452575589df50d607e0c45222efe87e7e8b.tar.gz
pardes-0cc61452575589df50d607e0c45222efe87e7e8b.zip
cloud9 re-pinned at fe9772b, whose 9ns invalidates a dead mount's entry off the serving thread; selfmount kills a session under a burst of reads of it through the registry mount and checks every read returns
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--build.zig.zon4
-rw-r--r--test/selfmount.py36
2 files changed, 38 insertions, 2 deletions
diff --git a/build.zig.zon b/build.zig.zon
index a542f83c..b1f501fe 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -9,8 +9,8 @@
// read-only HTTPS URL is what a manifest can carry. Re-pin with
// `zig fetch --save=cloud9 git+https://git.sr.ht/~gbrls/cloud9#<commit>`.
.cloud9 = .{
- .url = "git+https://git.sr.ht/~gbrls/cloud9#e66b2cfcf4e196ec04c73a33364cb567dbcc5d18",
- .hash = "cloud9-0.1.0-yt86qqQnHgDP1VHdmSD595GnfBPVkHSoe_LvCL4bLy9y",
+ .url = "git+https://git.sr.ht/~gbrls/cloud9#fe9772b178575fcab93de09fc61c2f13db7db1a8",
+ .hash = "cloud9-0.1.0-yt86qh4qHgC6OpG4gZE9yoQaO8huc5mnQCaqGwuJ0OU1",
},
// Pinned to the exact libvaxis commit ghostty is built against, so it
// is compatible with ghostty's uucode configuration (the one we share
diff --git a/test/selfmount.py b/test/selfmount.py
index c6565b5e..5aa9749a 100644
--- a/test/selfmount.py
+++ b/test/selfmount.py
@@ -267,6 +267,42 @@ def run(binary):
took = time.monotonic() - started
check(got == '0' and took < 5 and 'filter-hi' in client.read('/log').decode(),
f'an Edit filter writing its own ctl through the mount answers promptly ({got!r}, {took:.1f}s)')
+ # Another session dies under a burst of reads of it through the
+ # registry mount: every read returns (an error is fine), none
+ # hangs. 9ns invalidates the dead entry from a thread of its own,
+ # never the one that must answer the reads holding its parent.
+ other = name + 'k'
+ other_sock = f'{runtime}/pardes-9p-{other}.sock'
+ other_env = dict(env, PARDES_DUMP=f'/tmp/{other}.dump.zon')
+ other_pid, other_master = pty.fork()
+ if other_pid == 0:
+ os.execve(binary, [binary, '--tty', '--9p=' + other], other_env)
+ os.set_blocking(other_master, False)
+ deadline = time.monotonic() + 15
+ while not os.path.exists(other_sock) and time.monotonic() < deadline:
+ try:
+ os.read(other_master, 65536)
+ except OSError:
+ pass
+ time.sleep(.05)
+ codes = Path(f'/tmp/{name}.codes')
+ codes.unlink(missing_ok=True)
+
+ def kill_other():
+ time.sleep(1.5)
+ os.kill(other_pid, signal.SIGKILL)
+ killer = threading.Thread(target=kill_other, daemon=True)
+ killer.start()
+ ran = run((f"for k in 1 2 3 4 5; do (end=$((SECONDS+4)); while [ $SECONDS -lt $end ]; do "
+ f"timeout 10 cat /mnt/9p/pardes/{other}/index > /dev/null 2>&1; echo $? >> {codes}; done) & done; wait\n").encode())
+ killer.join(5)
+ os.waitpid(other_pid, 0)
+ got_codes = codes.read_text().split() if codes.exists() else []
+ codes.unlink(missing_ok=True)
+ for path in (other_sock, f'/tmp/{other}.dump.zon'):
+ Path(path).unlink(missing_ok=True)
+ check(ran == '0' and got_codes and '124' not in got_codes,
+ f'reads of a session killed under them all return ({ran!r}, {len(got_codes)} reads, {got_codes.count("124")} hung)')
# Forty event reads held through the mount, a follower each, and
# the mount still answers the rest: 9ns keeps workers past what
# the editor holds (128), so `cat layout` is not queued behind