mux web refuses a port another hub already listens on #70
No reviewers
Labels
No labels
backlog
client
copypaste
daemon
daily-driver
field
gui
layout
muxweb
observability
perf
quic
refactor
review
robustness
security
test
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
a73x/mux!70
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/webhub-refuse-taken-port"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
[claude 2026-09-02] Branch fix/webhub-refuse-taken-port, one commit on top of
0aed35b.The mechanism was SO_REUSEPORT, not SO_REUSEADDR. Zig's
std.net.Address.listen sets both when reuse_address is true for every
family but unix (deps/zig/lib/std/net.zig:279-294), and SO_REUSEPORT is
the option that lets several processes hold one addr:port at once while
the kernel spreads new connections across them. Two plain SO_REUSEADDR
binds of the same 127.0.0.1:P are refused EADDRINUSE on this box, so the
sharing was entirely the REUSEPORT half.
Reproduced and then re-measured with two real hubs on one port:
before (main): both printed 'serving http://127.0.0.1:P', both alive,
2 LISTEN rows for P in /proc/net/tcp
after: first serves, second exits 1 with
'mux web: a hub is already running on 127.0.0.1:P
(--port N serves elsewhere)', 1 LISTEN row, and the
first hub still answers /tiles
The change:
-- socket + SO_REUSEADDR + bind + listen + getsockname, wrapped in the
same std.net.Server the accept loop already used. SO_REUSEADDR stays
on deliberately and only for what it is for: a hub restarted while its
predecessor's accepted connections are in TIME_WAIT gets its port back
rather than a spurious refusal. A port a LIVE listener holds is
AddressInUse either way.
words -- one line, rc 1, the shape of 'mux d: a daemon is already
running on PATH'. Knowingly the same wrong-ish message when some other
program owns the port, which is the trade docs/decisions.md already
records for the daemon's own AddressInUse; the advice is right either
way, and --port N is named in the line. Every other bind error keeps
the old 'cannot bind ...: '.
holds is refused, the descriptor is asked for its own two flags rather
than the call that set them (serve.zig's cloexec test is the same
shape), and the port is takeable again once that listener is gone.
hub's port while it is serving -- rc 1, 'already running', no
'serving', exactly one LISTEN row read from /proc/net/tcp rather than
from either hub, and the first hub still serving its wall. Under
timeoutfor e2e_01_boot.sh's reason: a regressed second hub wouldserve until killed, so it has to show up as 124 rather than as a hung
suite.
Gate, honestly:
make checkFAILS in this VM, and it fails identicallyon unmodified main -- 'server_test_upgrade.test.Server: an upgrade asked
for during a session's hangup is refused, not attempted', with the
allocation leak that follows it. Verified by stashing this diff and
re-running: main gives 1114/1116 tests passed with that one failure,
this branch gives 1115/1117 with the same one failure -- the added test
passes and nothing else moved. It looks timing-bound (Pty.term_grace_ms
is 500 ms and the test writes a candidate file between the end_reply and
the upgrade_req); under -j2 a second timing test,
'main.test.waitPidGone', joins it. Nothing in this diff touches those
paths. The e2e suite could not be run here at all: it hard-requires nvim
and this VM has none, so the new leg was validated by running the same
assertions by hand against the built binary, with an isolated
XDG_STATE_HOME and XDG_RUNTIME_DIR.
Migrated from git-collab patche
7d4cf0531e80b67fd529982c1cb57c4620741197.Original author: forge-4 forge-4@a73x.sh.
Fixes #27
Revision 1:
c8cf81131363e3ccfeb1e21d1b14002f56466b45; base0aed35be5dc8ee07c55dd6d682c16bbd18733086[claude 2026-09-02] Checkpoint review of the guest's patch (forge-4, software-factory trial 2). Diff read in full. Root cause holds: std.net.Address.listen sets SO_REUSEPORT from the one reuse_address bit, and REUSEPORT is what let two hubs bind. listenLocal keeps REUSEADDR only; the unit test asks the descriptor for both flags and proves the second bind refuses and the port is takeable once the first listener is gone. The port-0 claim in the doc comment is true (webhub_main returns error.Usage on port 0). The e2e leg counts LISTEN rows from /proc/net/tcp and checks the survivor still serves.
make checkon this box: exit 0 on the branch. The single failure the guest reported (server_test_upgrade hangup case + leak) did not reproduce here; it is the known slow-host flake, not this diff.Branch is 1 behind local main (6932ec23, unpushed). Approve/merge is the human's call.
View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.