Skip to content

oss_12_onending_frozen#14

Open
ocelotl wants to merge 2 commits into
mainfrom
oss_12_onending_frozen
Open

oss_12_onending_frozen#14
ocelotl wants to merge 2 commits into
mainfrom
oss_12_onending_frozen

Conversation

@ocelotl

@ocelotl ocelotl commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Closes #11

Fixes SpanProcessor.on_ending mutability (Linear OSS-12, finding T1): an intermediate 'ending' state lets on_ending mutate the span (from the ending thread) before it is frozen; ConcurrentMultiSpanProcessor.on_ending now runs synchronously on the ending thread. Attributes/events/links added in on_ending are no longer dropped.

Linear issue: https://linear.app/dash0/issue/OSS-12/t1-spanprocessoronending-runs-after-the-span-is-frozen-mutability-must

The SDK froze the span (set _end_time and made attributes immutable)
before invoking _on_ending, so any attribute/event/link a processor added
inside the on_ending hook was silently dropped, violating the trace SDK
spec which requires the span to remain mutable while OnEnding runs.

Introduce an intermediate ending state: end() now records _end_time and
marks the span as ending (tracking the ending thread's id) but leaves it
mutable, runs _on_ending, then finalizes by freezing attributes and
clearing the ending state. Mutators are permitted during this window only
from the ending thread; after finalization, and from any other thread,
they are rejected as before.

ConcurrentMultiSpanProcessor previously fanned _on_ending out to its
thread pool, which is incompatible with 'mutable only from the ending
thread' and would race on the still-writable span. Its _on_ending now
runs the underlying hooks sequentially on the ending thread, matching the
documented contract that on_ending is called synchronously on the thread
that ends the span.
@ocelotl ocelotl mentioned this pull request Jul 22, 2026
@ocelotl

ocelotl commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

🔒 Internal (dash0) — not for upstream.

@ocelotl

ocelotl commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

📣 Public-facing draft — to be opened upstream in open-telemetry/opentelemetry-python. No internal references; copy verbatim.

Title: Allow SpanProcessor.on_ending to mutate the span before it is frozen

What

Introduce an intermediate "ending" state in Span.end(). The span records its end time and enters the ending state (attributes remain mutable) while on_ending runs; a finally block then finalizes it (freezes attributes) so it always ends read-only, even if a hook raises. A guard permits mutation during the ending window only from the ending thread. ConcurrentMultiSpanProcessor.on_ending now runs its hooks synchronously on the ending thread.

Why

Previously the span was frozen before on_ending ran, so any attribute/event/link a processor added in the hook was silently dropped — a spec violation (OnEnding must observe a still-mutable span). The thread-pool fan-out in ConcurrentMultiSpanProcessor was fundamentally incompatible with "mutable only from the ending thread" and also contradicted the documented "on_ending is synchronous" contract.

Testing

New tests prove an attribute, event, and link added inside on_ending survive into the exported span (for both the standard multi-span processor and the concurrent one), and that mutations attempted after end (outside on_ending) are still rejected. Full SDK suite passes (832).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

oss_12_onending_frozen

1 participant