diff options
| author | Adam Malczewski <[email protected]> | 2026-06-15 13:30:00 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-15 13:30:00 +0900 |
| commit | 91312ec3e164b04216ca10cd36bf77a6b72be533 (patch) | |
| tree | 44b95a8f7244a22de689acd4417d4e87d0a5a41f /packages/kernel/src/server.cpp | |
| parent | e051b9b22f8e237c7f52c119c57d71f02be24b3d (diff) | |
| download | unbox-91312ec3e164b04216ca10cd36bf77a6b72be533.tar.gz unbox-91312ec3e164b04216ca10cd36bf77a6b72be533.zip | |
kernel(rml-compositing): fix Wave-1 test-seam listener lifetime (root cause)
The Wave-1 headless surface-capture test seam attached a commit Listener to
every client wl_surface and only detached at Server teardown, so a client
destroying a surface mid-session tripped wlroots'
wl_list_empty(&surface->events.commit.listener_list) assertion (SIGABRT). This
aborted ext-stage-dock-glue at teardown and forced an in-unit workaround in
ext-xdg-shell-client.
Fix: the per-surface commit Listener is now RAII-scoped to the surface's own
lifetime (dropped on a wlr_surface.events.destroy listener), so it unsubscribes
before the surface resource is destroyed. Test-probe behavior unchanged;
extension-facing contract unchanged.
kernel suite green; ext-stage-dock now GREEN (no teardown SIGABRT); ext-xdg-shell
still green; build-asan kernel green, no new unbox::-framed leak/UB.
Diffstat (limited to 'packages/kernel/src/server.cpp')
| -rw-r--r-- | packages/kernel/src/server.cpp | 23 |
1 files changed, 19 insertions, 4 deletions
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp index 8b67c8a..f88fa8f 100644 --- a/packages/kernel/src/server.cpp +++ b/packages/kernel/src/server.cpp @@ -325,13 +325,28 @@ void Server::Impl::init() { if (compositor != nullptr) { test_new_surface.connect(compositor->events.new_surface, [this](void* data) { auto* surface = static_cast<wlr_surface*>(data); - test_surface_commits.emplace_back(); - Listener& commit = test_surface_commits.back(); - commit.connect(surface->events.commit, [this, surface](void*) { + test_surface_captures.emplace_back(); + Impl::TestSurfaceCapture& cap = test_surface_captures.back(); + cap.surface = surface; + cap.commit.connect(surface->events.commit, [this, surface](void*) { if (surface->buffer != nullptr) { test_last_client_surface = surface; } }); + // RAII to the surface's lifetime: when the client destroys this + // wl_surface, drop its capture record — unsubscribing the commit + // listener BEFORE wlroots destroys the surface resource (which asserts + // commit.listener_list is empty). Clearing the record is the handler's + // LAST action; nothing touches it afterwards (listener.hpp's + // destroy-event pattern). Also forget it if it was the captured one, so + // no probe builds a SurfaceElement from a dead surface. + cap.destroy.connect(surface->events.destroy, [this, surface](void*) { + if (test_last_client_surface == surface) { + test_last_client_surface = nullptr; + } + test_surface_captures.remove_if( + [surface](const Impl::TestSurfaceCapture& c) { return c.surface == surface; }); + }); }); } @@ -574,7 +589,7 @@ void Server::Impl::shutdown() { // commit hook) and the capture listeners BEFORE the substrate/compositor go. test_surface_element.reset(); test_new_surface.disconnect(); - test_surface_commits.clear(); + test_surface_captures.clear(); // each record's commit+destroy listeners unsubscribe test_last_client_surface = nullptr; // The virtual test keyboard (if added) is finished + freed before the seat / // display die (a wlr_keyboard outliving the seat it was set on is UB). |
