Skip to content

Commit 634cec5

Browse files
codebotenxrmx
andauthored
opentelemetry-sdk: revert RLock back to Lock (#5329)
* opentelemetry-sdk: revert RLock back to Lock With the changes in clean_attributes, the original issue that required the reentrant lock in the first place is no longer an issue. Added a test that was run with the old code to validate that the regression does not occur. Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> * changelog Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> * Update .changelog/5329.changed Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> * fix lint Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> * remove old test Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --------- Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent d086833 commit 634cec5

3 files changed

Lines changed: 42 additions & 11 deletions

File tree

.changelog/5329.changed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
opentelemetry-sdk: revert BoundedAttributes RLock back to Lock

opentelemetry-api/src/opentelemetry/attributes/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def __init__(
262262
MutableMapping[str, types.AnyValue]
263263
| OrderedDict[str, types.AnyValue]
264264
) = {}
265-
self._lock = threading.RLock()
265+
self._lock = threading.Lock()
266266
if attributes:
267267
for key, value in attributes.items():
268268
self[key] = value

opentelemetry-api/tests/attributes/test_attributes.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
# type: ignore
55

66
import copy
7+
import logging
8+
import threading
79
import unittest
810
import unittest.mock
911
from collections.abc import MutableSequence
@@ -269,19 +271,47 @@ def test_immutable(self):
269271
with self.assertRaises(TypeError):
270272
bdict["should-not-work"] = "dict immutable"
271273

272-
def test_locking(self):
273-
"""Supporting test case for a commit titled: Fix class BoundedAttributes to have RLock rather than Lock. See #3858.
274-
The change was introduced because __iter__ of the class BoundedAttributes holds lock, and we observed some deadlock symptoms
275-
in the codebase. This test case is to verify that the fix works as expected.
274+
def test_no_deadlock_on_reentrant_logging(self):
275+
"""Regression test for #3858.
276+
277+
The deadlock scenario: a logging handler intercepts the warning
278+
emitted by _clean_attribute for an invalid value and calls __setitem__
279+
on the same BoundedAttributes instance from the same thread.
280+
With _clean_attribute called inside the lock this caused a deadlock.
281+
With _clean_attribute called before the lock is acquired, no deadlock
282+
occurs.
276283
"""
277284
bdict = BoundedAttributes(immutable=False)
278285

279-
with bdict._lock: # pylint: disable=protected-access
280-
for num in range(100):
281-
bdict[str(num)] = num
282-
283-
for num in range(100):
284-
self.assertEqual(bdict[str(num)], num)
286+
class ReentrantHandler(logging.Handler):
287+
def emit(self, _record):
288+
# Simulates Sentry intercepting the OTel warning and writing
289+
# back into the same BoundedAttributes on the same thread.
290+
bdict["reentrant.key"] = "set_by_handler"
291+
292+
otel_logger = logging.getLogger("opentelemetry.attributes")
293+
handler = ReentrantHandler()
294+
otel_logger.addHandler(handler)
295+
try:
296+
completed = threading.Event()
297+
298+
def run():
299+
# None is an invalid attribute value and triggers _logger.warning
300+
# in _clean_attribute, which fires the ReentrantHandler above.
301+
bdict["trigger.key"] = None
302+
completed.set()
303+
304+
thread = threading.Thread(target=run, daemon=True)
305+
thread.start()
306+
thread.join(timeout=2.0)
307+
308+
self.assertTrue(
309+
completed.is_set(),
310+
"Deadlock detected: __setitem__ did not complete within 2s",
311+
)
312+
self.assertEqual(bdict.get("reentrant.key"), "set_by_handler")
313+
finally:
314+
otel_logger.removeHandler(handler)
285315

286316
# pylint: disable=no-self-use
287317
def test_extended_attributes(self):

0 commit comments

Comments
 (0)