@@ -188,10 +188,27 @@ def __init__(
188
188
# Track when we next *must* perform a scheduled export
189
189
self ._next_export_time = time .time () + self ._schedule_delay
190
190
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 ()
193
207
194
208
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
+
195
212
try :
196
213
self ._queue .put_nowait (trace )
197
214
except queue .Full :
@@ -206,6 +223,9 @@ def on_span_start(self, span: Span[Any]) -> None:
206
223
pass
207
224
208
225
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
+
209
229
try :
210
230
self ._queue .put_nowait (span )
211
231
except queue .Full :
@@ -216,7 +236,13 @@ def shutdown(self, timeout: float | None = None):
216
236
Called when the application stops. We signal our thread to stop, then join it.
217
237
"""
218
238
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 )
220
246
221
247
def force_flush (self ):
222
248
"""
0 commit comments