fix(netwatch): mark the socket broken when a rebind fails, so sends retry it - #201
fix(netwatch): mark the socket broken when a rebind fails, so sends retry it#201jgeluk wants to merge 1 commit into
Conversation
…etry it
SocketState::rebind deliberately stays in Closed on a bind failure
('will retry on next attempt'), but nothing armed that retry:
UdpSocket::rebind propagated the error without setting is_broken, and
maybe_rebind — the only recovery path on the send/recv side — is gated
on is_broken. A single failed rebind (typically during sleep->wake,
when the network-change handler fires before interfaces are back up)
therefore stranded the socket in Closed permanently: every subsequent
send logged 'socket closed' at WARN and failed with BrokenPipe, at the
caller's full send rate, until the process restarted.
Mark the socket broken when the rebind fails, so the next send/recv
retries the bind and the socket heals as soon as the address is
bindable again. A deliberate close() still stays closed: it never sets
is_broken, pinned by a new test.
|
Thanks for taking a close look — the "hammering forever" worry is the right thing to check, so let me be precise about what It is not a loop. The status quo is the unbounded one. Today a single failed rebind (bind fails once while interfaces are still coming up) leaves the socket in A deliberate Same defect class upstream: iroh#4289 ("Failed socket rebind kills noq EndpointDriver") is the same shape one layer up — you noted 7b06ecbe was "in the right direction but maybe not sufficiently careful". I agree iroh is the right place for policy (when to rebind, backoff across interface events); but netwatch is the place that decides whether a failed rebind is recoverable at all, and today it isn't. Fixing it in iroh alone can't reach a socket that netwatch has stranded. If you still want an explicit bound, I'm happy to make it smaller/safer: a retry counter or backoff on the broken flag (e.g. give up marking broken after N consecutive failed rebinds, or exponential spacing between Logs: the original capture rotated out, but I have a small repro binary (stock netwatch |
|
Next time - please use the correct PR template though :/ |
Fixes #200.
Problem
SocketState::rebind()deliberately stays inClosedon a bind failure ("will retry on next attempt"), but nothing arms that retry:UdpSocket::rebind()propagates the error without settingis_broken, andmaybe_rebind()— the only recovery path on the send/recv side — is gated onis_broken. A single failed rebind (typically during sleep→wake on macOS, when the network-change handler fires before interfaces are back up) strands the socket inClosedpermanently: every subsequent send logssocket closedat WARN and fails withBrokenPipe, at the caller's full send rate, until the process restarts.Fix
Mark the socket broken when the rebind fails, so the next send/recv retries the bind and the socket heals as soon as the address is bindable again. A deliberate
close()still stays closed: it never setsis_broken, pinned by a new test.Tests
test_failed_rebind_marks_broken_so_sends_retry— forces a rebind failure, asserts the socket self-heals on the next send once the address is bindable again.test_deliberate_close_stays_closed— pins thatclose()remains terminal.cargo test -p netwatch --lib: 18 passed.Field-tested for several days across daemons that previously exhibited the storm: sleep/wake now recovers within a second, zero
socket closedevents.🤖 Generated with Claude Code