forked from DataDog/dd-trace-py
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(futures): unload futures submodule during module cleanup (DataDog…
…#5402) This change ensures that the concurrent.futures submodule is unloaded when doing module unloading (e.g. when gevent is detected). This ensures that the module is reloaded when needed and uses the potential new state of the cloned threading module. ## Issue insight The captured stacks for all the processes during the thread pool deadlock look as follows ~~~ 💤🧒 Process 82315 🧵 Thread 4312286592 Thread._bootstrap (/Users/gabriele.tornetta/.pyenv/versions/3.11.2/lib/python3.11/threading.py:995) Thread._bootstrap_inner (/Users/gabriele.tornetta/.pyenv/versions/3.11.2/lib/python3.11/threading.py:1038) Thread.run (/Users/gabriele.tornetta/.pyenv/versions/3.11.2/lib/python3.11/threading.py:975) _worker (/Users/gabriele.tornetta/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:83) _WorkItem.run (/Users/gabriele.tornetta/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58) _wrap_execution (/Users/gabriele.tornetta/p403n1x87/dd-trace-py/ddtrace/contrib/futures/threading.py:43) Task.__call__ (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/tasks.py:139) Task._execute_main (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/tasks.py:162) SubmissionTask._main (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/tasks.py:269) UploadSubmissionTask._submit (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/upload.py:591) UploadSubmissionTask._submit_upload_request (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/upload.py:626) TransferCoordinator.submit (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/futures.py:323) BoundedExecutor.submit (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/s3transfer/futures.py:474) _wrap_submit (/Users/gabriele.tornetta/p403n1x87/dd-trace-py/ddtrace/contrib/futures/threading.py:30) ThreadPoolExecutor.submit (/Users/gabriele.tornetta/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:162) 💤 Process 81968 🧵 Thread 4312286592 <module> (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/bin/gunicorn:8) run (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py:67) Application.run (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/gunicorn/app/base.py:231) BaseApplication.run (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/gunicorn/app/base.py:72) Arbiter.run (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/gunicorn/arbiter.py:209) Arbiter.sleep (/Users/gabriele.tornetta/p403n1x87/ddtrace-gevent-bug/.venv/lib/python3.11/site-packages/gunicorn/arbiter.py:357) ~~~ ## Implementation details The patching of `ThreadPoolExecutor.submit` has been refactored to be performed with the internal wrapping utilities instead of `wrapt`. One issue caused by the previous implementation was in the way `wrapt` used module paths to locate and patch the designated callable. Since the `concurrent` module is not being unloaded, any attempts to look up `futures` from `concurrent` would lead to the original reference being returned, even in cases where the reference to the cloned `concurrent.futures` is desired. _En passant_, the refactor has also brought to light a tolerance to passing a mandatory positional argument to `ThreadPoolExecutor.submit` via keyword arguments. ## Testing strategy The change includes a simple reproducing case that works with the fix in place. ## Risks The `concurrent` module has been added to the exclude list in the initial implementation of DataDog#4863 because of issues observed during test runs (in particular with framework tests). It is unclear whether the same issues would actually arise outside the CI context and in actual applications. However, because the `concurrent.futures` modules creates lock objects on initialisation, and since it is imported indirectly by `ddtrace` via its dependencies, the issue caused by the `futures` integration requires the module to be reloaded after the gevent monkey-patching. The compromise of this PR is to unload only the `concurrent.futures` sub-module (and its sub-modules), which seems enough to solve the original issue. Fixes DataDog#5398
- Loading branch information
1 parent
551a7dd
commit a9dd3ed
Showing
10 changed files
with
102 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,45 @@ | ||
from concurrent import futures | ||
import sys | ||
|
||
from ddtrace.vendor.wrapt import wrap_function_wrapper as _w | ||
from ddtrace.internal.compat import PY2 | ||
from ddtrace.internal.wrapping import unwrap as _u | ||
from ddtrace.internal.wrapping import wrap as _w | ||
|
||
from ..trace_utils import unwrap as _u | ||
from .threading import _wrap_submit | ||
|
||
|
||
def patch(): | ||
"""Enables Context Propagation between threads""" | ||
if getattr(futures, "__datadog_patch", False): | ||
try: | ||
# Ensure that we get hold of the reloaded module if module cleanup was | ||
# performed. | ||
thread = sys.modules["concurrent.futures.thread"] | ||
except KeyError: | ||
import concurrent.futures.thread as thread | ||
|
||
if getattr(thread, "__datadog_patch", False): | ||
return | ||
setattr(futures, "__datadog_patch", True) | ||
setattr(thread, "__datadog_patch", True) | ||
|
||
_w("concurrent.futures", "ThreadPoolExecutor.submit", _wrap_submit) | ||
if PY2: | ||
_w(thread.ThreadPoolExecutor.submit.__func__, _wrap_submit) | ||
else: | ||
_w(thread.ThreadPoolExecutor.submit, _wrap_submit) | ||
|
||
|
||
def unpatch(): | ||
"""Disables Context Propagation between threads""" | ||
if not getattr(futures, "__datadog_patch", False): | ||
try: | ||
# Ensure that we get hold of the reloaded module if module cleanup was | ||
# performed. | ||
thread = sys.modules["concurrent.futures.thread"] | ||
except KeyError: | ||
return | ||
setattr(futures, "__datadog_patch", False) | ||
|
||
_u(futures.ThreadPoolExecutor, "submit") | ||
if not getattr(thread, "__datadog_patch", False): | ||
return | ||
setattr(thread, "__datadog_patch", False) | ||
|
||
if PY2: | ||
_u(thread.ThreadPoolExecutor.submit.__func__, _wrap_submit) | ||
else: | ||
_u(thread.ThreadPoolExecutor.submit, _wrap_submit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
releasenotes/notes/fix-futures-module-unload-e84900affff06152.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
fixes: | ||
- | | ||
futures: Resolves an issue that prevents tasks from being submitted to a | ||
thread pool executor when gevent is used (e.g. as a worker class for | ||
gunicorn or celery). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters