Skip to content

Commit a2297f4

Browse files
committed
Fix threading instrumentation contexxt types
Add None check to context handling in thread instrumentation Add testcases for None context
1 parent f98f568 commit a2297f4

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

instrumentation/opentelemetry-instrumentation-threading/src/opentelemetry/instrumentation/threading/__init__.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ def __wrap_threading_run(
150150
token = context.attach(instance._otel_context)
151151
return call_wrapped(*args, **kwargs)
152152
finally:
153-
context.detach(token)
153+
if token is not None:
154+
context.detach(token)
154155

155156
@staticmethod
156157
def __wrap_thread_pool_submit(
@@ -169,7 +170,8 @@ def wrapped_func(*func_args: Any, **func_kwargs: Any) -> R:
169170
token = context.attach(otel_context)
170171
return original_func(*func_args, **func_kwargs)
171172
finally:
172-
context.detach(token)
173+
if token is not None:
174+
context.detach(token)
173175

174176
# replace the original function with the wrapped function
175177
new_args: tuple[Callable[..., Any], ...] = (wrapped_func,) + args[1:]

instrumentation/opentelemetry-instrumentation-threading/tests/test_threading.py

+46-1
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
import threading
1616
from concurrent.futures import ThreadPoolExecutor
1717
from typing import List
18+
from unittest.mock import MagicMock, patch
1819

19-
from opentelemetry import trace
20+
from opentelemetry import context, trace
2021
from opentelemetry.instrumentation.threading import ThreadingInstrumentor
2122
from opentelemetry.test.test_base import TestBase
2223

2324

25+
# pylint: disable=too-many-public-methods
2426
class TestThreading(TestBase):
2527
def setUp(self):
2628
super().setUp()
@@ -224,3 +226,46 @@ def test_uninstrumented(self):
224226
self.assertEqual(len(spans), 1)
225227

226228
ThreadingInstrumentor().instrument()
229+
230+
def test_threading_with_none_context_token(self):
231+
with self.get_root_span(), patch(
232+
"opentelemetry.context.attach", return_value=None
233+
), patch("opentelemetry.context.detach") as mock_detach:
234+
thread = threading.Thread(target=self.fake_func)
235+
thread.start()
236+
thread.join()
237+
mock_detach.assert_not_called()
238+
239+
def test_threading_with_valid_context_token(self):
240+
mock_token = MagicMock(spec=context.Token)
241+
with patch(
242+
"opentelemetry.context.attach", return_value=mock_token
243+
), self.get_root_span(), patch(
244+
"opentelemetry.context.detach", autospec=True
245+
) as mock_detach:
246+
thread = threading.Thread(target=self.fake_func)
247+
thread.start()
248+
thread.join()
249+
mock_detach.assert_called_once()
250+
251+
def test_thread_pool_with_none_context_token(self):
252+
with self.get_root_span(), patch(
253+
"opentelemetry.context.attach", return_value=None
254+
), patch(
255+
"opentelemetry.context.detach"
256+
) as mock_detach, ThreadPoolExecutor(max_workers=1) as executor:
257+
future = executor.submit(self.get_current_span_context_for_test)
258+
future.result()
259+
260+
mock_detach.assert_not_called()
261+
262+
def test_threadpool_with_valid_context_token(self):
263+
mock_token = MagicMock(spec=context.Token)
264+
with self.get_root_span(), patch(
265+
"opentelemetry.context.attach", return_value=mock_token
266+
), patch(
267+
"opentelemetry.context.detach", autospec=True
268+
) as mock_detach, ThreadPoolExecutor(max_workers=1) as executor:
269+
future = executor.submit(self.get_current_span_context_for_test)
270+
future.result()
271+
mock_detach.assert_called_once()

0 commit comments

Comments
 (0)