Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Commit

Permalink
Inherit ContextVarsScopeManagerFix directly from ScopeManager, fix do…
Browse files Browse the repository at this point in the history
…cstrings and README
  • Loading branch information
condorcet committed Sep 9, 2019
1 parent 784d815 commit c4468e7
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 deletions.
36 changes: 26 additions & 10 deletions opentracing/scope_managers/contextvars.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,35 @@
from contextlib import contextmanager
from contextvars import ContextVar

from opentracing import Scope
from opentracing.scope_managers import ThreadLocalScopeManager
from opentracing import Scope, ScopeManager


_SCOPE = ContextVar('scope')


class ContextVarsScopeManager(ThreadLocalScopeManager):
class ContextVarsScopeManager(ScopeManager):
"""
:class:`~opentracing.ScopeManager` implementation for **asyncio**
that stores the :class:`~opentracing.Scope` using ContextVar.
The scope manager provides automatic :class:`~opentracing.Span` propagation
from parent coroutines to their children.
from parent coroutines, tasks and scheduled in event loop callbacks to
their children.
.. code-block:: python
async def child_coroutine(span):
async def child_coroutine():
# No need manual activation of parent span in child coroutine.
with tracer.start_active_span('child') as scope:
...
async def parent_coroutine():
with tracer.start_active_span('parent') as scope:
...
await child_coroutine(span)
await child_coroutine()
...
"""
# TODO: update description

def activate(self, span, finish_on_close):
"""
Expand All @@ -74,13 +73,11 @@ def activate(self, span, finish_on_close):
def active(self):
"""
Return the currently active :class:`~opentracing.Scope` which
can be used to access the currently active
:attr:`Scope.span`.
can be used to access the currently active :attr:`Scope.span`.
:return: the :class:`~opentracing.Scope` that is active,
or ``None`` if not available.
"""
# TODO: update description

return self._get_scope()

Expand All @@ -100,6 +97,7 @@ def __init__(self, manager, span, finish_on_close):
def close(self):
if self.manager.active is not self:
return

_SCOPE.reset(self._token)

if self._finish_on_close:
Expand All @@ -108,6 +106,24 @@ def close(self):

@contextmanager
def no_parent_scope():
"""
Context manager that resets current Scope. Intended to break span
propagation to children coroutines, tasks or scheduled callbacks.
.. code-block:: python
from opentracing.scope_managers.contextvars import no_parent_scope
def periodic()
# `periodic` span will be children of root only at the first time.
with self.tracer.start_active_span('periodic'):
# Now we break span propagation.
with no_parent_scope():
self.loop.call_soon(periodic)
with self.tracer.start_active_span('root'):
self.loop.call_soon(periodic)
"""
token = _SCOPE.set(None)
try:
yield
Expand Down
4 changes: 2 additions & 2 deletions testbed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ No automatic `Span` propagation between parent and children tasks is provided, a

`TornadoScopeManager` uses a variation of `tornado.stack_context.StackContext` to both store **and** automatically propagate the context from parent coroutines to their children.

Currently, yielding over multiple children is not supported, as the context is effectively shared, and switching from coroutine to coroutine messes up the current active `Span`.

### contextvars

`ContextVarsScopeManager` uses [contextvars](https://docs.python.org/3/library/contextvars.html) module to both store **and** automatically propagate the context from parent coroutines / tasks / scheduled in event loop callbacks to their children.

Currently, yielding over multiple children is not supported, as the context is effectively shared, and switching from coroutine to coroutine messes up the current active `Span`.

## List of patterns

- [Active Span replacement](test_active_span_replacement) - Start an isolated task and query for its results in another task/thread.
Expand Down
33 changes: 33 additions & 0 deletions testbed/test_nested_callbacks/test_contextvars.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,36 @@ def callbacks():
# Second task "wrapped" by `no_parent_scope`.
self.assertEmptySpan(task2, 'task_2')
self.assertHasNoParent(task2)

def test_await_with_no_parent_scope(self):

async def coro(name):
with self.tracer.start_active_span(name):
pass

async def main_coro():
await coro('coro_1')
with no_parent_scope():
await coro('coro_2')
await coro('coro_3')

with self.tracer.start_active_span('root'):
self.loop.create_task(main_coro())

stop_loop_when(self.loop,
lambda: len(self.tracer.finished_spans()) == 4)
self.loop.run_forever()

root, coro1, coro2, coro3 = self.tracer.finished_spans()

self.assertEmptySpan(root, 'root')

self.assertEmptySpan(coro1, 'coro_1')
self.assertIsChildOf(coro1, root)

# second coroutine "wrapped" by `no_parent_scope`.
self.assertEmptySpan(coro2, 'coro_2')
self.assertHasNoParent(coro2)

self.assertEmptySpan(coro3, 'coro_3')
self.assertIsChildOf(coro3, root)

0 comments on commit c4468e7

Please sign in to comment.