open-telemetry / open-telemetry/opentelemetry-python

Concurrent multi-processors never flush at interpreter exit

Open
#5,568 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.