Skip to content

Commit 995af4d

Browse files
authored
Only start tracing worker thread on first span/trace (#804)
Closes #796. Shouldn't start a busy waiting thread if there aren't any traces. Test plan ``` import threading assert threading.active_count() == 1 import agents assert threading.active_count() == 1 ```
1 parent d4c7a23 commit 995af4d

File tree

1 file changed

+29
-3
lines changed

1 file changed

+29
-3
lines changed

src/agents/tracing/processors.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,27 @@ def __init__(
188188
# Track when we next *must* perform a scheduled export
189189
self._next_export_time = time.time() + self._schedule_delay
190190

191-
self._worker_thread = threading.Thread(target=self._run, daemon=True)
192-
self._worker_thread.start()
191+
# We lazily start the background worker thread the first time a span/trace is queued.
192+
self._worker_thread: threading.Thread | None = None
193+
self._thread_start_lock = threading.Lock()
194+
195+
def _ensure_thread_started(self) -> None:
196+
# Fast path without holding the lock
197+
if self._worker_thread and self._worker_thread.is_alive():
198+
return
199+
200+
# Double-checked locking to avoid starting multiple threads
201+
with self._thread_start_lock:
202+
if self._worker_thread and self._worker_thread.is_alive():
203+
return
204+
205+
self._worker_thread = threading.Thread(target=self._run, daemon=True)
206+
self._worker_thread.start()
193207

194208
def on_trace_start(self, trace: Trace) -> None:
209+
# Ensure the background worker is running before we enqueue anything.
210+
self._ensure_thread_started()
211+
195212
try:
196213
self._queue.put_nowait(trace)
197214
except queue.Full:
@@ -206,6 +223,9 @@ def on_span_start(self, span: Span[Any]) -> None:
206223
pass
207224

208225
def on_span_end(self, span: Span[Any]) -> None:
226+
# Ensure the background worker is running before we enqueue anything.
227+
self._ensure_thread_started()
228+
209229
try:
210230
self._queue.put_nowait(span)
211231
except queue.Full:
@@ -216,7 +236,13 @@ def shutdown(self, timeout: float | None = None):
216236
Called when the application stops. We signal our thread to stop, then join it.
217237
"""
218238
self._shutdown_event.set()
219-
self._worker_thread.join(timeout=timeout)
239+
240+
# Only join if we ever started the background thread; otherwise flush synchronously.
241+
if self._worker_thread and self._worker_thread.is_alive():
242+
self._worker_thread.join(timeout=timeout)
243+
else:
244+
# No background thread: process any remaining items synchronously.
245+
self._export_batches(force=True)
220246

221247
def force_flush(self):
222248
"""

0 commit comments

Comments
 (0)