Skip to content

Commit

Permalink
Add deadlock detection for ValueClass-es
Browse files Browse the repository at this point in the history
Signed-off-by: Przemysław Suliga <mail@suligap.net>
  • Loading branch information
suligap committed Dec 1, 2024
1 parent eb874cf commit a19124d
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 5 deletions.
30 changes: 25 additions & 5 deletions prometheus_client/values.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import os
from threading import Lock
from threading import Lock, RLock
import warnings

from .mmap_dict import mmap_key, MmapedDict


class _WarningRLock:
"""A wrapper around threading.RLock that detects possible deadlocks.
Raises a RuntimeError when it detects attempts to re-enter the critical
section from a single thread.
"""

def __init__(self):
self._rlock = RLock()
self._lock = Lock()

def __enter__(self):
self._rlock.acquire()
if not self._lock.acquire(blocking=False):
raise RuntimeError('Attempt to enter a non reentrant context from a single thread.')

def __exit__(self, exc_type, exc_value, traceback):
self._lock.release()
self._rlock.release()


class MutexValue:
"""A float protected by a mutex."""

Expand All @@ -13,7 +34,7 @@ class MutexValue:
def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, **kwargs):
self._value = 0.0
self._exemplar = None
self._lock = Lock()
self._lock = _WarningRLock()

def inc(self, amount):
with self._lock:
Expand Down Expand Up @@ -47,10 +68,9 @@ def MultiProcessValue(process_identifier=os.getpid):
files = {}
values = []
pid = {'value': process_identifier()}
# Use a single global lock when in multi-processing mode
# as we presume this means there is no threading going on.
# Use a single global lock when in multi-processing mode.
# This avoids the need to also have mutexes in __MmapDict.
lock = Lock()
lock = _WarningRLock()

class MmapedValue:
"""A float protected by a mutex backed by a per-process mmaped file."""
Expand Down
11 changes: 11 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ def test_exemplar_too_long(self):
'y123456': '7+15 characters',
})

def test_single_thread_deadlock_detection(self):
counter = self.counter

class Tracked(float):
def __radd__(self, other):
counter.inc(10)
return self + other

expected_msg = 'Attempt to enter a non reentrant context from a single thread.'
self.assertRaisesRegex(RuntimeError, expected_msg, counter.inc, Tracked(100))


class TestDisableCreated(unittest.TestCase):
def setUp(self):
Expand Down

0 comments on commit a19124d

Please sign in to comment.