From 237e206ff3ec81fd5d19ac81edadfe4efaee6fc1 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 6 Feb 2025 12:27:34 -0500 Subject: [PATCH 1/3] remove asyncio deprecations --- ddtrace/contrib/internal/asyncio/compat.py | 29 ----- ddtrace/contrib/internal/asyncio/helpers.py | 105 ------------------- ddtrace/contrib/internal/asyncio/provider.py | 83 --------------- ddtrace/contrib/internal/asyncio/wrappers.py | 25 ----- 4 files changed, 242 deletions(-) delete mode 100644 ddtrace/contrib/internal/asyncio/compat.py delete mode 100644 ddtrace/contrib/internal/asyncio/helpers.py delete mode 100644 ddtrace/contrib/internal/asyncio/provider.py delete mode 100644 ddtrace/contrib/internal/asyncio/wrappers.py diff --git a/ddtrace/contrib/internal/asyncio/compat.py b/ddtrace/contrib/internal/asyncio/compat.py deleted file mode 100644 index 95be608a3cc..00000000000 --- a/ddtrace/contrib/internal/asyncio/compat.py +++ /dev/null @@ -1,29 +0,0 @@ -import asyncio - -from ddtrace.vendor.debtcollector import deprecate - - -if hasattr(asyncio, "current_task"): - - def asyncio_current_task(): - deprecate( - "ddtrace.contrib.internal.asyncio.create_task(..) is deprecated. " - "The ddtrace library fully supports propagating " - "trace contextes to async tasks. No additional configurations are required.", - version="3.0.0", - ) - try: - return asyncio.current_task() - except RuntimeError: - return None - -else: - - def asyncio_current_task(): - deprecate( - "ddtrace.contrib.internal.asyncio.create_task(..) is deprecated. " - "The ddtrace library fully supports propagating " - "trace contextes to async tasks. No additional configurations are required.", - version="3.0.0", - ) - return asyncio.Task.current_task() diff --git a/ddtrace/contrib/internal/asyncio/helpers.py b/ddtrace/contrib/internal/asyncio/helpers.py deleted file mode 100644 index e5d56705aa5..00000000000 --- a/ddtrace/contrib/internal/asyncio/helpers.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -This module includes a list of convenience methods that -can be used to simplify some operations while handling -Context and Spans in instrumented ``asyncio`` code. -""" -import asyncio - -import ddtrace -from ddtrace.contrib.internal.asyncio.provider import AsyncioContextProvider -from ddtrace.contrib.internal.asyncio.wrappers import wrapped_create_task -from ddtrace.vendor.debtcollector import deprecate - - -def set_call_context(task, ctx): - """ - Updates the ``Context`` for the given Task. Useful when you need to - pass the context among different tasks. - - This method is available for backward-compatibility. Use the - ``AsyncioContextProvider`` API to set the current active ``Context``. - """ - deprecate( - "ddtrace.contrib.internal.asyncio.set_call_context(..) is deprecated. " - "The ddtrace library fully supports propagating " - "trace contextes to async tasks. No additional configurations are required.", - version="3.0.0", - ) - setattr(task, AsyncioContextProvider._CONTEXT_ATTR, ctx) - - -def ensure_future(coro_or_future, *, loop=None, tracer=None): - """Wrapper that sets a context to the newly created Task. - - If the current task already has a Context, it will be attached to the new Task so the Trace list will be preserved. - """ - deprecate( - "ddtrace.contrib.internal.asyncio.ensure_future(..) is deprecated. " - "The ddtrace library fully supports propagating " - "trace contextes to async tasks. No additional configurations are required.", - version="3.0.0", - ) - tracer = tracer or ddtrace.tracer - current_ctx = tracer.current_trace_context() - task = asyncio.ensure_future(coro_or_future, loop=loop) - set_call_context(task, current_ctx) - return task - - -def run_in_executor(loop, executor, func, *args, tracer=None): - """Wrapper function that sets a context to the newly created Thread. - - If the current task has a Context, it will be attached as an empty Context with the current_span activated to - inherit the ``trace_id`` and the ``parent_id``. - - Because the Executor can run the Thread immediately or after the - coroutine is executed, we may have two different scenarios: - * the Context is copied in the new Thread and the trace is sent twice - * the coroutine flushes the Context and when the Thread copies the - Context it is already empty (so it will be a root Span) - - To support both situations, we create a new Context that knows only what was - the latest active Span when the new thread was created. In this new thread, - we fallback to the thread-local ``Context`` storage. - - """ - deprecate( - "ddtrace.contrib.internal.asyncio.run_in_executor(..) is deprecated. " - "The ddtrace library fully supports propagating " - "trace contexts to async tasks. No additional configurations are required.", - version="3.0.0", - ) - tracer = tracer or ddtrace.tracer - current_ctx = tracer.current_trace_context() - - # prepare the future using an executor wrapper - future = loop.run_in_executor(executor, _wrap_executor, func, args, tracer, current_ctx) - return future - - -def _wrap_executor(fn, args, tracer, ctx): - """ - This function is executed in the newly created Thread so the right - ``Context`` can be set in the thread-local storage. This operation - is safe because the ``Context`` class is thread-safe and can be - updated concurrently. - """ - # the AsyncioContextProvider knows that this is a new thread - # so it is legit to pass the Context in the thread-local storage; - # fn() will be executed outside the asyncio loop as a synchronous code - tracer.context_provider.activate(ctx) - return fn(*args) - - -def create_task(*args, **kwargs): - """This function spawns a task with a Context that inherits the - `trace_id` and the `parent_id` from the current active one if available. - """ - deprecate( - "ddtrace.contrib.internal.asyncio.create_task(..) is deprecated. " - "The ddtrace library fully supports propagating " - "trace contexts to async tasks. No additional configurations are required.", - version="3.0.0", - ) - loop = asyncio.get_event_loop() - return wrapped_create_task(loop.create_task, None, args, kwargs) diff --git a/ddtrace/contrib/internal/asyncio/provider.py b/ddtrace/contrib/internal/asyncio/provider.py deleted file mode 100644 index fac41470740..00000000000 --- a/ddtrace/contrib/internal/asyncio/provider.py +++ /dev/null @@ -1,83 +0,0 @@ -import asyncio - -from ddtrace._trace.provider import BaseContextProvider -from ddtrace._trace.provider import DatadogContextMixin -from ddtrace.trace import Span -from ddtrace.vendor.debtcollector import deprecate - - -class AsyncioContextProvider(BaseContextProvider, DatadogContextMixin): - """Manages the active context for asyncio execution. Framework - instrumentation that is built on top of the ``asyncio`` library, should - use this provider when contextvars are not available (Python versions - less than 3.7). - - This Context Provider inherits from ``DefaultContextProvider`` because - it uses a thread-local storage when the ``Context`` is propagated to - a different thread, than the one that is running the async loop. - """ - - # Task attribute used to set/get the context - _CONTEXT_ATTR = "__datadog_context" - - def __init__(self) -> None: - deprecate( - "The `ddtrace.contrib.internal.asyncio.AsyncioContextProvider` class is deprecated." - " Use `ddtrace.DefaultContextProvider` instead.", - version="3.0.0", - ) - super().__init__() - - def activate(self, context, loop=None): - """Sets the scoped ``Context`` for the current running ``Task``.""" - loop = self._get_loop(loop) - if not loop: - super(AsyncioContextProvider, self).activate(context) - return context - - # the current unit of work (if tasks are used) - task = asyncio.Task.current_task(loop=loop) - if task: - setattr(task, self._CONTEXT_ATTR, context) - return context - - def _get_loop(self, loop=None): - """Helper to try and resolve the current loop""" - try: - return loop or asyncio.get_event_loop() - except RuntimeError: - # Detects if a loop is available in the current thread; - # DEV: This happens when a new thread is created from the out that is running the async loop - # DEV: It's possible that a different Executor is handling a different Thread that - # works with blocking code. In that case, we fallback to a thread-local Context. - pass - return None - - def _has_active_context(self, loop=None): - """Helper to determine if we have a currently active context""" - loop = self._get_loop(loop=loop) - if loop is None: - return super(AsyncioContextProvider, self)._has_active_context() - - # the current unit of work (if tasks are used) - task = asyncio.Task.current_task(loop=loop) - if task is None: - return False - - ctx = getattr(task, self._CONTEXT_ATTR, None) - return ctx is not None - - def active(self, loop=None): - """Returns the active context for the execution.""" - loop = self._get_loop(loop=loop) - if not loop: - return super(AsyncioContextProvider, self).active() - - # the current unit of work (if tasks are used) - task = asyncio.Task.current_task(loop=loop) - if task is None: - return None - ctx = getattr(task, self._CONTEXT_ATTR, None) - if isinstance(ctx, Span): - return self._update_active(ctx) - return ctx diff --git a/ddtrace/contrib/internal/asyncio/wrappers.py b/ddtrace/contrib/internal/asyncio/wrappers.py deleted file mode 100644 index 1166fed96c3..00000000000 --- a/ddtrace/contrib/internal/asyncio/wrappers.py +++ /dev/null @@ -1,25 +0,0 @@ -from ddtrace.contrib.internal.asyncio.compat import asyncio_current_task -from ddtrace.contrib.internal.asyncio.provider import AsyncioContextProvider - - -def wrapped_create_task(wrapped, instance, args, kwargs): - """Wrapper for ``create_task(coro)`` that propagates the current active - ``Context`` to the new ``Task``. This function is useful to connect traces - of detached executions. - - Note: we can't just link the task contexts due to the following scenario: - * begin task A - * task A starts task B1..B10 - * finish task B1-B9 (B10 still on trace stack) - * task A starts task C - * now task C gets parented to task B10 since it's still on the stack, - however was not actually triggered by B10 - """ - new_task = wrapped(*args, **kwargs) - current_task = asyncio_current_task() - - ctx = getattr(current_task, AsyncioContextProvider._CONTEXT_ATTR, None) - if ctx: - setattr(new_task, AsyncioContextProvider._CONTEXT_ATTR, ctx) - - return new_task From ac55696b03e7c0ca37e2470be1b90f9e69e93f07 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 6 Feb 2025 16:44:57 -0500 Subject: [PATCH 2/3] remove gevent deps --- ddtrace/contrib/internal/gevent/provider.py | 47 --------------------- 1 file changed, 47 deletions(-) delete mode 100644 ddtrace/contrib/internal/gevent/provider.py diff --git a/ddtrace/contrib/internal/gevent/provider.py b/ddtrace/contrib/internal/gevent/provider.py deleted file mode 100644 index c07b0512d6d..00000000000 --- a/ddtrace/contrib/internal/gevent/provider.py +++ /dev/null @@ -1,47 +0,0 @@ -import gevent - -from ddtrace._trace.provider import BaseContextProvider -from ddtrace._trace.provider import DatadogContextMixin -from ddtrace.trace import Span -from ddtrace.vendor.debtcollector import deprecate - - -class GeventContextProvider(BaseContextProvider, DatadogContextMixin): - """Manages the active context for gevent execution. - - This provider depends on corresponding monkey patches to copy the active - context from one greenlet to another. - """ - - # Greenlet attribute used to set/get the context - _CONTEXT_ATTR = "__datadog_context" - - def __init__(self) -> None: - deprecate("GeventContextProvider is deprecated and will be removed in a future version.", "3.0.0") - super().__init__() - - def _get_current_context(self): - """Helper to get the active context from the current greenlet.""" - current_g = gevent.getcurrent() - if current_g is not None: - return getattr(current_g, self._CONTEXT_ATTR, None) - return None - - def _has_active_context(self): - """Helper to determine if there is an active context.""" - return self._get_current_context() is not None - - def activate(self, context): - """Sets the active context for the current running ``Greenlet``.""" - current_g = gevent.getcurrent() - if current_g is not None: - setattr(current_g, self._CONTEXT_ATTR, context) - super(GeventContextProvider, self).activate(context) - return context - - def active(self): - """Returns the active context for this execution flow.""" - ctx = self._get_current_context() - if isinstance(ctx, Span): - return self._update_active(ctx) - return ctx From 7fda1a7ab0b922b2e63266c951ba27c0b9ae4ee2 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 6 Feb 2025 16:45:49 -0500 Subject: [PATCH 3/3] fix test --- tests/contrib/asyncio/test_tracer.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/contrib/asyncio/test_tracer.py b/tests/contrib/asyncio/test_tracer.py index cb474786b72..2304544e62b 100644 --- a/tests/contrib/asyncio/test_tracer.py +++ b/tests/contrib/asyncio/test_tracer.py @@ -4,7 +4,6 @@ import pytest from ddtrace.constants import ERROR_MSG -from ddtrace.contrib.internal.asyncio.compat import asyncio_current_task from ddtrace.contrib.internal.asyncio.patch import patch from ddtrace.contrib.internal.asyncio.patch import unpatch @@ -65,20 +64,6 @@ def test_event_loop_exception(tracer): assert ctx is None -def test_context_task_none(tracer): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - # it should handle the case where a Task is not available - # Note: the @pytest.mark.asyncio is missing to simulate an execution - # without a Task - task = asyncio_current_task() - # the task is not available - assert task is None - - ctx = tracer.current_trace_context() - assert ctx is None - - @pytest.mark.asyncio async def test_exception(tracer): async def f1():