Skip to content

Commit 7a432ec

Browse files
committed
test(otel): verify published async thread context
1 parent bbd384e commit 7a432ec

1 file changed

Lines changed: 36 additions & 12 deletions

File tree

tests/opentelemetry/test_context.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import ctypes
23
import sys
34
import threading
45
import time
@@ -182,32 +183,55 @@ async def coro(i):
182183
reason="requires the CPython 3.14 context watcher on Linux",
183184
)
184185
@pytest.mark.asyncio
185-
async def test_otel_thread_context_follows_async_context_switches(oteltracer, monkeypatch):
186-
from ddtrace.internal.opentelemetry import thread_context
187-
188-
published_spans = []
189-
monkeypatch.setattr(
190-
thread_context,
191-
"update_otel_thread_context",
192-
lambda span, local_root, trace_flags: published_spans.append(span),
193-
)
186+
async def test_otel_thread_context_follows_async_context_switches(oteltracer):
187+
"""The published thread context follows each OTel span as asyncio switches tasks."""
188+
from ddtrace.internal.native import _native
189+
190+
class _ThreadContextRecord(ctypes.Structure):
191+
_fields_ = [
192+
("trace_id", ctypes.c_ubyte * 16),
193+
("span_id", ctypes.c_ubyte * 8),
194+
("valid", ctypes.c_ubyte),
195+
]
196+
197+
native_library = ctypes.CDLL(_native.__file__)
198+
199+
def published_context():
200+
slot = ctypes.c_void_p.in_dll(native_library, "otel_thread_ctx_v1")
201+
if slot.value is None:
202+
return None
203+
204+
record = _ThreadContextRecord.from_address(slot.value)
205+
if not record.valid:
206+
return None
207+
return int.from_bytes(record.trace_id, byteorder="big"), int.from_bytes(record.span_id, byteorder="big")
208+
209+
def span_context(span):
210+
context = span.get_span_context()
211+
return context.trace_id, context.span_id
212+
194213
first_started = asyncio.Event()
195214
second_started = asyncio.Event()
196215

197216
async def first():
198217
with oteltracer.start_as_current_span("first") as first_span:
218+
expected_context = span_context(first_span)
219+
assert published_context() == expected_context
199220
first_started.set()
200221
await second_started.wait()
201-
assert published_spans and published_spans[-1] is first_span._ddspan
222+
assert published_context() == expected_context
202223

203224
async def second():
204225
await first_started.wait()
205-
with oteltracer.start_as_current_span("second"):
206-
published_spans.clear()
226+
with oteltracer.start_as_current_span("second") as second_span:
227+
expected_context = span_context(second_span)
228+
assert published_context() == expected_context
207229
second_started.set()
208230
await asyncio.sleep(0)
231+
assert published_context() == expected_context
209232

210233
await asyncio.gather(first(), second())
234+
assert published_context() is None
211235

212236

213237
def test_otel_get_current_span(oteltracer):

0 commit comments

Comments
 (0)