summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/server.cpp
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-15 13:30:00 +0900
committerAdam Malczewski <[email protected]>2026-06-15 13:30:00 +0900
commit91312ec3e164b04216ca10cd36bf77a6b72be533 (patch)
tree44b95a8f7244a22de689acd4417d4e87d0a5a41f /packages/kernel/src/server.cpp
parente051b9b22f8e237c7f52c119c57d71f02be24b3d (diff)
downloadunbox-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.cpp23
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).