feat: add opt-in periodic interface-change monitor#1798
Conversation
cc6dd24 to
f624cb2
Compare
9465fb1 to
db40f9a
Compare
f624cb2 to
f7b6624
Compare
db40f9a to
4699b53
Compare
PR Review — feat: add opt-in periodic interface-change monitorSolid, well-scoped opt-in feature. No blocking issues; one behavior question to confirm before merge. Strengths:
Needs attention:
🟡 Important
1. Snapshot watches every system adapter, not the configured interface subset
|
bluetoothbot
left a comment
There was a problem hiding this comment.
Blocking issues found.
- Snapshot watches every system adapter, not the configured interface subset
f7b6624 to
14d468f
Compare
4699b53 to
24c504e
Compare
14d468f to
9c65842
Compare
24c504e to
5339f81
Compare
9c65842 to
4029b73
Compare
5339f81 to
622abc9
Compare
4029b73 to
b2f6c89
Compare
622abc9 to
f767f06
Compare
b2f6c89 to
320f800
Compare
f767f06 to
00d724e
Compare
320f800 to
f97422b
Compare
00d724e to
e5c7779
Compare
f97422b to
96e57f9
Compare
e5c7779 to
e606f55
Compare
96e57f9 to
74251cc
Compare
Instead of raising, moving a Default single-family instance to an explicit interface set now demotes its dual-use listen/responder socket to a pure listener and rebuilds it clean, then adds per-interface responders for the whole desired set. Rebuilding releases the dual-use socket's existing group memberships so the new joins do not collide (EADDRINUSE) when the desired set overlaps the interface it served, and demoting it first prevents double announcements.
e406194 to
6a4feaa
Compare
6a4feaa to
a5155c9
Compare
… of recomputing Add a _without_transport helper for the repeated 'filter wrappers whose transport is not X' list comprehension, used by _async_remove_transport and the dual-use demote. In the demote, pop the known listen key from current instead of rebuilding the whole dict.
…sten socket The rebuild re-join discarded add_multicast_member's result, so a staying interface that could not re-join went silently send-only. Surface it with log_warning_once, mirroring _async_add_interface.
a5155c9 to
e3e1ed0
Compare
Add regression guards the diff structurally couldn't fail on: re-announce over multiple registrations with one failing, an interface IP change handled as remove+add in one rescan, and the IPv6 leave packing the join interface index. Document that re-announcement is best-effort.
e3e1ed0 to
b1bfd3f
Compare
A bare except OSError in _listen_socket_supports masked a getsockopt failure on any platform into 'dual-stack supported', which could suppress a needed listen-socket rebuild and leave an added address family unreceivable. Narrow the fallback to win32 (where the read legitimately fails) and re-raise elsewhere, mirroring make_wrapped_transport.
b1bfd3f to
30201c8
Compare
The re-announce warning now identifies which registration failed (zipped back to its ServiceInfo) so a partial failure is actionable. The sync update_interfaces wrapper awaits the announce inline (to keep per-service failure logging), so widen its timeout to cover the full announce window plus reconcile overhead rather than leaving a thin margin under EventLoopBlocked.
30201c8 to
4890fd8
Compare
make_wrapped_transport runs on the startup/connection path; re-raising a failed IPV6_MULTICAST_IF read there could abort instance startup to protect multicast_index, which only selects the interface for a benign group leave that drop_multicast_member already tolerates. Fall back to the default index with a debug log instead. Apply the same graceful fallback to _listen_socket_supports (assume dual-stack) so a getsockopt failure can't abort a rescan, and the two paths agree. Soften the _core config-commit comment, which overstated the invariant on a partial reconcile failure.
4890fd8 to
05467e4
Compare
…rt bring-up A failed IPV6_V6ONLY read assumes dual-stack (returning False could loop rebuilds when the rebuilt socket's read also fails), but if the socket really were v6-only that skips a needed rebuild and strands an added IPv4 family, so log it at warning rather than debug. Document that interface bring-up is best-effort, and note the benign group leave on the rebuilt listen socket.
05467e4 to
221daf7
Compare
… bind failure normalize_interface_choice raises RuntimeError for an All/Default instance that transiently resolves to zero addresses during adapter churn; catch it as a logged no-op so a momentary down state doesn't crash a caller's adapter-change handler. Make a per-interface endpoint-creation failure roll back and skip (log_warning_once) rather than abort the whole reconcile, so other interfaces still come up and get re-announced, matching the documented best-effort contract. Log a non-Windows IPV6_MULTICAST_IF read failure at warning (Windows WSAEINVAL stays debug) since a wrong-index leave leaks the membership.
221daf7 to
963e1fe
Compare
Best-effort bring-up should downgrade an expected socket-level failure, not a real bug. Roll back on any wrap failure, but re-raise anything that is not an OSError so a TypeError/AttributeError surfaces instead of being deduped into a one-time 'interface not added' warning.
963e1fe to
1cad66f
Compare
The Default dual-use conversion demoted the socket from senders before the fallible rebuild, so a rebuild failure left it pulled from senders but not replaced (instance stops responding) and propagated into the caller's handler. The explicit demote was redundant; the rebuild removes the old transport from senders itself, but only after the new socket succeeds. Drop the pre-demote (keep only the diff-view pop) so a failed rebuild leaves senders intact, and catch the rebuild failure as a logged no-op to honor the best-effort contract.
1cad66f to
9e14e0f
Compare
gather(return_exceptions=True) also captures BaseExceptions like CancelledError, which the isinstance(result, Exception) check skipped, so a cancelled re-announce vanished silently; re-raise a captured BaseException instead of swallowing it. Document on the public update_interfaces methods that apple_p2p on a non-Apple platform raises RuntimeError.
9e14e0f to
ca0e061
Compare

Summary
Adds an opt-in poller that watches for adapter changes and calls
async_update_interfaceswhen the set of interface addresses changes, so a consumer with no native interface-change signal can self-heal without restartingZeroconf. It is off by default; interface change detection stays platform specific and left to the consumer, this is just a convenience for callers that want it. Stacked on #1797.Details
InterfaceMonitorin_utils/interface_monitor.pypollsifaddr.get_adapterseveryintervalseconds (default 5), diffs a hashable snapshot of adapter index plus address, and rescans only on change.async_start_interface_monitor/async_stop_interface_monitoronZeroconfandAsyncZeroconf; the monitor task is cancelled and awaited on close.async_update_interfacesdirectly and skip the monitor.Test plan
tests/test_interface_monitor.pycovers the snapshot helper, rescan on change, no rescan when unchanged, surviving a rescan error, start and stop idempotency, stop without start, and cancellation on close.interface_monitor.pyat 100 percent; full monitor, rescan, and engine suites green;pre-commit runclean.