-
Notifications
You must be signed in to change notification settings - Fork 161
AsyncioScopeManager based on contextvars and supporting Tornado 6 #118
AsyncioScopeManager based on contextvars and supporting Tornado 6 #118
Conversation
Hey hey @condorcet awesome, looks pretty neat. I will get back for a full review in the next days. One thing to maybe do, is to rename the 'new' (also, this way it won't be weird to use it with Tornado apps ;) ) |
@carlosalberto I thought about backward compatibility between new and old scope manager, that's why I made changes right into |
2824a21
to
a3bc006
Compare
@carlosalberto ping |
Hey @condorcet Sorry for the delayed answer. So keeping backwards compatibility is nice - so what about having the new manager be class AsyncioScopeManager(ContextScopeManager):
""" Documentation mentioning this now inherits from ContextScopeManager, etc
"""
pass We can keep the old Also, we can keep a single testbed for Let me know if this sounds reasonable for you - else, feel free to comment :) |
ping @condorcet |
Hello @carlosalberto thank you for answer.
If we make special What do you think? |
ping @carlosalberto |
opentracing/__init__.py
Outdated
@@ -14,6 +14,11 @@ | |||
# limitations under the License. | |||
|
|||
from __future__ import absolute_import | |||
try: | |||
# Contextvars backport with coroutine supporting (python 3.6). | |||
import aiocontextvars # noqa |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not try this import from opentracing.scope_managers.asyncio
?
|
||
install: | ||
- make bootstrap | ||
- pip install -q "tornado$TORNADO" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably it's safer to do this before make bootstrap
;)
Automatic :class:`~opentracing.Span` propagation from | ||
parent coroutines to their children is not provided, which needs to be | ||
done manually: | ||
The scope manager provides automatic :class:`~opentracing.Span` propagation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one reason that makes me feel we should keep this new implementation under ContextVarScopeManager
or similar - otherwise, we would be breaking the assumption that manual propagation is required (we could at the same time deprecate AsyncioScopeManager
in favor of this new one, of course).
At the same time, does aiocontextvars
work for Python 3.4/3.5? If not, we cannot use them with asyncio
(I bring this up as I've talked to people and the need to still keep Python 3.4 backwards compatibility).
(I we are going to take this route, we would need, yeah, a section for contextvars
in the testbed :( )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @carlosalberto !
...otherwise, we would be breaking the assumption that manual propagation is required (we could at the same time deprecate AsyncioScopeManager in favor of this new one, of course).
That's what I meant about backward compatibility of new scope manager :) No need to activate parent span, but you can do it and it doesn't break behavior of existing user code. There are tests that prove it.
I bring this up as I've talked to people and the need to still keep Python 3.4 backwards compatibility
Wow! But this is really strong reason to separate new scope manager, because both libraries (contextvars
and aiocontextvars
) doesn't support python 3.4. So this brave new ContextVarScopeManager
we have to use only with python >= 3.5
we could at the same time deprecate AsyncioScopeManager in favor of this new one, of course
Maybe we can release new scope manager in new major version of the library (3.0.0) with renamed existing AsyncioScopeManager
-> OldAsyncioScopeManager
? Yes, it will break backward compatibility for python 3.4 users, but it's ok in terms of semver.
Hey @condorcet Sorry for the delayed answer - a mix of hectic days (which included talking to people on how to manage things with the nice incoming I left a pair of comments. Let me know what you think (as I won't be taking long holidays anytime soon, I should be able to be super responsive now - I can also help with the |
@carlosalberto We definitely have to separate Another feature we should remember when using
Of course it's very synthetic and rude: you shouldn't I have some draft and hope to commit it in next days. |
a3bc006
to
376c91d
Compare
Hello @carlosalberto ! Also I removed
Also I've added some new tests for case when parent context propagates to coroutines / callbacks that was scheduled in event loop by
|
Hello @condorcet, You probably might help with the similar work for OpenTelemetry which is the next major version of the Thank you! |
Hello @carlosalberto, |
|
||
|
||
@contextmanager | ||
def no_parent_scope(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not simply provide default=None
for _SCOPE
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not enough. I've added docstring to make the idea clear. Sometimes we can have a scope, that we don't want to propagate.
_SCOPE = ContextVar('scope') | ||
|
||
|
||
class ContextVarsScopeManager(ThreadLocalScopeManager): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to inherit from ThreadLocalScopeManager
, I think (we are not calling into it, and contextvars
itself works fine with thread-local data).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -28,6 +28,10 @@ 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. | |||
|
|||
### 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC contextvars
automatically propagates context only when used with asyncio
- else, the user needs to propagate himself the context (through contextvars.copy_context()
). Maybe we could add a note here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean explicit span propagation in multi-threaded code? Because you don't need to do this in single-threaded code, it works implicitly (like threading.local
). Or I misunderstood your idea?
@@ -47,4 +55,6 @@ def get_test_directories(): | |||
suite = loader.loadTestsFromModule(test_module) | |||
main_suite.addTests(suite) | |||
|
|||
unittest.TextTestRunner(verbosity=3).run(main_suite) | |||
result = unittest.TextTestRunner(verbosity=3).run(main_suite) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
@@ -44,7 +44,7 @@ def submit_subtasks(self, parent_span): | |||
logger.info('Running %s' % name) | |||
with self.tracer.scope_manager.activate(parent_span, False): | |||
with self.tracer.start_active_span(name): | |||
asyncio.sleep(0.1) | |||
await asyncio.sleep(0.1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
def assertNamesEqual(self, spans, names): | ||
self.assertEqual(list(map(lambda x: x.operation_name, spans)), names) | ||
|
||
def assertEmptySpan(self, span, name): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd leave the name assertion out of this test method ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's super convenient to assert span with name, because every span must has name :) I can make name
optional in this helper.
Hey @condorcet Sorry for the delay, had to look for some cycles to review (and play) with your code ;) it all looks good, thanks a lot! I left a few comments. Once that is resolved (along your own TODO notes), we should be ready to merge ;) |
…ornado to travis.yml.
376c91d
to
c4468e7
Compare
Hello @carlosalberto thank you for your feedback! I need your help with this bunch of contextvar-specific tests https://github.com/opentracing/opentracing-python/pull/118/files#diff-bd336347a31f644e377b29aa0b028962R64 Where should I move them? Or just leave them in this file? |
Hi @carlosalberto ! Please could you take a look at fixes? |
ping @carlosalberto |
excited for this one.. bump @carlosalberto . Thanks @condorcet for the implementation! |
@carlosalberto @yurishkuro Hi! What about the PR? While we don't have stable release of Open Telemetry which includes contextvars out of the box, it would be great to have this feature in OT. For example this feature was added by https://github.com/DataDog/dd-trace-py (which compatible with OT) on September. |
I have no experience with asyncio or contextvars, so cannot provide a review. However, it appears that this change is purely additive, so appears to be safe to merge.
Do you mean that the test bed runs all the existing scope manager implementations as well as the new one? |
@yurishkuro Yes, you're right, finally we have the new one scope manager |
ping @carlosalberto |
This would be massive for those using AsyncIO in Python3.7+ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the testbed design kind of weird, I was expecting some common code in each of the use cases, and each test for a specific context manager just providing some customizations. But it's not in scope of this PR to fix.
@@ -5,7 +5,8 @@ This example shows a `Span` used with `RequestHandler`, which is used as a middl | |||
Implementation details: | |||
- For `threading`, no active `Span` is consumed as the tasks may be run concurrently on different threads, and an explicit `SpanContext` has to be saved to be used as parent. | |||
- For `gevent` and `asyncio`, as no automatic `Span` propagation is done, an explicit `Span` has to be saved to be used as parent (observe an instrumentation library could help to do that implicitly - we stick to the simplest case, though). | |||
- For `tornado`, as the `StackContext` automatically propapates the context (even is the tasks are called through different coroutines), we **do** leverage the active `Span`. | |||
- For `tornado` (as the `StackContext`) and `contextvars` automatically propagates the context (even is the tasks are called through different coroutines), we **do** leverage the active `Span`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this sentence seems broken, eg. what is "(as the StackContext
)"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, thank you! I'll fix the typo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Co-Authored-By: Yuri Shkuro <yurishkuro@users.noreply.github.com>
Co-Authored-By: Yuri Shkuro <yurishkuro@users.noreply.github.com>
Co-Authored-By: Yuri Shkuro <yurishkuro@users.noreply.github.com>
Co-Authored-By: Yuri Shkuro <yurishkuro@users.noreply.github.com>
Python 3.7 provides contextvars that can be used for implicitly propagation of context from parent to child coroutines / tasks.
Implementation of
AsyncioScopeManager
in the PR based on this feature so no more need to make manual activation of parent span in child coroutine / tasks like this:For python 3.6 we have backport of contextvars with async / await supporting https://github.com/fantix/aiocontextvars that based on the "official" backport https://github.com/MagicStack/contextvars. So in python 3.6 "new"
AsyncioScopeManager
also works.Testbed contains tests for "new" AsyncioScopeManager and compatibility tests for original scope manager.
On the other side
Tornado >= 6
became fully asyncio-based framework (that whystack_context
was deprecated) so we no need to supportTornadoScopeManager
for these versions. I think the better way is to useAsyncioScopeManager
in new Tornado applications instead of backporting stack_context or something like this inTornadoScopeManager
specially for opentracing needs.Of course for
Tornado < 6
we have to useTornadoScopeManager
and it still here.