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

Commit 1de5779

Browse files
authored
Refactor context propagation to work with async (#588)
Introduced the generic RuntimeContext. Migrate execution_context to RuntimeContext
1 parent ab0b588 commit 1de5779

File tree

22 files changed

+625
-50
lines changed

22 files changed

+625
-50
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Add this changelog.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OpenCensus Runtime Context
2+
============================================================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opencensus-context.svg
7+
:target: https://pypi.org/project/opencensus-context/
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import asyncio
16+
from opencensus.common.runtime_context import RuntimeContext
17+
18+
RuntimeContext.register_slot('current_span', None)
19+
20+
21+
class Span(object):
22+
def __init__(self, name):
23+
self.name = name
24+
self.parent = RuntimeContext.current_span
25+
26+
def __repr__(self):
27+
return ('{}(name={}, parent={})'
28+
.format(
29+
type(self).__name__,
30+
self.name,
31+
self.parent,
32+
))
33+
34+
async def __aenter__(self):
35+
RuntimeContext.current_span = self
36+
37+
async def __aexit__(self, exc_type, exc, tb):
38+
RuntimeContext.current_span = self.parent
39+
40+
41+
async def main():
42+
print(RuntimeContext)
43+
async with Span('foo'):
44+
print(RuntimeContext)
45+
await asyncio.sleep(0.1)
46+
async with Span('bar'):
47+
print(RuntimeContext)
48+
await asyncio.sleep(0.1)
49+
print(RuntimeContext)
50+
await asyncio.sleep(0.1)
51+
print(RuntimeContext)
52+
53+
54+
if __name__ == '__main__':
55+
asyncio.run(main())
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from threading import Thread
16+
from opencensus.common.runtime_context import RuntimeContext
17+
18+
RuntimeContext.register_slot('operation_id', '<empty>')
19+
20+
21+
def work(name):
22+
print('Entering worker:', RuntimeContext)
23+
RuntimeContext.operation_id = name
24+
print('Exiting worker:', RuntimeContext)
25+
26+
27+
if __name__ == '__main__':
28+
print('Main thread:', RuntimeContext)
29+
RuntimeContext.operation_id = 'main'
30+
31+
print('Main thread:', RuntimeContext)
32+
33+
# by default context is not propagated to worker thread
34+
thread = Thread(target=work, args=('foo',))
35+
thread.start()
36+
thread.join()
37+
38+
print('Main thread:', RuntimeContext)
39+
40+
# user can propagate context explicitly
41+
thread = Thread(
42+
target=RuntimeContext.with_current_context(work),
43+
args=('bar',),
44+
)
45+
thread.start()
46+
thread.join()
47+
48+
print('Main thread:', RuntimeContext)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.common.runtime_context import RuntimeContext
16+
17+
RuntimeContext.register_slot('correlation_context', lambda: {})
18+
19+
20+
def hello(name):
21+
correlation_context = RuntimeContext.correlation_context.copy()
22+
correlation_context['name'] = name
23+
RuntimeContext.correlation_context = correlation_context
24+
25+
print(RuntimeContext)
26+
27+
28+
if __name__ == '__main__':
29+
print(RuntimeContext)
30+
RuntimeContext.correlation_context['test'] = True
31+
print(RuntimeContext)
32+
hello('hello')
33+
RuntimeContext.clear()
34+
print(RuntimeContext)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import asyncio
16+
17+
from opencensus.common.runtime_context import RuntimeContext
18+
19+
RuntimeContext.register_slot('correlation_context', lambda: dict())
20+
21+
22+
async def hello(name):
23+
correlation_context = RuntimeContext.correlation_context.copy()
24+
correlation_context['name'] = name
25+
RuntimeContext.correlation_context = correlation_context
26+
27+
for i in range(3):
28+
print('Hello {} {} {}'.format(
29+
name,
30+
i,
31+
RuntimeContext,
32+
))
33+
await asyncio.sleep(0.1)
34+
35+
36+
async def main():
37+
print(RuntimeContext)
38+
RuntimeContext.correlation_context['test'] = True
39+
print(RuntimeContext)
40+
await asyncio.gather(
41+
hello('foo'),
42+
hello('bar'),
43+
hello('baz'),
44+
)
45+
print(RuntimeContext)
46+
RuntimeContext.clear()
47+
print(RuntimeContext)
48+
49+
50+
if __name__ == '__main__':
51+
asyncio.run(main())
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.common.runtime_context import RuntimeContext
16+
17+
RuntimeContext.register_slot('current_span', None)
18+
19+
20+
class Span(object):
21+
def __init__(self, name):
22+
self.name = name
23+
self.parent = RuntimeContext.current_span
24+
25+
def __repr__(self):
26+
return ('{}({})'.format(type(self).__name__, self.name))
27+
28+
def __enter__(self):
29+
RuntimeContext.current_span = self
30+
31+
def __exit__(self, type, value, traceback):
32+
RuntimeContext.current_span = self.parent
33+
34+
def start(self):
35+
RuntimeContext.current_span = self
36+
37+
def end(self):
38+
RuntimeContext.current_span = self.parent
39+
40+
41+
if __name__ == '__main__':
42+
print(RuntimeContext)
43+
with Span('foo'):
44+
print(RuntimeContext)
45+
with Span('bar'):
46+
print(RuntimeContext)
47+
print(RuntimeContext)
48+
print(RuntimeContext)
49+
50+
# explicit start/end span
51+
span = Span('baz')
52+
print(RuntimeContext)
53+
span.start()
54+
print(RuntimeContext)
55+
span.end()
56+
print(RuntimeContext)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from multiprocessing.dummy import Pool as ThreadPool
16+
import time
17+
import threading
18+
from opencensus.common.runtime_context import RuntimeContext
19+
20+
RuntimeContext.register_slot('operation_id', '<empty>')
21+
_console_lock = threading.Lock()
22+
23+
24+
def println(msg):
25+
with _console_lock:
26+
print(msg)
27+
28+
29+
def work(name):
30+
println('Entering worker[{}]: {}'.format(name, RuntimeContext))
31+
RuntimeContext.operation_id = name
32+
time.sleep(0.01)
33+
println('Exiting worker[{}]: {}'.format(name, RuntimeContext))
34+
35+
36+
if __name__ == "__main__":
37+
println('Main thread: {}'.format(RuntimeContext))
38+
RuntimeContext.operation_id = 'main'
39+
pool = ThreadPool(2) # create a thread pool with 2 threads
40+
pool.map(RuntimeContext.with_current_context(work), [
41+
'bear',
42+
'cat',
43+
'dog',
44+
'horse',
45+
'rabbit',
46+
])
47+
pool.close()
48+
pool.join()
49+
println('Main thread: {}'.format(RuntimeContext))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)

0 commit comments

Comments
 (0)