open-telemetry / open-telemetry/opentelemetry-python
Concurrent multi-processors never flush at interpreter exit
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.6k
- Forks
- 1k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 19
Description
Describe your environment
Labels: bug, sdk, trace, logs, data-loss
Affected packages: opentelemetry-sdk
Found on: main @ 0a5d76b6
Environment: CPython 3.12
What happened?
TracerProvider and LoggerProvider register their shutdown with atexit.register(). concurrent.futures registers its cleanup through threading._register_atexit(), and CPython runs threading._shutdown() before the atexit queue.
So by the time the provider's shutdown runs, the thread pool is already closed. _submit_and_await raises RuntimeError: cannot schedule new futures after shutdown, and the underlying BatchSpanProcessor / BatchLogRecordProcessor is never shut down - its buffered telemetry is simply lost.
Steps to Reproduce
from opentelemetry.sdk.trace import TracerProvider, ConcurrentMultiSpanProcessor
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor, SpanExporter, SpanExportResult,
)
class Exporter(SpanExporter):
def export(self, spans):
for span in spans:
print("EXPORTED", span.name)
return SpanExportResult.SUCCESS
def shutdown(self):
print("EXPORTER_SHUTDOWN")
provider = TracerProvider(active_span_processor=ConcurrentMultiSpanProcessor(2))
provider.add_span_processor(BatchSpanProcessor(Exporter(), schedule_delay_millis=600000))
with provider.get_tracer(__name__).start_as_current_span("span-flushed-at-exit"):
pass
# interpreter exits here; nothing is printed
Expected Result
The buffered span is exported and the exporter's shutdown is called, exactly as happens with the default SynchronousMultiSpanProcessor.
Actual Result
Exception ignored in atexit callback: TracerProvider.shutdown
Traceback (most recent call last):
File ".../sdk/trace/__init__.py", line 293, in shutdown
self._submit_and_await(lambda sp: sp.shutdown)
File ".../sdk/trace/__init__.py", line 272, in _submit_and_await
future = self._executor.submit(func(sp), *args, **kwargs)
RuntimeError: cannot schedule new futures after shutdown
# stdout is empty - the span was never exported
# CPython's finalization order, observed directly
ORDER: threading._shutdown hooks -> atexit hooks
# control: an explicit mid-program shutdown works fine
['EXPORTED 1', 'EXPORTER_SHUTDOWN', 'MANUAL_SHUTDOWN_OK']
Additional context
Every buffered span and log record is lost on every clean exit, for anyone using the concurrent multi-processors. Both signals are affected.
It is quiet in the worst way: the traceback is printed as an ignored atexit error, so the process still exits 0 and CI stays green. The telemetry most likely to be lost is the telemetry from the end of the run - often exactly the part you care about when diagnosing a shutdown.
force_flush has the same failure mode when called late.
Would you like to implement a fix?
Yes
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the concurrent processor shutdown and _submit_and_await paths in the SDK trace and logging provider implementations, then run the supplied CPython 3.12 reproduction. Check how threading._shutdown precedes atexit callbacks and compare concurrent processors with SynchronousMultiSpanProcessor. Done means buffered spans and log records flush and exporter shutdown runs at interpreter exit, while late force_flush remains safe.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- observability
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100