Skip to content

Commit b715ee5

Browse files
fix(signals): only handle signals if in main thread [backport 2.0] (#7218)
Backport 0e21580 from #7204 to 2.0. Prior to this change ddtrace attempted to register signal handling code regardless of whether or not it was in the main thread. This caused ddtrace to fail and exit because in Python signal handling can only happen in the main thread. After this change, we only add signal handling capabilities if ddtrace is imported/initialized as part of the main thread. ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. --------- Co-authored-by: Teague Bick <[email protected]>
1 parent b4a8548 commit b715ee5

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

ddtrace/internal/atexit.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import atexit
88
import logging
99
import signal
10+
import threading
1011
import typing
1112

1213
from ddtrace.internal.utils import signals
@@ -66,5 +67,13 @@ def register_on_exit_signal(f):
6667
def handle_exit(sig, frame):
6768
f()
6869

69-
signals.handle_signal(signal.SIGTERM, handle_exit)
70-
signals.handle_signal(signal.SIGINT, handle_exit)
70+
if threading.current_thread() is threading.main_thread():
71+
try:
72+
signals.handle_signal(signal.SIGTERM, handle_exit)
73+
signals.handle_signal(signal.SIGINT, handle_exit)
74+
except Exception:
75+
# We catch a general exception here because we don't know
76+
# what might go wrong, but we don't want to stop
77+
# normal program execution based upon failing to register
78+
# a signal handler.
79+
log.debug("Encountered an exception while registering a signal", exc_info=True)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fixes:
2+
- |
3+
data_streams: This fix resolves an issue where including exit signal handling for message queues caused imports of ddtrace in a thread to fail.

tests/datastreams/test_processor.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,29 @@ def run_test():
9797
env["DD_DATA_STREAMS_ENABLED"] = "True"
9898
out, err, status, _ = ddtrace_run_python_code_in_subprocess(code, env=env, timeout=5)
9999
assert out.decode().strip() == "Fake flush called"
100+
101+
102+
def test_threaded_import(ddtrace_run_python_code_in_subprocess):
103+
code = """
104+
import pytest
105+
import sys
106+
import time
107+
import threading
108+
109+
from ddtrace.internal.datastreams.processor import DataStreamsProcessor
110+
111+
def fake_flush(*args, **kwargs):
112+
print("Fake flush called")
113+
114+
def run_test():
115+
processor = DataStreamsProcessor("http://localhost:8126")
116+
117+
t = threading.Thread(target=run_test)
118+
t.start()
119+
t.join()
120+
"""
121+
122+
env = os.environ.copy()
123+
env["DD_DATA_STREAMS_ENABLED"] = "True"
124+
out, err, status, _ = ddtrace_run_python_code_in_subprocess(code, env=env, timeout=5)
125+
assert err.decode().strip() == ""

tests/tracer/test_tracer.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,18 @@ def test_enable():
11291129
assert not t2.enabled
11301130

11311131

1132+
@pytest.mark.subprocess(parametrize={"DD_TRACE_ENABLED": ["true", "false"]})
1133+
def test_threaded_import():
1134+
import threading
1135+
1136+
def thread_target():
1137+
import ddtrace # noqa: F401
1138+
1139+
t = threading.Thread(target=thread_target)
1140+
t.start()
1141+
t.join()
1142+
1143+
11321144
def test_runtime_id_parent_only():
11331145
tracer = ddtrace.Tracer()
11341146

0 commit comments

Comments
 (0)