Skip to content

don't log the deliberate multicast re-bind as a socket error - #49

Merged
crgimenes merged 1 commit into
crgimenes:trunkfrom
johncohn:feature/quiet-multicast-rebind-log
Aug 20, 2026
Merged

crgimenes merged 1 commit into
crgimenes:trunkfrom
johncohn:feature/quiet-multicast-rebind-log

Conversation

@johncohn

Copy link
Copy Markdown
Contributor

udpControlSupervisor closes the listener every udpRebindInterval to force a
fresh group join. That close is exactly what unblocks ReadFrom, so the read
loop always lands in its error branch and logs the close it was asked to cause:

kutta: UDP control: read udp 0.0.0.0:9000: use of closed network connection

every 30s, for as long as a multicast listener runs. On an exhibit Pi left up
for an afternoon that was 375 lines of routine maintenance reported as failure.

It reads convincingly like a listener that has died, which is misleading in
exactly the situation the log exists to help with -- while debugging why an
ESP32 knob box wasn't reaching kutta, this cost me a detour before I realised
the socket was fine and the message was self-inflicted.

The fix passes the read loop an atomic.Bool that the supervisor sets just
before a deliberate close, so it can tell that case apart from a socket that
failed on its own. Real errors still log, including on the multicast path --
the flag is only ever set for the re-bind, and is a fresh variable per
iteration. Unicast callers pass nil, since they never close early.

No behaviour change beyond the logging.

Verified with gofmt, go vet, go build, and go test -race ./..., plus on
the Raspberry Pi exhibit where the noise was found.

udpControlSupervisor closes the listener every udpRebindInterval to force
a fresh group join. That close is exactly what unblocks ReadFrom, so the
read loop always ends up in its error branch and logs the close it was
asked to cause -- a multicast listener printed

    kutta: UDP control: read udp 0.0.0.0:9000: use of closed network connection

every 30s for as long as it ran. On an exhibit Pi left up for an
afternoon that was 375 lines of routine maintenance reported as failure,
and it reads convincingly like a listener that has died, which is
misleading in exactly the situation the log is there to help with.

Pass the read loop an atomic.Bool the supervisor sets just before a
deliberate close, so it can tell that case apart from a socket that
failed on its own. Real errors still log, including on the multicast
path: the flag is only ever set for the re-bind. Unicast callers pass
nil, since they never close early.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@crgimenes crgimenes left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right diagnosis: the close is what unblocks ReadFrom, so the loop was reporting its own maintenance. Merging, thanks.

@crgimenes
crgimenes merged commit ae5ca65 into crgimenes:trunk Aug 20, 2026
6 checks passed
crgimenes added a commit that referenced this pull request Aug 20, 2026
Three loose ends from #42, #46 and #49, none of which change what those
PRs set out to do.

outboundInterface hardened: the deferred Close was unchecked (errcheck),
the LocalAddr type assertion could panic, and a routing table with no
default route to dial against made the multicast join fail outright --
which is exactly the exhibit case, a Pi wired straight to the sender with
a static address and no gateway. It now falls back to the first interface
that is up, not loopback and multicast-capable, so that machine joins on
the one interface it has instead of refusing.

udpControlLoop drops the atomic.Bool: net.ErrClosed already distinguishes
a deliberate close from a socket that failed on its own, so the signature
and the plumbing through both call sites go away with it.

-max-kn rejects NaN, Inf and non-positive values, warning and keeping the
default rather than printing a garbage legend. The default itself is now
defaultMaxDisplayKn instead of a 25 repeated in two files.
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