Skip to content

opentelemetry-instrumentation-pyramid double BeforeTraversal subscriber registration after config.commit() #4663

@adamgregory

Description

@adamgregory

Describe your environment

OS: Ubuntu
Python version: Python 3.11.8
Package version: v0.63b1

What happened?

PyramidInstrumentor registers duplicate _before_traversal event subscribers when any Pyramid library
calls config.commit() during its includeme. This causes OpenTelemetry context corruption — child spans
(DB, cache, HTTP) become orphaned as separate traces instead of parenting to the request span.

_traced_init wraps Configurator.init and unconditionally calls
instance.include("opentelemetry.instrumentation.pyramid.callbacks"). Pyramid's processSpec deduplication
(via action_state._seen_files) is supposed to prevent double-registration, but config.commit() replaces
action_state with a fresh instance, clearing _seen_files.

After any commit(), the next config.include() creates a child Configurator → triggers _traced_init →
processSpec passes on the new empty _seen_files → _before_traversal is registered again.

Steps to Reproduce

"""
Demonstrates the opentelemetry-instrumentation-pyramid double subscriber bug.

When any library calls config.commit() during configuration, the OTEL pyramid
instrumentation registers its _before_traversal subscriber a second time. This
causes child spans (DB, cache, HTTP calls) to be orphaned as separate traces.

Requirements:
    pip install pyramid opentelemetry-instrumentation-pyramid

Run:
    python otel_pyramid_double_subscriber.py

Expected output:
    Without commit: 1 _before_traversal subscriber(s) - OK
    With commit:    2 _before_traversal subscriber(s) - BUG: double registration!
"""

from opentelemetry.instrumentation.pyramid import PyramidInstrumentor

PyramidInstrumentor().instrument()

from pyramid.config import Configurator


def library_that_commits(config):
    """Simulates any library that calls config.commit() in its includeme.
    This is what tomb_routes does on every add_simple_route call."""
    config.add_route("example", "/example")
    config.commit()


def another_library(config):
    """Any subsequent config.include() after the commit triggers the bug."""
    pass


registrations = []
from pyramid.config.adapters import AdaptersConfiguratorMixin

_orig_add_subscriber = AdaptersConfiguratorMixin.add_subscriber


def _counting_add_subscriber(self, subscriber, iface=None, **kw):
    if "before_traversal" in getattr(subscriber, "__name__", ""):
        registrations.append(subscriber)
    return _orig_add_subscriber(self, subscriber, iface=iface, **kw)


AdaptersConfiguratorMixin.add_subscriber = _counting_add_subscriber

# --- Without commit: works correctly ---
registrations.clear()
config1 = Configurator(settings={})
config1.include(another_library)

count1 = len(registrations)
print(f"Without commit: {count1} _before_traversal subscriber(s) - {'OK' if count1 == 1 else 'BUG'}")

# --- With commit: triggers the bug ---
registrations.clear()
config2 = Configurator(settings={})
config2.include(library_that_commits)
config2.include(another_library)

count2 = len(registrations)
print(f"With commit:    {count2} _before_traversal subscriber(s) - {'OK' if count2 == 1 else 'BUG: double registration!'}")

PyramidInstrumentor().uninstrument()

Expected Result

Only one BeforeTraversal subscriber should be registered.

Actual Result

When any library calls config.commit() the action state is cleared causing the traced init to subscribe the BeforeTraversal subscriber a second time on the next config.include()

Additional context

No response

Would you like to implement a fix?

No

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions