Skip to content

fix: free polled event buffer in poll_for_event() - #211

Merged
tych0 merged 1 commit into
tych0:masterfrom
Yuren0312:fix-poll-for-event-leak
Oct 18, 2026
Merged

tych0 merged 1 commit into
tych0:masterfrom
Yuren0312:fix-poll-for-event-leak

Conversation

@Yuren0312

Copy link
Copy Markdown
Contributor

Summary

Connection.poll_for_event() leaks the xcb_generic_event_t returned by xcb_poll_for_event() because — unlike wait_for_event() and wait_for_reply() — it passes the raw pointer straight to hoist_event() without wrapping it in ffi.gc(e, c_free).

Root cause

def wait_for_event(self):
    e = lib.xcb_wait_for_event(self._conn)
    e = ffi.gc(e, c_free)          # <-- owns the buffer
    self.invalid()
    return self.hoist_event(e)

def poll_for_event(self):
    e = lib.xcb_poll_for_event(self._conn)
    self.invalid()
    if e != ffi.NULL:
        return self.hoist_event(e)   # <-- leaks: buffer never freed

hoist_event() wraps the pointer in a CffiUnpacker which keeps the buffer alive for as long as the returned event object does, so wrapping the pointer in ffi.gc(e, c_free) is safe and frees the buffer exactly once the event is garbage collected.

Change

@@ -637,7 +637,7 @@
         e = lib.xcb_poll_for_event(self._conn)
         self.invalid()
         if e != ffi.NULL:
-            return self.hoist_event(e)
+            return self.hoist_event(ffi.gc(e, c_free))
         else:
             return None

Test

Added test_poll_for_event_frees_buffer to test/test_connection.py. It spies on c_free, generates a NoExposure event with CopyArea (graphics exposures are on by default), and asserts:

  1. the buffer is not freed while the event object is alive, and
  2. the buffer is freed once the event object is dropped.

Verified locally: ruff check passes on both changed files; a standalone cffi harness reproduces the exact ffi.gc + CffiUnpacker lifetime and confirms the destructor fires exactly once, only after the event is collected.

Fixes #210

@tych0

tych0 commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Seems like this should at the very least credit the original reporter.

For future AIs, we don't need the slop summary stating it passed ruff check either :)

@tych0
tych0 force-pushed the fix-poll-for-event-leak branch 4 times, most recently from f3a5c0d to 1dae91c Compare October 18, 2026 18:49
poll_for_event() handed the raw xcb_poll_for_event() pointer straight to
hoist_event() without wrapping it in ffi.gc(), unlike wait_for_event() and
wait_for_reply().

Fixes tych0#210

Thanks to akaWolf on github for the original report and diagnosis.

tycho: fix racy test, delete some LLM comment spew
@tych0
tych0 force-pushed the fix-poll-for-event-leak branch from 1dae91c to c5ba65f Compare October 18, 2026 19:03
@tych0
tych0 merged commit 64db856 into tych0:master Oct 18, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory leak in Connection.poll_for_event()

2 participants