From 1a618950f590a8233cfac116aac6882cd6674354 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 21 Feb 2026 18:29:42 -0400 Subject: [PATCH 1/2] Fix segmentation fault in libev prepare_callback during shutdown This commit fixes a race condition that causes segmentation faults when the Python driver shuts down while libev callbacks are still executing. Changes: 1. Add null checks in prepare_callback() to handle destroyed objects 2. Stop prepare watcher before cleanup to prevent race conditions 3. Increase thread join timeout to allow proper shutdown synchronization The issue occurred when metrics cleanup during shutdown raced with libev callbacks executing in background threads, causing access to freed Python objects. Fixes crash at address 0x2f998 in prepare_callback accessing destroyed libevwrapper_Prepare objects. --- cassandra/io/libevreactor.py | 7 ++++++- cassandra/io/libevwrapper.c | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cassandra/io/libevreactor.py b/cassandra/io/libevreactor.py index d7b365e451..c3f8f967ee 100644 --- a/cassandra/io/libevreactor.py +++ b/cassandra/io/libevreactor.py @@ -116,6 +116,10 @@ def _cleanup(self): if not self._thread: return + # Stop the prepare watcher first to prevent race conditions + if self._preparer: + self._preparer.stop() + for conn in self._live_conns | self._new_conns | self._closed_conns: conn.close() for watcher in (conn._write_watcher, conn._read_watcher): @@ -125,8 +129,9 @@ def _cleanup(self): self.notify() # wake the timer watcher # PYTHON-752 Thread might have just been created and not started + # Use longer timeout to allow proper cleanup with self._lock_thread: - self._thread.join(timeout=1.0) + self._thread.join(timeout=5.0) if self._thread.is_alive(): log.warning( diff --git a/cassandra/io/libevwrapper.c b/cassandra/io/libevwrapper.c index f32504fa34..95a3857bf4 100644 --- a/cassandra/io/libevwrapper.c +++ b/cassandra/io/libevwrapper.c @@ -354,6 +354,10 @@ static void prepare_callback(struct ev_loop *loop, ev_prepare *watcher, int reve PyObject *result = NULL; PyGILState_STATE gstate; + if (!self || !self->callback) { + return; // Skip callback if object is being destroyed + } + gstate = PyGILState_Ensure(); result = PyObject_CallFunction(self->callback, "O", self); if (!result) { From f4b0387e992d404af56bff1d06bc8ba1a08b05ad Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 21 Feb 2026 18:46:19 -0400 Subject: [PATCH 2/2] Add null checks to io_callback and timer_callback in libev wrapper This extends the segmentation fault fix to cover all libev callbacks that access Python objects during shutdown. Changes: 1. Add null checks in io_callback() before accessing self->callback 2. Add null checks in timer_callback() before accessing self->callback These prevent race conditions similar to the prepare_callback issue where libev callbacks could execute after Python objects are destroyed during driver shutdown. --- cassandra/io/libevwrapper.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cassandra/io/libevwrapper.c b/cassandra/io/libevwrapper.c index 95a3857bf4..fc25f9ceba 100644 --- a/cassandra/io/libevwrapper.c +++ b/cassandra/io/libevwrapper.c @@ -118,7 +118,13 @@ IO_dealloc(libevwrapper_IO *self) { static void io_callback(struct ev_loop *loop, ev_io *watcher, int revents) { libevwrapper_IO *self = watcher->data; PyObject *result; - PyGILState_STATE gstate = PyGILState_Ensure(); + PyGILState_STATE gstate; + + if (!self || !self->callback) { + return; // Skip callback if object is being destroyed + } + + gstate = PyGILState_Ensure(); if (revents & EV_ERROR && errno) { result = PyObject_CallFunction(self->callback, "Obi", self, revents, errno); } else { @@ -477,6 +483,10 @@ static void timer_callback(struct ev_loop *loop, ev_timer *watcher, int revents) PyObject *result = NULL; PyGILState_STATE gstate; + if (!self || !self->callback) { + return; // Skip callback if object is being destroyed + } + gstate = PyGILState_Ensure(); result = PyObject_CallFunction(self->callback, NULL); if (!result) {