|
31 | 31 | logger = logging.getLogger(__name__) |
32 | 32 |
|
33 | 33 |
|
| 34 | +class DebugLock: |
| 35 | + """A wrapper around threading.Lock that provides detailed debugging for lock acquisition/release""" |
| 36 | + def __init__(self, name: str = "DebugLock"): |
| 37 | + self._lock = threading.Lock() |
| 38 | + self._name = name |
| 39 | + self._owner = None |
| 40 | + self._waiters = [] |
| 41 | + self._debug_logger = logging.getLogger(f"{__name__}.{name}") |
| 42 | + # Ensure debug logging is visible |
| 43 | + if not self._debug_logger.handlers: |
| 44 | + handler = logging.StreamHandler() |
| 45 | + formatter = logging.Formatter( |
| 46 | + ":lock: %(asctime)s [%(threadName)s-%(thread)d] LOCK-%(name)s: %(message)s" |
| 47 | + ) |
| 48 | + handler.setFormatter(formatter) |
| 49 | + self._debug_logger.addHandler(handler) |
| 50 | + self._debug_logger.setLevel(logging.DEBUG) |
| 51 | + def acquire(self, blocking=True, timeout=-1): |
| 52 | + current = threading.current_thread() |
| 53 | + thread_info = f"{current.name}-{current.ident}" |
| 54 | + if self._owner: |
| 55 | + self._debug_logger.warning( |
| 56 | + f":rotating_light: WAITING: {thread_info} waiting for lock held by {self._owner}" |
| 57 | + ) |
| 58 | + self._waiters.append(thread_info) |
| 59 | + else: |
| 60 | + self._debug_logger.debug( |
| 61 | + f":large_green_circle: TRYING: {thread_info} attempting to acquire lock" |
| 62 | + ) |
| 63 | + # Try to acquire the lock |
| 64 | + acquired = self._lock.acquire(blocking, timeout) |
| 65 | + if acquired: |
| 66 | + self._owner = thread_info |
| 67 | + self._debug_logger.info(f":white_check_mark: ACQUIRED: {thread_info} got the lock") |
| 68 | + if self._waiters: |
| 69 | + self._debug_logger.info( |
| 70 | + f":clipboard: WAITERS: {len(self._waiters)} threads waiting: {self._waiters}" |
| 71 | + ) |
| 72 | + else: |
| 73 | + self._debug_logger.error( |
| 74 | + f":x: FAILED: {thread_info} failed to acquire lock (timeout)" |
| 75 | + ) |
| 76 | + if thread_info in self._waiters: |
| 77 | + self._waiters.remove(thread_info) |
| 78 | + return acquired |
| 79 | + def release(self): |
| 80 | + current = threading.current_thread() |
| 81 | + thread_info = f"{current.name}-{current.ident}" |
| 82 | + if self._owner != thread_info: |
| 83 | + self._debug_logger.error( |
| 84 | + f":rotating_light: ERROR: {thread_info} trying to release lock owned by {self._owner}" |
| 85 | + ) |
| 86 | + else: |
| 87 | + self._debug_logger.info(f":unlock: RELEASED: {thread_info} released the lock") |
| 88 | + self._owner = None |
| 89 | + # Remove from waiters if present |
| 90 | + if thread_info in self._waiters: |
| 91 | + self._waiters.remove(thread_info) |
| 92 | + if self._waiters: |
| 93 | + self._debug_logger.info( |
| 94 | + f":loudspeaker: NEXT: {len(self._waiters)} threads still waiting: {self._waiters}" |
| 95 | + ) |
| 96 | + self._lock.release() |
| 97 | + def __enter__(self): |
| 98 | + self.acquire() |
| 99 | + return self |
| 100 | + def __exit__(self, exc_type, exc_val, exc_tb): |
| 101 | + self.release() |
| 102 | + |
| 103 | + |
34 | 104 | class TelemetryHelper: |
35 | 105 | """Helper class for getting telemetry related information.""" |
36 | 106 |
|
@@ -355,7 +425,8 @@ class TelemetryClientFactory: |
355 | 425 | ] = {} # Map of session_id_hex -> BaseTelemetryClient |
356 | 426 | _executor: Optional[ThreadPoolExecutor] = None |
357 | 427 | _initialized: bool = False |
358 | | - _lock = threading.Lock() # Thread safety for factory operations |
| 428 | + # _lock = threading.Lock() # Thread safety for factory operations |
| 429 | + _lock = DebugLock("TelemetryClientFactory") # Thread safety for factory operations with debugging |
359 | 430 | _original_excepthook = None |
360 | 431 | _excepthook_installed = False |
361 | 432 |
|
|
0 commit comments