@@ -10,14 +10,16 @@ use mg_common::lock;
1010use mg_common:: thread:: ManagedThread ;
1111use ndp:: Ipv6NetworkInterface ;
1212use network_interface:: { NetworkInterface , NetworkInterfaceConfig } ;
13- use slog:: { Logger , info, o, warn} ;
13+ use slog:: { Logger , crit , info, o, warn} ;
1414use std:: {
1515 collections:: HashMap ,
1616 net:: { IpAddr , Ipv6Addr } ,
1717 num:: NonZeroU32 ,
1818 sync:: {
19- Arc , Condvar , Mutex ,
20- atomic:: { AtomicBool , Ordering } ,
19+ Arc , Mutex ,
20+ mpsc:: {
21+ Receiver , RecvTimeoutError , SyncSender , TrySendError , sync_channel,
22+ } ,
2123 } ,
2224 thread,
2325 time:: { Duration , Instant } ,
@@ -49,14 +51,20 @@ pub struct UnnumberedManager {
4951 /// currently have active router-discovery runtime.
5052 configured_interfaces : Arc < Mutex < HashMap < String , u16 > > > ,
5153
54+ /// Wakes the monitor thread when configuration changes. Buffer size 1:
55+ /// if a wake token is already pending the monitor has yet to run and
56+ /// will observe this change too, so a full buffer needs no new token.
57+ ///
58+ /// None means no monitor thread exists by construction (tests), where
59+ /// reconciliation is driven manually. The manager's Drop impl takes the
60+ /// sender to disconnect the channel, ending the monitor loop before
61+ /// ManagedThread's drop joins the thread.
62+ monitor_tx : Option < SyncSender < ( ) > > ,
63+
5264 /// Managed thread for interface monitoring.
5365 /// Stored to ensure automatic cleanup on drop - the ManagedThread's Drop
54- /// impl will signal shutdown and join the thread.
66+ /// impl will join the thread.
5567 monitor_thread : Arc < ManagedThread > ,
56-
57- /// Condvar to wake the monitor thread when work is added
58- monitor_condvar : Arc < Condvar > ,
59- monitor_mutex : Arc < Mutex < ( ) > > ,
6068}
6169
6270impl UnnumberedManager {
@@ -65,37 +73,30 @@ impl UnnumberedManager {
6573 "module" => MOD_UNNUMBERED_MANAGER ,
6674 ) ) ;
6775
68- // Create the managed thread and get its dropped flag before constructing
69- // the manager, so we can pass the flag to the thread function.
7076 let monitor_thread = Arc :: new ( ManagedThread :: new ( ) ) ;
71- let dropped = monitor_thread. dropped_flag ( ) ;
7277 let active_interfaces = Arc :: new ( Mutex :: new ( InterfaceMap :: new ( ) ) ) ;
7378 let configured_interfaces = Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
74- let monitor_condvar = Arc :: new ( Condvar :: new ( ) ) ;
75- let monitor_mutex = Arc :: new ( Mutex :: new ( ( ) ) ) ;
79+ let ( monitor_tx, monitor_rx) = sync_channel ( 1 ) ;
7680
7781 let manager = Arc :: new ( Self {
7882 log : log. clone ( ) ,
7983 active_interfaces : Arc :: clone ( & active_interfaces) ,
8084 configured_interfaces : Arc :: clone ( & configured_interfaces) ,
85+ monitor_tx : Some ( monitor_tx) ,
8186 monitor_thread : Arc :: clone ( & monitor_thread) ,
82- monitor_condvar : Arc :: clone ( & monitor_condvar) ,
83- monitor_mutex : Arc :: clone ( & monitor_mutex) ,
8487 } ) ;
8588
86- // Spawn and register the interface monitor thread.
87- // The ManagedThread will automatically signal shutdown and join the
88- // thread when the manager is dropped .
89+ // Spawn and register the interface monitor thread. Dropping the
90+ // manager disconnects the wake channel, which ends the monitor
91+ // loop; the ManagedThread then joins the thread .
8992 let handle = thread:: Builder :: new ( )
9093 . name ( "unnumbered-interface-monitor" . to_string ( ) )
9194 . spawn ( move || {
9295 Self :: run_interface_monitor (
9396 log,
9497 active_interfaces,
9598 configured_interfaces,
96- monitor_condvar,
97- monitor_mutex,
98- dropped,
99+ monitor_rx,
99100 ) ;
100101 } )
101102 . expect ( "failed to spawn interface monitor thread" ) ;
@@ -110,9 +111,9 @@ impl UnnumberedManager {
110111 log : Logger :: root ( slog:: Discard , o ! ( ) ) ,
111112 active_interfaces : Arc :: new ( Mutex :: new ( InterfaceMap :: new ( ) ) ) ,
112113 configured_interfaces : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
114+ // No monitor thread; tests drive reconciliation manually.
115+ monitor_tx : None ,
113116 monitor_thread : Arc :: new ( ManagedThread :: new ( ) ) ,
114- monitor_condvar : Arc :: new ( Condvar :: new ( ) ) ,
115- monitor_mutex : Arc :: new ( Mutex :: new ( ( ) ) ) ,
116117 } )
117118 }
118119
@@ -125,25 +126,17 @@ impl UnnumberedManager {
125126 /// Uses batched interface checking: a single call to `NetworkInterface::show()`
126127 /// per poll cycle, regardless of how many interfaces are being monitored.
127128 ///
128- /// The `dropped` flag is set by the ManagedThread when the manager is dropped,
129- /// signaling this thread to exit .
129+ /// Exits when `monitor_rx` disconnects, which happens when the manager
130+ /// (holding the sender) is dropped .
130131 fn run_interface_monitor (
131132 log : Logger ,
132133 active_interfaces : Arc < Mutex < InterfaceMap > > ,
133134 configured_interfaces : Arc < Mutex < HashMap < String , u16 > > > ,
134- monitor_condvar : Arc < Condvar > ,
135- monitor_mutex : Arc < Mutex < ( ) > > ,
136- dropped : Arc < AtomicBool > ,
135+ monitor_rx : Receiver < ( ) > ,
137136 ) {
138137 info ! ( log, "interface monitor started" ) ;
139138
140139 loop {
141- // Check for shutdown (set by ManagedThread::drop)
142- if dropped. load ( Ordering :: Relaxed ) {
143- info ! ( log, "interface monitor shutting down" ) ;
144- break ;
145- }
146-
147140 // Check if there's any work to do
148141 let has_configured = !lock ! ( configured_interfaces) . is_empty ( ) ;
149142 let has_active = !lock ! ( active_interfaces) . is_empty ( ) ;
@@ -169,11 +162,17 @@ impl UnnumberedManager {
169162 }
170163 }
171164
172- // Wait until notified about new work/shutdown, or poll again
173- // after the interval.
174- let guard = lock ! ( monitor_mutex) ;
175- let _result =
176- monitor_condvar. wait_timeout ( guard, INTERFACE_POLL_INTERVAL ) ;
165+ // Sleep until woken by a config change, the poll interval
166+ // elapsing, or the manager being dropped (disconnecting the
167+ // channel). A token sent while we were reconciling above is
168+ // buffered and returns immediately, so wakes cannot be lost.
169+ match monitor_rx. recv_timeout ( INTERFACE_POLL_INTERVAL ) {
170+ Ok ( ( ) ) | Err ( RecvTimeoutError :: Timeout ) => ( ) ,
171+ Err ( RecvTimeoutError :: Disconnected ) => {
172+ info ! ( log, "interface monitor shutting down" ) ;
173+ break ;
174+ }
175+ }
177176 }
178177
179178 info ! ( log, "interface monitor exited" ) ;
@@ -385,8 +384,24 @@ impl UnnumberedManager {
385384 "interface" => interface_str,
386385 ) ;
387386
388- // Wake up the monitor thread since there's new or updated work.
389- self . monitor_condvar . notify_one ( ) ;
387+ // Wake the monitor thread since there's new or updated work. A full
388+ // buffer means a wake is already pending and this change will be
389+ // observed by it. A disconnected channel means the monitor thread
390+ // exited while the manager is still alive — the loop only exits on
391+ // sender disconnect, so this indicates the monitor panicked and the
392+ // configuration just recorded will never be reconciled. A None
393+ // sender means no monitor exists by construction (tests) and the
394+ // caller drives reconciliation itself.
395+ if let Some ( tx) = & self . monitor_tx
396+ && let Err ( TrySendError :: Disconnected ( ( ) ) ) = tx. try_send ( ( ) )
397+ {
398+ crit ! (
399+ self . log,
400+ "interface monitor thread is gone; \
401+ unnumbered interface reconciliation is not running";
402+ "interface" => interface_str,
403+ ) ;
404+ }
390405
391406 Ok ( ( ) )
392407 }
@@ -466,11 +481,11 @@ impl UnnumberedManager {
466481
467482impl Drop for UnnumberedManager {
468483 fn drop ( & mut self ) {
469- let _guard = lock ! ( self . monitor_mutex ) ;
470- self . monitor_thread
471- . dropped_flag ( )
472- . store ( true , Ordering :: Relaxed ) ;
473- self . monitor_condvar . notify_one ( ) ;
484+ // Disconnect the wake channel so the monitor loop exits before
485+ // ` monitor_thread`'s field drop joins the thread. Drop bodies run
486+ // before automatic field drops, so this ordering is guaranteed
487+ // regardless of field declaration order.
488+ self . monitor_tx = None ;
474489 }
475490}
476491
0 commit comments