Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit a099a02

Browse files
author
DRV2SI
committed
- make context propagation robust to unavailability of root tracer
1 parent b85e476 commit a099a02

File tree

3 files changed

+138
-2
lines changed

3 files changed

+138
-2
lines changed

contrib/opencensus-ext-threading/opencensus/ext/threading/trace.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
1516
import logging
1617
import threading
1718
from concurrent import futures
@@ -88,8 +89,14 @@ def wrap_apply_async(apply_async_func):
8889
that will be called and wrap it then add the opencensus context."""
8990

9091
def call(self, func, args=(), kwds={}, **kwargs):
91-
wrapped_func = wrap_task_func(func)
9292
_tracer = execution_context.get_opencensus_tracer()
93+
94+
from opencensus.trace.tracers.noop_tracer import NoopTracer
95+
96+
if isinstance(_tracer, NoopTracer):
97+
return apply_async_func(self, func, args=args, kwds={}, **kwargs)
98+
99+
wrapped_func = wrap_task_func(func)
93100
propagator = binary_format.BinaryFormatPropagator()
94101

95102
wrapped_kwargs = {}
@@ -113,14 +120,21 @@ def wrap_submit(submit_func):
113120
that will be called and wrap it then add the opencensus context."""
114121

115122
def call(self, func, *args, **kwargs):
116-
wrapped_func = wrap_task_func(func)
117123
_tracer = execution_context.get_opencensus_tracer()
124+
125+
from opencensus.trace.tracers.noop_tracer import NoopTracer
126+
127+
if isinstance(_tracer, NoopTracer):
128+
return submit_func(self, func, *args, **kwargs)
129+
130+
wrapped_func = wrap_task_func(func)
118131
propagator = binary_format.BinaryFormatPropagator()
119132

120133
wrapped_kwargs = {}
121134
wrapped_kwargs["span_context_binary"] = propagator.to_header(
122135
_tracer.span_context
123136
)
137+
124138
wrapped_kwargs["kwds"] = kwargs
125139
wrapped_kwargs["sampler"] = _tracer.sampler
126140
wrapped_kwargs["exporter"] = _tracer.exporter
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock
3+
4+
from opencensus.trace.tracers.noop_tracer import NoopTracer
5+
from opencensus.ext.threading.trace import wrap_submit, wrap_apply_async
6+
7+
8+
class TestNoopTracer(unittest.TestCase):
9+
"""
10+
In case no OpenCensus context is present (i.e. we have a NoopTracer), do _not_ pass down tracer in apply_async
11+
and submit; instead invoke function directly.
12+
"""
13+
14+
@patch("opencensus.ext.threading.trace.wrap_task_func")
15+
@patch("opencensus.trace.execution_context.get_opencensus_tracer")
16+
def test_noop_tracer_apply_async(
17+
self, get_opencensus_tracer_mock: MagicMock, wrap_task_func_mock: MagicMock
18+
):
19+
mock_tracer = NoopTracer()
20+
get_opencensus_tracer_mock.return_value = mock_tracer
21+
submission_function_mock = MagicMock()
22+
original_function_mock = MagicMock()
23+
24+
wrap_apply_async(submission_function_mock)(None, original_function_mock)
25+
26+
# check whether invocation of original function _has_ happened
27+
submission_function_mock.assert_called_once_with(
28+
None, original_function_mock, args=(), kwds={}
29+
)
30+
31+
# ensure that the function has _not_ been wrapped
32+
wrap_task_func_mock.assert_not_called()
33+
34+
@patch("opencensus.ext.threading.trace.wrap_task_func")
35+
@patch("opencensus.trace.execution_context.get_opencensus_tracer")
36+
def test_noop_tracer_wrap_submit(
37+
self, get_opencensus_tracer_mock: MagicMock, wrap_task_func_mock: MagicMock
38+
):
39+
mock_tracer = NoopTracer()
40+
get_opencensus_tracer_mock.return_value = mock_tracer
41+
submission_function_mock = MagicMock()
42+
original_function_mock = MagicMock()
43+
44+
wrap_submit(submission_function_mock)(None, original_function_mock)
45+
46+
# check whether invocation of original function _has_ happened
47+
submission_function_mock.assert_called_once_with(None, original_function_mock)
48+
49+
# ensure that the function has _not_ been wrapped
50+
wrap_task_func_mock.assert_not_called()
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock
3+
from opencensus.ext.threading.trace import wrap_submit, wrap_apply_async
4+
5+
6+
class TestTracer(unittest.TestCase):
7+
"""
8+
Ensures that sampler, exporter, propagator are passed through
9+
in case global tracer is present.
10+
"""
11+
12+
@patch("opencensus.trace.propagation.binary_format.BinaryFormatPropagator")
13+
@patch("opencensus.ext.threading.trace.wrap_task_func")
14+
@patch("opencensus.trace.execution_context.get_opencensus_tracer")
15+
def test_apply_async_context_passed(
16+
self,
17+
get_opencensus_tracer_mock: MagicMock,
18+
wrap_task_func_mock: MagicMock,
19+
binary_format_propagator_mock: MagicMock,
20+
):
21+
mock_tracer = NoNoopTracerMock()
22+
# ensure that unique object is generated
23+
mock_tracer.sampler = MagicMock()
24+
mock_tracer.exporter = MagicMock()
25+
mock_tracer.propagator = MagicMock()
26+
27+
get_opencensus_tracer_mock.return_value = mock_tracer
28+
29+
submission_function_mock = MagicMock()
30+
original_function_mock = MagicMock()
31+
32+
wrap_apply_async(submission_function_mock)(None, original_function_mock)
33+
34+
# check whether invocation of original function _has_ happened
35+
call = submission_function_mock.call_args_list[0].kwargs
36+
37+
self.assertEqual(id(call["kwds"]["sampler"]), id(mock_tracer.sampler))
38+
self.assertEqual(id(call["kwds"]["exporter"]), id(mock_tracer.exporter))
39+
self.assertEqual(id(call["kwds"]["propagator"]), id(mock_tracer.propagator))
40+
41+
@patch("opencensus.trace.propagation.binary_format.BinaryFormatPropagator")
42+
@patch("opencensus.ext.threading.trace.wrap_task_func")
43+
@patch("opencensus.trace.execution_context.get_opencensus_tracer")
44+
def test_wrap_submit_context_passed(
45+
self,
46+
get_opencensus_tracer_mock: MagicMock,
47+
wrap_task_func_mock: MagicMock,
48+
binary_format_propagator_mock: MagicMock,
49+
):
50+
mock_tracer = NoNoopTracerMock()
51+
# ensure that unique object is generated
52+
mock_tracer.sampler = MagicMock()
53+
mock_tracer.exporter = MagicMock()
54+
mock_tracer.propagator = MagicMock()
55+
56+
get_opencensus_tracer_mock.return_value = mock_tracer
57+
58+
submission_function_mock = MagicMock()
59+
original_function_mock = MagicMock()
60+
61+
wrap_submit(submission_function_mock)(None, original_function_mock)
62+
63+
# check whether invocation of original function _has_ happened
64+
call = submission_function_mock.call_args_list[0].kwargs
65+
66+
self.assertEqual(id(call["sampler"]), id(mock_tracer.sampler))
67+
self.assertEqual(id(call["exporter"]), id(mock_tracer.exporter))
68+
self.assertEqual(id(call["propagator"]), id(mock_tracer.propagator))
69+
70+
71+
class NoNoopTracerMock(MagicMock):
72+
pass

0 commit comments

Comments
 (0)