@@ -237,20 +237,27 @@ def _drain_tcp(self) -> None:
237237 Each message is expected to be a telemetry row or batch compatible with
238238 ``SQLiteWriterSimple.ingest()``. A legacy subset is also mirrored into
239239 ``RemoteDBStore`` for renderers that still depend on the in-memory path.
240+
241+ Design note
242+ -----------
243+ Errors in each sink are isolated: a failure in one does not prevent the
244+ other from receiving the message. Direct try/except is used instead of
245+ ``_safe(lambda ...)`` to avoid allocating two closure objects per message
246+ in the hot path.
240247 """
241248 for msg in self ._tcp_server .poll ():
242249 remote_msg = self ._filter_remote_store_message (msg )
243250 if remote_msg is not None :
244- _safe (
245- self ._logger ,
246- "RemoteDBStore.ingest failed" ,
247- lambda m = remote_msg : self ._store . ingest ( m ),
248- )
249- _safe (
250- self . _logger ,
251- "SQLiteWriter. ingest failed" ,
252- lambda m = msg : self . _sqlite_writer . ingest ( m ),
253- )
251+ try :
252+ self ._store . ingest ( remote_msg )
253+ except Exception :
254+ self ._logger . exception (
255+ "[TraceML] RemoteDBStore.ingest failed"
256+ )
257+ try :
258+ self . _sqlite_writer . ingest ( msg )
259+ except Exception :
260+ self . _logger . exception ( "[TraceML] SQLiteWriter.ingest failed" )
254261
255262 def _message_sampler_name (self , msg : Any ) -> Optional [str ]:
256263 """
@@ -300,26 +307,38 @@ def _filter_remote_store_message(self, msg: Any) -> Any:
300307
301308 def _loop (self ) -> None :
302309 """
303- Run the periodic drain and display tick loop.
310+ Run the event-driven drain and display tick loop.
311+
312+ The loop blocks on ``TCPServer.wait_for_data()`` rather than sleeping
313+ for a fixed interval. This means the aggregator drains new messages
314+ as soon as they arrive over TCP — reducing end-to-end ingestion latency
315+ from up to ``render_interval_sec`` down to near-zero.
304316
305- The aggregator does not render directly. Rendering is delegated to the
306- configured display driver .
317+ The display driver tick is still rate-limited to at most once per
318+ ``render_interval_sec`` so the UI cadence is unchanged .
307319 """
308320 interval_sec = float (self ._settings .render_interval_sec )
321+ last_tick_ts = 0.0
309322
310323 while not self ._stop_event .is_set ():
324+ # Wake immediately when data arrives, or after interval_sec at most.
325+ self ._tcp_server .wait_for_data (timeout = interval_sec )
311326 self ._drain_tcp ()
312- _safe (
313- self ._logger ,
314- "Final summary service poll failed" ,
315- self ._summary_service .poll ,
316- )
317- _safe (
318- self ._logger ,
319- "Display driver tick failed" ,
320- self ._display_driver .tick ,
321- )
322- self ._stop_event .wait (interval_sec )
327+
328+ # Rate-limit the UI tick to interval_sec cadence.
329+ now = time .monotonic ()
330+ if now - last_tick_ts >= interval_sec :
331+ _safe (
332+ self ._logger ,
333+ "Final summary service poll failed" ,
334+ self ._summary_service .poll ,
335+ )
336+ _safe (
337+ self ._logger ,
338+ "Display driver tick failed" ,
339+ self ._display_driver .tick ,
340+ )
341+ last_tick_ts = now
323342
324343 # Final drain and final display tick on shutdown.
325344 self ._drain_tcp ()
0 commit comments