Skip to content

Commit 43e92af

Browse files
Fix silent suppression of AssertionError exceptions in _ProfiledLock::_acquire
1 parent d5872b9 commit 43e92af

File tree

2 files changed

+43
-16
lines changed

2 files changed

+43
-16
lines changed

ddtrace/profiling/collector/_lock.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,41 +83,36 @@ def _acquire(self, inner_func: Callable[..., Any], *args: Any, **kwargs: Any) ->
8383
try:
8484
return inner_func(*args, **kwargs)
8585
finally:
86-
try:
87-
end: int = time.monotonic_ns()
88-
self._self_acquired_at = end
86+
end: int = time.monotonic_ns()
87+
# Set acquired_at immediately so release() can match, even if profiling fails below
88+
self._self_acquired_at = end
8989

90-
thread_id: int
91-
thread_name: str
90+
# All profiling operations below can throw - wrap them tightly
91+
try:
9292
thread_id, thread_name = _current_thread()
93-
94-
task_id: Optional[int]
95-
task_name: Optional[str]
96-
task_frame: Optional[FrameType]
9793
task_id, task_name, task_frame = _task.get_task(thread_id)
9894

9995
self._maybe_update_self_name()
10096
lock_name: str = (
10197
"%s:%s" % (self._self_init_loc, self._self_name) if self._self_name else self._self_init_loc
10298
)
10399

104-
frame: FrameType
100+
# Get the frame for traceback
105101
if task_frame is None:
106102
# If we can't get the task frame, we use the caller frame. We expect acquire/release or
107103
# __enter__/__exit__ to be on the stack, so we go back 2 frames.
108104
frame = sys._getframe(2)
109105
else:
110106
frame = task_frame
111107

112-
frames: List[DDFrame]
113108
frames, _ = _traceback.pyframe_to_frames(frame, self._self_max_nframes)
109+
thread_native_id = _threading.get_thread_native_id(thread_id)
114110

115-
thread_native_id: int = _threading.get_thread_native_id(thread_id)
116-
111+
# Push sample to profiling backend
117112
handle: ddup.SampleHandle = ddup.SampleHandle()
118113
handle.push_monotonic_ns(end)
119114
handle.push_lock_name(lock_name)
120-
handle.push_acquire(end - start, 1) # AFAICT, capture_pct does not adjust anything here
115+
handle.push_acquire(end - start, 1)
121116
handle.push_threadinfo(thread_id, thread_native_id, thread_name)
122117
handle.push_task_id(task_id)
123118
handle.push_task_name(task_name)
@@ -127,8 +122,10 @@ def _acquire(self, inner_func: Callable[..., Any], *args: Any, **kwargs: Any) ->
127122
for ddframe in frames:
128123
handle.push_frame(ddframe.function_name, ddframe.file_name, 0, ddframe.lineno)
129124
handle.flush_sample()
130-
except Exception:
131-
pass # nosec
125+
except Exception as e:
126+
# _maybe_update_self_name throws AssertionError exceptions which need to propagate
127+
if type(e) is AssertionError:
128+
raise e
132129

133130
def acquire(self, *args: Any, **kwargs: Any) -> Any:
134131
return self._acquire(self.__wrapped__.acquire, *args, **kwargs)

tests/profiling_v2/collector/test_threading.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,36 @@ def test_upload_resets_profile(self) -> None:
979979
with pytest.raises(AssertionError):
980980
pprof_utils.parse_newest_profile(self.output_filename)
981981

982+
def test_assertion_error_suppressed_in_acquire(self) -> None:
983+
""" Ensure that AssertionError exceptions is propagated in the `_acquire` method. """
984+
985+
with self.collector_class(capture_pct=100):
986+
lock = self.lock_class()
987+
988+
# Patch _maybe_update_self_name to raise AssertionError
989+
lock._maybe_update_self_name = mock.Mock(
990+
side_effect=AssertionError("test: unexpected frame in stack")
991+
)
992+
993+
with pytest.raises(AssertionError):
994+
# acquire() will propagate AssertionError if _maybe_update_self_name raises it
995+
lock.acquire()
996+
997+
def test_other_exceptions_are_suppressed(self) -> None:
998+
""" Ensure that non - AssertionError exceptions are silently suppressed in the `_acquire` method. """
999+
1000+
with self.collector_class(capture_pct=100):
1001+
lock = self.lock_class()
1002+
1003+
# Patch _maybe_update_self_name to raise RuntimeError
1004+
lock._maybe_update_self_name = mock.Mock(
1005+
side_effect=RuntimeError("Some profiling error")
1006+
)
1007+
1008+
# RuntimeError should be caught and suppressed - lock operations should succeed
1009+
lock.acquire()
1010+
lock.release()
1011+
9821012

9831013
class TestThreadingLockCollector(BaseThreadingLockCollectorTest):
9841014
"""Test Lock profiling"""

0 commit comments

Comments
 (0)