Stop forked children re-exporting the parent's OTel metrics - #71855
Open
dstandish wants to merge 3 commits into
Open
Stop forked children re-exporting the parent's OTel metrics#71855dstandish wants to merge 3 commits into
dstandish wants to merge 3 commits into
Conversation
A forked child inherited the atexit flush hook, the MeterProvider, and — because the SDK restarts its reader threads in the child — a live pipeline over the parent's accumulated state, so it re-exported metrics it never recorded. Building the pipeline once per process rather than once per call closes the same duplication on the re-initialization path.
The idle child shows the inherited pipeline goes quiet, but every worker that emits metrics reaches a different state: it stops the pipeline it inherited and then exports through one of its own, on the same interval, for the rest of its life. Nothing pinned that the second half still works -- dropping the reset of the module's provider reference leaves the child recording into the pipeline it just silenced, and its own metrics disappear with no test noticing.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The OTel metrics pipeline can end up with two writers on one cumulative series — one climbing,
one frozen — in two independent ways. This fixes both.
A second
get_otel_logger()call in the same process leaves the first pipeline running. EveryMeterProviderowns aPeriodicExportingMetricReaderwhose constructor starts a daemon exportthread, and
shutdown_on_exit=Falsemeans nothing reaps one that gets replaced. Its instrumentsstop being recorded to, so it republishes frozen totals under a different
start_time_unix_nanofor the life of the process. The scheduler reaches this on every start:
BaseExecutor.__init__andSchedulerJobRunner._executeeach callstats.initialize(), with astats.incr("schedulerjob_start")in between that materialises the first pipeline. Fixed bybuilding the pipeline once per process, so there is no second one to reap and no cumulative reset
partway through startup.
A forked child re-exports the pipeline it inherited.
fork()copies the parent's provider, andthe SDK registers
register_at_fork(after_in_child=...)for everyPeriodicExportingMetricReader,so the child restarts the export thread behind it. Nothing in the child records to that pipeline,
so it republishes the totals the parent held at the instant of the fork, once per export interval,
for as long as the child lives. Airflow forks constantly and the long-lived children make it
permanent: LocalExecutor pool workers, the OpenLineage dag-state-change
ProcessPoolExecutor, andthe scheduler's log and health-check servers all fork from a scheduler whose pipeline is already
live. Stopping the reader is not enough on its own — the revived ticker does one final collect on
its way out — so the collect callback is dropped too.
A
MeterProviderthe SDK built for the deployment, fromOTEL_CONFIG_FILEor anopentelemetry-instrumentagent, reaches the child the same way, carrying the atexit shutdown thatshutdown_on_exit=Trueregistered for it. Its readers are left running, since on that path theyare the only pipeline the child has, but the inherited copy of that hook is dropped so the child
cannot dump the parent's state on the way out. Only a provider's own
_metric_readersare everstopped, never the class-level
_all_metric_readersevery provider shares, so an agent's pipelinekeeps working in forked children.
related: #71800 — that PR fixes the first defect above by shutting down the replaced provider
instead of never building a second one. The two are alternatives for that half; shutting down and
replacing force-flushes the old pipeline and restarts the series under a new
start_time_unix_nano, which building once avoids.supersedes: #71804, which carried an earlier version of this work.
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5) following the guidelines