Skip to content

oss_12_onending_frozen #11

Open
Open

Description

@ocelotl

Tracks SpanProcessor.on_ending mutability (Linear OSS-12, finding T1).

Problem: SpanProcessor.on_ending runs after the span is already frozen, so any attribute/event/link a processor adds in the hook is silently dropped (spec MUST violated).

Fix: Introduce an intermediate 'ending' state so on_ending runs while the span is still mutable by the ending thread, then finalize. Rework ConcurrentMultiSpanProcessor.on_ending (thread-pool fan-out is incompatible with single-thread mutability).

Branch / worktree: oss_12_onending_frozen

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

Activity

  1. ocelotl commented on Jul 22, 2026

    @ocelotl
    OwnerAuthor

    🔒 Internal (dash0) — not for upstream.

    Tracking for the SpanProcessor.on_ending mutability fix.

  2. ocelotl commented on Jul 22, 2026

    @ocelotl
    OwnerAuthor

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

    Title: SpanProcessor.on_ending runs after the span is frozen — mutations are silently dropped (spec MUST)

    Summary

    SpanProcessor.on_ending exists, but the SDK freezes the span before invoking it, so any attribute/event/link a processor adds inside on_ending is silently discarded. The specification requires the span to still be mutable while OnEnding runs.

    Evidence

    In opentelemetry-sdk/.../trace/__init__.py, Span.end() sets self._end_time and marks the attributes immutable (self._attributes._immutable = True) before calling self._span_processor._on_ending(self). All mutators are gated by the @_check_span_ended decorator (and inline _end_time is not None checks in set_attribute/set_attributes), so any write attempted during on_ending is rejected and dropped.

    Spec

    specification/trace/sdk.md — the OnEnding hook is called synchronously on the thread that ends the span, and the span MUST still be mutable during the callback so a processor can enrich it (e.g. add attributes) before it becomes read-only.

    Reference implementation

    Java models the span lifecycle with a three-state EndState in SdkSpan.java: the span enters an intermediate "ending" state during which the ending thread may still mutate it, then it is finalized to read-only.

    Proposed fix

    Introduce an intermediate "ending" state in Span.end(): record end_time, mark the span as ending and remember the ending thread, run on_ending while the span is still writable by that thread only, then finalize (freeze attributes) in a finally block so the span always ends up read-only even if a hook raises. Mutation guards must permit writes during the ending window only from the ending thread and reject them afterward or from other threads. Note: ConcurrentMultiSpanProcessor currently fans on_ending out to a thread pool, which is incompatible with "mutable only from the ending thread"; run its on_ending hooks synchronously on the ending thread (this also matches the documented contract that on_ending is synchronous).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions