From 825e9438c600ca3ca05e939c49e3813a650d1476 Mon Sep 17 00:00:00 2001 From: Windsor Date: Tue, 13 Jan 2026 23:57:50 -0800 Subject: [PATCH 1/3] fix: guard debug stack walk --- uvloop/cbhandles.pyx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/uvloop/cbhandles.pyx b/uvloop/cbhandles.pyx index 2914b42e..a42c539f 100644 --- a/uvloop/cbhandles.pyx +++ b/uvloop/cbhandles.pyx @@ -424,7 +424,16 @@ cdef extract_stack(): return try: - stack = tb_StackSummary.extract(tb_walk_stack(f), + frames = [] + while f is not None: + lineno = getattr(f, "f_lineno", None) + if lineno is None: + break + if getattr(f, "f_code", None) is None: + break + frames.append((f, lineno)) + f = getattr(f, "f_back", None) + stack = tb_StackSummary.extract(frames, limit=DEBUG_STACK_DEPTH, lookup_lines=False) finally: From 68af9b3984a1ebb378ab68bafeb71c9bb9794b0c Mon Sep 17 00:00:00 2001 From: Windsor Date: Wed, 14 Jan 2026 00:26:30 -0800 Subject: [PATCH 2/3] fix(debug): guard stack extraction --- uvloop/cbhandles.pyx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/uvloop/cbhandles.pyx b/uvloop/cbhandles.pyx index a42c539f..59df1bd1 100644 --- a/uvloop/cbhandles.pyx +++ b/uvloop/cbhandles.pyx @@ -423,19 +423,14 @@ cdef extract_stack(): if f is None: return + if getattr(f, "f_lineno", None) is None or getattr(f, "f_code", None) is None: + return None try: - frames = [] - while f is not None: - lineno = getattr(f, "f_lineno", None) - if lineno is None: - break - if getattr(f, "f_code", None) is None: - break - frames.append((f, lineno)) - f = getattr(f, "f_back", None) - stack = tb_StackSummary.extract(frames, + stack = tb_StackSummary.extract(tb_walk_stack(f), limit=DEBUG_STACK_DEPTH, lookup_lines=False) + except (AttributeError, TypeError): + return None finally: f = None From f9754dacee49a82e7b9b958292201fd81c49618c Mon Sep 17 00:00:00 2001 From: Windsor Date: Wed, 14 Jan 2026 00:40:26 -0800 Subject: [PATCH 3/3] fix(debug): lock cb handle counters --- uvloop/cbhandles.pyx | 22 +++++++++++++++++++--- uvloop/loop.pyx | 7 +++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/uvloop/cbhandles.pyx b/uvloop/cbhandles.pyx index 59df1bd1..ad18fe94 100644 --- a/uvloop/cbhandles.pyx +++ b/uvloop/cbhandles.pyx @@ -1,3 +1,20 @@ +cdef inline void _debug_cb_handles_inc(Loop loop): + if _debug_cb_handles_lock != NULL: + PyThread_acquire_lock(_debug_cb_handles_lock, 1) + loop._debug_cb_handles_total += 1 + loop._debug_cb_handles_count += 1 + if _debug_cb_handles_lock != NULL: + PyThread_release_lock(_debug_cb_handles_lock) + + +cdef inline void _debug_cb_handles_dec(Loop loop): + if _debug_cb_handles_lock != NULL: + PyThread_acquire_lock(_debug_cb_handles_lock, 1) + loop._debug_cb_handles_count -= 1 + if _debug_cb_handles_lock != NULL: + PyThread_release_lock(_debug_cb_handles_lock) + + @cython.no_gc_clear @cython.freelist(DEFAULT_FREELIST_SIZE) cdef class Handle: @@ -9,8 +26,7 @@ cdef class Handle: cdef inline _set_loop(self, Loop loop): self.loop = loop if UVLOOP_DEBUG: - loop._debug_cb_handles_total += 1 - loop._debug_cb_handles_count += 1 + _debug_cb_handles_inc(loop) if loop._debug: self._source_traceback = extract_stack() @@ -21,7 +37,7 @@ cdef class Handle: def __dealloc__(self): if UVLOOP_DEBUG and self.loop is not None: - self.loop._debug_cb_handles_count -= 1 + _debug_cb_handles_dec(self.loop) if self.loop is None: raise RuntimeError('Handle.loop is None in Handle.__dealloc__') diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index 2ed1f272..4eb1495c 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -31,6 +31,12 @@ from libc cimport errno from cpython cimport PyObject from cpython cimport PyErr_CheckSignals, PyErr_Occurred from cpython cimport PyThread_get_thread_ident +from cpython.pythread cimport ( + PyThread_type_lock, + PyThread_allocate_lock, + PyThread_acquire_lock, + PyThread_release_lock, +) from cpython cimport Py_INCREF, Py_DECREF, Py_XDECREF, Py_XINCREF from cpython cimport ( PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, @@ -52,6 +58,7 @@ cdef: int PY311 = PY_VERSION_HEX >= 0x030b0000 int PY313 = PY_VERSION_HEX >= 0x030d0000 uint64_t MAX_SLEEP = 3600 * 24 * 365 * 100 + PyThread_type_lock _debug_cb_handles_lock = PyThread_allocate_lock() cdef _is_sock_stream(sock_type):