Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix: stop resending byte-identical dmabuf feedback to every client
Each dmabuf feedback carries a format-table fd, and Mesa parks those on
its private WSI queues until the next acquire/present — so an idle,
damage-driven window banks one per resend and never drains. Measured on
the live session: ONE enter+exit of the overview added 67 fds to every
long-lived client at once (cce-calendar 394->461, cce-terminal 413->480
in the same toggle), against a 1024 soft limit. That is what eventually
kills a client's connection and gets its window rebuilt.
scene_buffer_send_dmabuf_feedback already suppresses duplicates by
memcmp against prev_feedback_options. The cache was then cleared
wholesale whenever a buffer's primary_output changed, which defeats it:
composition feedback is {renderer, NULL} whichever output is primary, so
the clear only ever forced a byte-identical resend. The options being
compared already carry everything the change could affect — main_renderer
and scanout_primary_output — so the memcmp catches every real change on
its own. Dropped the clear.
Also logs one WLR_DEBUG line per feedback that actually goes out, so the
burst is greppable rather than inferrable:
`grep -c 'dmabuf feedback sent'`.
VERIFIED: no regression. In a shadow session after the change a client
still receives the identical feedback — 10 format_table events, same as
before, 30 tranche/done — and renders normally across overview toggles.
NOT VERIFIED: the reduction itself. The resend storm is live-only, needing
a real DRM output to re-evaluate scanout per frame; a headless shadow
sends feedback at startup and never storms (0 extra events across two
overview cycles, before or after). So this cannot be confirmed until the
next login, when one overview toggle settles it — see the commit body of
cce-ui@2416904 for the fd-counting recipe.
Root cause of the client-side symptom is cce-fx@3c68c41 / cce-ui@2416904;
this removes the source rather than the consequences.
Co-Authored-By: Claude Opus 5 <[email protected]>
scenefx/types/scene/wlr_scene.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/scenefx/types/scene/wlr_scene.c b/scenefx/types/scene/wlr_scene.c
index a0c4d09..21a9667 100644
--- a/scenefx/types/scene/wlr_scene.c
+++ b/scenefx/types/scene/wlr_scene.c
@@ -606,10 +606,18 @@ static void update_node_update_outputs(struct wlr_scene_node *node,
}
}
- if (old_primary_output != scene_buffer->primary_output) {
- scene_buffer->prev_feedback_options =
- (struct wlr_linux_dmabuf_feedback_v1_init_options){0};
- }
+ // NOT cleared on a primary_output change (cce): clearing it defeats the
+ // duplicate-suppression memcmp in scene_buffer_send_dmabuf_feedback, and
+ // the options it compares already carry everything a change here could
+ // affect — main_renderer and scanout_primary_output. Composition feedback
+ // is {renderer, NULL} whichever output is primary, so clearing only ever
+ // forced a byte-identical resend.
+ //
+ // Each resend costs the client a format-table fd, and Mesa parks those on
+ // its private WSI queues until the next acquire/present — so an idle,
+ // damage-driven window banks one per resend and never drains. Measured on
+ // a live session before this change: one enter+exit of the overview added
+ // 67 fds to every long-lived client at once, against a 1024 soft limit.
uint64_t old_active = scene_buffer->active_outputs;
scene_buffer->active_outputs = active_outputs;
@@ -3117,6 +3125,12 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene,
scene_buffer->prev_feedback_options = *options;
+ // One line per feedback that actually goes out (the memcmp above having
+ // found a real change). Each carries an fd to the client, so a burst here
+ // is the fd-exhaustion signature: `grep -c 'dmabuf feedback sent'`.
+ wlr_log(WLR_DEBUG, "dmabuf feedback sent (scanout_output=%p)",
+ (void *)options->scanout_primary_output);
+
struct wlr_linux_dmabuf_feedback_v1 feedback = {0};
if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, options)) {
return;