From 98786d48c7d95cc8b3b84c606bac62febaa5e18c Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 5 Nov 2025 14:49:46 -0500 Subject: [PATCH 01/12] use unpatched lock --- ddtrace/internal/forksafe.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ddtrace/internal/forksafe.py b/ddtrace/internal/forksafe.py index 313f8a73cb9..87f1d74ab61 100644 --- a/ddtrace/internal/forksafe.py +++ b/ddtrace/internal/forksafe.py @@ -5,12 +5,13 @@ import functools import logging import os -import threading import typing import weakref import wrapt +from ddtrace.internal._unpatched import _threading as unpatched_threading + log = logging.getLogger(__name__) @@ -138,13 +139,13 @@ def _reset_object(self): self.__wrapped__ = self._self_wrapped_class() -def Lock() -> threading.Lock: - return ResetObject(threading.Lock) # type: ignore +def Lock() -> unpatched_threading.Lock: + return ResetObject(unpatched_threading.Lock) # type: ignore -def RLock() -> threading.RLock: - return ResetObject(threading.RLock) # type: ignore +def RLock() -> unpatched_threading.RLock: + return ResetObject(unpatched_threading.RLock) # type: ignore -def Event() -> threading.Event: - return ResetObject(threading.Event) # type: ignore +def Event() -> unpatched_threading.Event: + return ResetObject(unpatched_threading.Event) # type: ignore From 13802f1617544e0a885f4f47c0ab275bffe9921f Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 5 Nov 2025 15:01:47 -0500 Subject: [PATCH 02/12] release note --- releasenotes/notes/forksafe-lock-06ca188ef798c1f8.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/forksafe-lock-06ca188ef798c1f8.yaml diff --git a/releasenotes/notes/forksafe-lock-06ca188ef798c1f8.yaml b/releasenotes/notes/forksafe-lock-06ca188ef798c1f8.yaml new file mode 100644 index 00000000000..cbd45fb9646 --- /dev/null +++ b/releasenotes/notes/forksafe-lock-06ca188ef798c1f8.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + core: This fix resolves an issue where forksafe locks used patched threading primitives from the profiling module, causing performance issues. The forksafe module now uses unpatched threading primitives (``Lock``, ``RLock``, ``Event``). From 1ab4f3c59b1cbbe795c45fb72757d5ee06f83c68 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 6 Nov 2025 21:35:40 -0500 Subject: [PATCH 03/12] add test case and modify _unpatch.py to explicit about which reference its keeping --- ddtrace/internal/_unpatched.py | 17 ++++++++++------- tests/internal/test_forksafe.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py index 8e99656c0ea..58ef7797518 100644 --- a/ddtrace/internal/_unpatched.py +++ b/ddtrace/internal/_unpatched.py @@ -3,15 +3,18 @@ from builtins import open as unpatched_open # noqa from json import loads as unpatched_json_loads # noqa -# Acquire a reference to the threading module. Some parts of the library (e.g. -# the profiler) might be enabled programmatically and therefore might end up -# getting a reference to the tracee's threading module. By storing a reference -# to the threading module used by ddtrace here, we make it easy for those parts -# to get a reference to the right threading module. -import threading as _threading # noqa -import gc as _gc # noqa +# Acquire references to threading and gc, then remove them from sys.modules. +# Some parts of the library (e.g. the profiler) might be enabled programmatically +# and patch the threading and gc modules. We store references to the original +# modules here to allow ddtrace internal modules to access the right versions of +# the modules. +import gc # noqa import sys +import threading # noqa + +_threading = sys.modules.pop("threading", None) +_gc = sys.modules.pop("gc", None) previous_loaded_modules = frozenset(sys.modules.keys()) from subprocess import Popen as unpatched_Popen # noqa # nosec B404 diff --git a/tests/internal/test_forksafe.py b/tests/internal/test_forksafe.py index a355e4e0656..3ee58173f84 100644 --- a/tests/internal/test_forksafe.py +++ b/tests/internal/test_forksafe.py @@ -204,6 +204,36 @@ def test_lock_fork(): assert exit_code == 12 +@pytest.mark.subprocess( + env=dict(DD_PROFILING_ENABLED="1"), + ddtrace_run=True, +) +def test_lock_unpatched(): + """Check that a forksafe.Lock is not patched when profiling is enabled.""" + + from ddtrace.internal import forksafe + from ddtrace.profiling import bootstrap + from ddtrace.profiling.collector.threading import ThreadingLockCollector + + # When Profiler is started, bootstrap.profiler is set to the Profiler + # instance. We explicitly access the Profiler instance and the collector list + # to verify that the forksafe.Lock is not using the same class that is + # patched by ThreadingLockCollector that's running. + profiler = bootstrap.profiler._profiler + lock_collector = None + for c in profiler._collectors: + if isinstance(c, ThreadingLockCollector): + lock_collector = c + break + + assert lock_collector is not None, "ThreadingLockCollector not found in profiler collectors" + + lock = forksafe.Lock() + assert ( + lock_collector._get_patch_target() is not lock._self_wrapped_class + ), "forksafe.Lock is using the same class that is patched by ThreadingLockCollector" + + def test_rlock_basic(): # type: (...) -> None """Check that a forksafe.RLock implements the correct threading.RLock interface""" From 847769c132eb0f82256f3614ac8794203e0db864 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 6 Nov 2025 22:05:16 -0500 Subject: [PATCH 04/12] address profiling-v2 threading tests --- .../profiling_v2/collector/test_threading.py | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 11bfc1acfce..22b89d93379 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -111,7 +111,8 @@ def test_patch( @pytest.mark.subprocess( - env=dict(WRAPT_DISABLE_EXTENSIONS="True", DD_PROFILING_FILE_PATH=__file__), + env=dict(WRAPT_DISABLE_EXTENSIONS="True", DD_PROFILING_FILE_PATH=__file__, DD_PROFILING_ENABLED="1"), + ddtrace_run=True, ) def test_wrapt_disable_extensions() -> None: import os @@ -132,9 +133,7 @@ def test_wrapt_disable_extensions() -> None: test_name: str = "test_wrapt_disable_extensions" pprof_prefix: str = "/tmp" + os.sep + test_name output_filename: str = pprof_prefix + "." + str(os.getpid()) - ddup.config( - env="test", service=test_name, version="my_version", output_filename=pprof_prefix - ) # pyright: ignore[reportCallIssue] + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) # pyright: ignore[reportCallIssue] ddup.start() init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) @@ -205,9 +204,7 @@ def test_lock_gevent_tasks() -> None: test_name: str = "test_lock_gevent_tasks" pprof_prefix: str = "/tmp" + os.sep + test_name output_filename: str = pprof_prefix + "." + str(os.getpid()) - ddup.config( - env="test", service=test_name, version="my_version", output_filename=pprof_prefix - ) # pyright: ignore[reportCallIssue] + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) # pyright: ignore[reportCallIssue] ddup.start() init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) @@ -223,9 +220,7 @@ def validate_and_cleanup() -> None: expected_filename: str = "test_threading.py" linenos: LineNo = get_lock_linenos(test_name) - profile: pprof_pb2.Profile = pprof_utils.parse_newest_profile( - output_filename - ) # pyright: ignore[reportInvalidTypeForm] + profile: pprof_pb2.Profile = pprof_utils.parse_newest_profile(output_filename) # pyright: ignore[reportInvalidTypeForm] pprof_utils.assert_lock_events( profile, expected_acquire_events=[ @@ -297,9 +292,7 @@ def test_rlock_gevent_tasks() -> None: test_name: str = "test_rlock_gevent_tasks" pprof_prefix: str = "/tmp" + os.sep + test_name output_filename: str = pprof_prefix + "." + str(os.getpid()) - ddup.config( - env="test", service=test_name, version="my_version", output_filename=pprof_prefix - ) # pyright: ignore[reportCallIssue] + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) # pyright: ignore[reportCallIssue] ddup.start() init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) @@ -360,7 +353,7 @@ def validate_and_cleanup() -> None: validate_and_cleanup() -@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="true")) +@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="true", DD_PROFILING_ENABLED="1"), ddtrace_run=True) def test_assertion_error_raised_with_enable_asserts(): """Ensure that AssertionError is propagated when config.enable_asserts=True.""" import threading @@ -387,7 +380,7 @@ def test_assertion_error_raised_with_enable_asserts(): lock.acquire() -@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="false")) +@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="false", DD_PROFILING_ENABLED="1"), ddtrace_run=True) def test_all_exceptions_suppressed_by_default() -> None: """ Ensure that exceptions are silently suppressed in the `_acquire` method @@ -446,9 +439,7 @@ def setup_method(self, method: Callable[..., None]) -> None: # ddup is available when the native module is compiled assert ddup.is_available, "ddup is not available" - ddup.config( - env="test", service=self.test_name, version="my_version", output_filename=self.pprof_prefix - ) # pyright: ignore[reportCallIssue] + ddup.config(env="test", service=self.test_name, version="my_version", output_filename=self.pprof_prefix) # pyright: ignore[reportCallIssue] ddup.start() def teardown_method(self, method: Callable[..., None]) -> None: From d227c6ae1cb0df1ef15a1d0e5d878e5a2f5a4520 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 6 Nov 2025 22:07:45 -0500 Subject: [PATCH 05/12] address linting errors --- ddtrace/internal/_unpatched.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py index 58ef7797518..2a643c414c6 100644 --- a/ddtrace/internal/_unpatched.py +++ b/ddtrace/internal/_unpatched.py @@ -13,8 +13,11 @@ import sys import threading # noqa -_threading = sys.modules.pop("threading", None) -_gc = sys.modules.pop("gc", None) +_threading = threading +_gc = gc + +sys.modules.pop("threading", None) +sys.modules.pop("gc", None) previous_loaded_modules = frozenset(sys.modules.keys()) from subprocess import Popen as unpatched_Popen # noqa # nosec B404 From 4b02c9d57649a07b152b2705b65442e8a11e05e3 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 6 Nov 2025 22:11:16 -0500 Subject: [PATCH 06/12] format --- .../profiling_v2/collector/test_threading.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 22b89d93379..5bfaaabb13b 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -133,7 +133,9 @@ def test_wrapt_disable_extensions() -> None: test_name: str = "test_wrapt_disable_extensions" pprof_prefix: str = "/tmp" + os.sep + test_name output_filename: str = pprof_prefix + "." + str(os.getpid()) - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) # pyright: ignore[reportCallIssue] + ddup.config( + env="test", service=test_name, version="my_version", output_filename=pprof_prefix + ) # pyright: ignore[reportCallIssue] ddup.start() init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) @@ -204,7 +206,9 @@ def test_lock_gevent_tasks() -> None: test_name: str = "test_lock_gevent_tasks" pprof_prefix: str = "/tmp" + os.sep + test_name output_filename: str = pprof_prefix + "." + str(os.getpid()) - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) # pyright: ignore[reportCallIssue] + ddup.config( + env="test", service=test_name, version="my_version", output_filename=pprof_prefix + ) # pyright: ignore[reportCallIssue] ddup.start() init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) @@ -220,7 +224,9 @@ def validate_and_cleanup() -> None: expected_filename: str = "test_threading.py" linenos: LineNo = get_lock_linenos(test_name) - profile: pprof_pb2.Profile = pprof_utils.parse_newest_profile(output_filename) # pyright: ignore[reportInvalidTypeForm] + profile: pprof_pb2.Profile = pprof_utils.parse_newest_profile( + output_filename + ) # pyright: ignore[reportInvalidTypeForm] pprof_utils.assert_lock_events( profile, expected_acquire_events=[ @@ -292,7 +298,9 @@ def test_rlock_gevent_tasks() -> None: test_name: str = "test_rlock_gevent_tasks" pprof_prefix: str = "/tmp" + os.sep + test_name output_filename: str = pprof_prefix + "." + str(os.getpid()) - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) # pyright: ignore[reportCallIssue] + ddup.config( + env="test", service=test_name, version="my_version", output_filename=pprof_prefix + ) # pyright: ignore[reportCallIssue] ddup.start() init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) @@ -439,7 +447,9 @@ def setup_method(self, method: Callable[..., None]) -> None: # ddup is available when the native module is compiled assert ddup.is_available, "ddup is not available" - ddup.config(env="test", service=self.test_name, version="my_version", output_filename=self.pprof_prefix) # pyright: ignore[reportCallIssue] + ddup.config( + env="test", service=self.test_name, version="my_version", output_filename=self.pprof_prefix + ) # pyright: ignore[reportCallIssue] ddup.start() def teardown_method(self, method: Callable[..., None]) -> None: From 224e897b5715d1fd657ca489981690f92688099a Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 6 Nov 2025 23:32:10 -0500 Subject: [PATCH 07/12] update --- ddtrace/internal/_unpatched.py | 20 +++++++------------ .../profiling_v2/collector/test_threading.py | 7 +++---- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py index 2a643c414c6..8e99656c0ea 100644 --- a/ddtrace/internal/_unpatched.py +++ b/ddtrace/internal/_unpatched.py @@ -3,21 +3,15 @@ from builtins import open as unpatched_open # noqa from json import loads as unpatched_json_loads # noqa -# Acquire references to threading and gc, then remove them from sys.modules. -# Some parts of the library (e.g. the profiler) might be enabled programmatically -# and patch the threading and gc modules. We store references to the original -# modules here to allow ddtrace internal modules to access the right versions of -# the modules. +# Acquire a reference to the threading module. Some parts of the library (e.g. +# the profiler) might be enabled programmatically and therefore might end up +# getting a reference to the tracee's threading module. By storing a reference +# to the threading module used by ddtrace here, we make it easy for those parts +# to get a reference to the right threading module. +import threading as _threading # noqa +import gc as _gc # noqa -import gc # noqa import sys -import threading # noqa - -_threading = threading -_gc = gc - -sys.modules.pop("threading", None) -sys.modules.pop("gc", None) previous_loaded_modules = frozenset(sys.modules.keys()) from subprocess import Popen as unpatched_Popen # noqa # nosec B404 diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 5bfaaabb13b..11bfc1acfce 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -111,8 +111,7 @@ def test_patch( @pytest.mark.subprocess( - env=dict(WRAPT_DISABLE_EXTENSIONS="True", DD_PROFILING_FILE_PATH=__file__, DD_PROFILING_ENABLED="1"), - ddtrace_run=True, + env=dict(WRAPT_DISABLE_EXTENSIONS="True", DD_PROFILING_FILE_PATH=__file__), ) def test_wrapt_disable_extensions() -> None: import os @@ -361,7 +360,7 @@ def validate_and_cleanup() -> None: validate_and_cleanup() -@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="true", DD_PROFILING_ENABLED="1"), ddtrace_run=True) +@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="true")) def test_assertion_error_raised_with_enable_asserts(): """Ensure that AssertionError is propagated when config.enable_asserts=True.""" import threading @@ -388,7 +387,7 @@ def test_assertion_error_raised_with_enable_asserts(): lock.acquire() -@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="false", DD_PROFILING_ENABLED="1"), ddtrace_run=True) +@pytest.mark.subprocess(env=dict(DD_PROFILING_ENABLE_ASSERTS="false")) def test_all_exceptions_suppressed_by_default() -> None: """ Ensure that exceptions are silently suppressed in the `_acquire` method From 1c0bada0a5986de5770f3f3e3788a635400928e0 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 6 Nov 2025 23:41:43 -0500 Subject: [PATCH 08/12] explicitly take references to classes --- ddtrace/internal/_unpatched.py | 6 +++++- ddtrace/internal/forksafe.py | 16 +++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py index 8e99656c0ea..5923cd4884e 100644 --- a/ddtrace/internal/_unpatched.py +++ b/ddtrace/internal/_unpatched.py @@ -10,9 +10,13 @@ # to get a reference to the right threading module. import threading as _threading # noqa import gc as _gc # noqa - import sys +_Lock = _threading.Lock +_RLock = _threading.RLock +_Event = _threading.Event + + previous_loaded_modules = frozenset(sys.modules.keys()) from subprocess import Popen as unpatched_Popen # noqa # nosec B404 from os import close as unpatched_close # noqa: F401, E402 diff --git a/ddtrace/internal/forksafe.py b/ddtrace/internal/forksafe.py index 87f1d74ab61..9391e75736f 100644 --- a/ddtrace/internal/forksafe.py +++ b/ddtrace/internal/forksafe.py @@ -10,7 +10,9 @@ import wrapt -from ddtrace.internal._unpatched import _threading as unpatched_threading +from ddtrace.internal._unpatched import _Event +from ddtrace.internal._unpatched import _Lock +from ddtrace.internal._unpatched import _RLock log = logging.getLogger(__name__) @@ -139,13 +141,13 @@ def _reset_object(self): self.__wrapped__ = self._self_wrapped_class() -def Lock() -> unpatched_threading.Lock: - return ResetObject(unpatched_threading.Lock) # type: ignore +def Lock() -> _Lock: + return ResetObject(_Lock) # type: ignore -def RLock() -> unpatched_threading.RLock: - return ResetObject(unpatched_threading.RLock) # type: ignore +def RLock() -> _RLock: + return ResetObject(_RLock) # type: ignore -def Event() -> unpatched_threading.Event: - return ResetObject(unpatched_threading.Event) # type: ignore +def Event() -> _Event: + return ResetObject(_Event) # type: ignore From 773666f0a46a498bf2bbcc8bbefb401f57295709 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 7 Nov 2025 00:31:15 -0500 Subject: [PATCH 09/12] update test for 3.14 --- tests/internal/test_forksafe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/internal/test_forksafe.py b/tests/internal/test_forksafe.py index 3ee58173f84..05fe8d1fcba 100644 --- a/tests/internal/test_forksafe.py +++ b/tests/internal/test_forksafe.py @@ -1,5 +1,6 @@ from collections import Counter import os +import sys import pytest @@ -204,6 +205,7 @@ def test_lock_fork(): assert exit_code == 12 +@pytest.mark.skipif(sys.version_info < (3, 14), reason="Profiling is not supported on Python 3.14 yet") @pytest.mark.subprocess( env=dict(DD_PROFILING_ENABLED="1"), ddtrace_run=True, From a71620816ea87562659686d3453a5b15d4b42c5f Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 7 Nov 2025 01:12:07 -0500 Subject: [PATCH 10/12] 3.14 --- tests/internal/test_forksafe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/internal/test_forksafe.py b/tests/internal/test_forksafe.py index 05fe8d1fcba..cd911c520bb 100644 --- a/tests/internal/test_forksafe.py +++ b/tests/internal/test_forksafe.py @@ -205,7 +205,7 @@ def test_lock_fork(): assert exit_code == 12 -@pytest.mark.skipif(sys.version_info < (3, 14), reason="Profiling is not supported on Python 3.14 yet") +@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Profiling is not supported on Python 3.14 yet") @pytest.mark.subprocess( env=dict(DD_PROFILING_ENABLED="1"), ddtrace_run=True, From ef260f55b6bf319889565f7a1e18272042f8684c Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 7 Nov 2025 12:03:17 -0500 Subject: [PATCH 11/12] update --- ddtrace/internal/_unpatched.py | 6 +++--- ddtrace/internal/forksafe.py | 16 +++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py index 5923cd4884e..b885ec45178 100644 --- a/ddtrace/internal/_unpatched.py +++ b/ddtrace/internal/_unpatched.py @@ -12,9 +12,9 @@ import gc as _gc # noqa import sys -_Lock = _threading.Lock -_RLock = _threading.RLock -_Event = _threading.Event +threading_Lock = _threading.Lock +threading_RLock = _threading.RLock +threading_Event = _threading.Event previous_loaded_modules = frozenset(sys.modules.keys()) diff --git a/ddtrace/internal/forksafe.py b/ddtrace/internal/forksafe.py index 9391e75736f..4c66fd69c10 100644 --- a/ddtrace/internal/forksafe.py +++ b/ddtrace/internal/forksafe.py @@ -10,9 +10,7 @@ import wrapt -from ddtrace.internal._unpatched import _Event -from ddtrace.internal._unpatched import _Lock -from ddtrace.internal._unpatched import _RLock +from ddtrace.internal import _unpatched log = logging.getLogger(__name__) @@ -141,13 +139,13 @@ def _reset_object(self): self.__wrapped__ = self._self_wrapped_class() -def Lock() -> _Lock: - return ResetObject(_Lock) # type: ignore +def Lock() -> _unpatched.threading_Lock: + return ResetObject(_unpatched._threading_Lock) # type: ignore -def RLock() -> _RLock: - return ResetObject(_RLock) # type: ignore +def RLock() -> _unpatched.threading_RLock: + return ResetObject(_unpatched._threading_RLock) # type: ignore -def Event() -> _Event: - return ResetObject(_Event) # type: ignore +def Event() -> _unpatched.threading_Event: + return ResetObject(_unpatched._threading_Event) # type: ignore From f7a91676e153d75242fb9c57309901d30453f553 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 7 Nov 2025 12:44:44 -0500 Subject: [PATCH 12/12] typo --- ddtrace/internal/forksafe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/internal/forksafe.py b/ddtrace/internal/forksafe.py index 4c66fd69c10..63ff4ef07e5 100644 --- a/ddtrace/internal/forksafe.py +++ b/ddtrace/internal/forksafe.py @@ -140,12 +140,12 @@ def _reset_object(self): def Lock() -> _unpatched.threading_Lock: - return ResetObject(_unpatched._threading_Lock) # type: ignore + return ResetObject(_unpatched.threading_Lock) # type: ignore def RLock() -> _unpatched.threading_RLock: - return ResetObject(_unpatched._threading_RLock) # type: ignore + return ResetObject(_unpatched.threading_RLock) # type: ignore def Event() -> _unpatched.threading_Event: - return ResetObject(_unpatched._threading_Event) # type: ignore + return ResetObject(_unpatched.threading_Event) # type: ignore