@@ -106,40 +106,38 @@ def pop(self, cam_id: str, default=None) -> VideoRecorder | None:
106106 return self ._recorders .pop (cam_id , default )
107107
108108 def _start_dispatcher (self ) -> None :
109- with self ._lock :
110- if self ._dispatch_thread is not None and self ._dispatch_thread .is_alive ():
111- return
109+ if self ._dispatch_thread is not None and self ._dispatch_thread .is_alive ():
110+ return
112111
113- self ._dispatch_stop .clear ()
114- self ._frame_queue = queue .Queue (maxsize = 4096 )
115- self ._dispatch_thread = threading .Thread (
116- target = self ._dispatch_loop ,
117- name = "RecordingManagerDispatcher" ,
118- daemon = True ,
119- )
120- self ._dispatch_thread .start ()
112+ self ._dispatch_stop .clear ()
113+ self ._frame_queue = queue .Queue (maxsize = 4096 )
114+ self ._dispatch_thread = threading .Thread (
115+ target = self ._dispatch_loop ,
116+ name = "RecordingManagerDispatcher" ,
117+ daemon = True ,
118+ )
119+ self ._dispatch_thread .start ()
121120
122121 def _stop_dispatcher (self , timeout : float = 2.0 ) -> None :
123- self ._dispatch_stop .set ()
124-
125122 with self ._lock :
126123 q = self ._frame_queue
127124 t = self ._dispatch_thread
128125
129126 if q is not None :
130127 try :
131- q .put_nowait (_FRAME_SENTINEL )
128+ q .put (_FRAME_SENTINEL , block = True , timeout = timeout )
132129 except queue .Full :
133- pass
130+ log . warning ( "Recording frame queue full while stopping dispatcher; dispatcher may not stop promptly." )
134131
135132 if t is not None :
136133 t .join (timeout = timeout )
137134 if t .is_alive ():
138135 log .warning ("Recording frame dispatcher did not stop within %.1fs" , timeout )
139136
140137 with self ._lock :
141- self ._dispatch_thread = None
142- self ._frame_queue = None
138+ if self ._dispatch_thread is t :
139+ self ._dispatch_thread = None
140+ self ._frame_queue = None
143141 self ._dispatch_stop .clear ()
144142
145143 def _dispatch_loop (self ) -> None :
@@ -149,11 +147,8 @@ def _dispatch_loop(self) -> None:
149147 if q is None :
150148 return
151149
152- while not self ._dispatch_stop .is_set ():
153- try :
154- item = q .get (timeout = 0.1 )
155- except queue .Empty :
156- continue
150+ while True :
151+ item = q .get ()
157152
158153 try :
159154 if item is _FRAME_SENTINEL :
@@ -270,7 +265,6 @@ def start_all(
270265 self ._run_dir = None
271266 return None
272267
273- self ._start_dispatcher ()
274268 return run_dir
275269
276270 def stop_all (self ) -> None :
@@ -326,13 +320,23 @@ def _write_frame_now(
326320 log .exception ("Failed to stop recorder for %s after write error." , cam_id )
327321
328322 def write_frame (
329- self , cam_id : str , frame : np .ndarray , timestamp : float | None = None , timestamp_metadata : object | None = None
323+ self ,
324+ cam_id : str ,
325+ frame : np .ndarray ,
326+ timestamp : float | None = None ,
327+ timestamp_metadata : object | None = None ,
330328 ) -> None :
331329 with self ._lock :
332- q = self ._frame_queue
333330 active = cam_id in self ._recorders
331+ if not active :
332+ return
333+
334+ if self ._frame_queue is None or self ._dispatch_thread is None or not self ._dispatch_thread .is_alive ():
335+ self ._start_dispatcher ()
334336
335- if not active or q is None :
337+ q = self ._frame_queue
338+
339+ if q is None :
336340 return
337341
338342 try :
@@ -345,6 +349,27 @@ def write_frame(
345349 getattr (frame , "dtype" , None ),
346350 )
347351
352+ def flush (self , timeout : float = 2.0 ) -> bool :
353+ """Wait until all currently queued recording frames have been dispatched.
354+
355+ Returns True if the queue drained before timeout, False otherwise.
356+ """
357+ with self ._lock :
358+ q = self ._frame_queue
359+
360+ if q is None :
361+ return True
362+
363+ done = threading .Event ()
364+
365+ def waiter () -> None :
366+ q .join ()
367+ done .set ()
368+
369+ t = threading .Thread (target = waiter , name = "RecordingManagerFlush" , daemon = True )
370+ t .start ()
371+ return done .wait (timeout )
372+
348373 def get_stats_summary (self ) -> str :
349374 totals = {
350375 "enqueued" : 0 ,
0 commit comments