[PR #3] Drop freed interpipesrc listeners from nodes #3

Open
opened 2026-10-15 12:47:17 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/gst-interpipe/pull/3
Author: @gorylpe
Created: 9/30/2026
Status: 🔄 Open

Base: master ← Head: fix/stale-listeners


📝 Commits (1)

  • 757a441 Drop freed interpipesrc listeners from nodes

📊 Changes

6 files changed (+119 additions, -2 deletions)

View changed files

📝 gst/interpipe/gstinterpipe.c (+32 -0)
📝 gst/interpipe/gstinterpipe.h (+9 -0)
📝 gst/interpipe/gstinterpipeinode.c (+13 -0)
📝 gst/interpipe/gstinterpipeinode.h (+15 -0)
📝 gst/interpipe/gstinterpipesink.c (+38 -2)
📝 gst/interpipe/gstinterpipesrc.c (+12 -0)

📄 Description

When a listen-to switch races an interpipesrc's teardown, the source can stay registered on a node after it is freed. The node then pushes every buffer to freed memory: gst_inter_pipe_ilistener_push_buffer() fails its type check and the ref taken in gst_inter_pipe_sink_push_to_listener() is never released. With raw 1080p60 video that's ~500 MB/s until the OOM killer steps in. A second effect: when the stream resumes, its new source has the same name, add_listener() keeps the stale entry as "already registered", and the resumed stream freezes.

Reproducing with Wolf

  • Wolf ghcr.io/games-on-whales/wolf:stable (a1edc1b), GStreamer 1.26.7, NVIDIA legacy pipeline (WOLF_USE_ZERO_COPY=FALSE)
  • Client: moonlight-web-stream v2.10.0, 1080p60 H.264 over WebRTC
  1. Connect with moonlight-web, start Steam from Wolf UI (Wolf creates a lobby and moves the stream into it).
  2. Close the browser tab instead of using Exit.
  3. About 10 s later Wolf logs the stream as over, and its RSS starts growing.

Reproduced 3 of 3 times this way. Log from one of them (Wolf timestamps, UTC):

16:19:21.329  [LOBBY] Moonlight stream 1467…441 over, leaving lobby 921af6e4-…
16:19:21.329  [LOBBY] Session 1467…441 leaving lobby 921af6e4-…
ERROR interpipesink gstinterpipesink.c:1048:gst_inter_pipe_sink_add_listener:<1467…441_video> Failed to reconfigure
WARN  interpipe gstinterpipe.c:169:gst_inter_pipe_listen_node: Could not add listener interpipesrc_1467…441_video to node 1467…441_video
ERROR interpipesink gstinterpipesink.c:1104:gst_inter_pipe_sink_remove_listener:<921af6e4-…_video> Listener interpipesrc_1467…441_video is not registered in node 921af6e4-…_video
ERROR interpipesrc gstinterpipesrc.c:275:gst_inter_pipe_src_set_property:<interpipesrc_1467…441_video> Could not listen to node 1467…441_video
16:19:21.353  Stopped container: /Wolf-UI_1467…441
** CRITICAL **: gst_inter_pipe_ilistener_push_buffer: assertion 'GST_INTER_PIPE_IS_ILISTENER (self)' failed    (~100/s from here)
16:19:48      supervisord: exited: wolf (terminated by SIGKILL; not expected)    (memory cgroup oom_kill)

Wolf's leave_lobby() switches the stream's interpipesrc back to the session's own node while the stream's pipelines are going to EOS. The switch fails (Failed to reconfigure), and the fallback in gst_inter_pipe_src_listen_node() leaves the global listeners table and the lobby node's table out of step. After that nothing removes the source from the lobby's node, not even when the source is freed. In another run, measured: 1.1 GiB → 6.5 GiB in 11 s.

The desktop Moonlight client didn't trigger it (Ctrl+Alt+Shift+Q): that is a clean pause, the session's own producer stays up and the switch back succeeds. Closing a moonlight-web tab drops the connection late, when its WebRTC peer times out, while Wolf stops the session's Wolf UI producer at the same moment (Stopped container above), so the switch lands on a producer that is being torn down. moonlight-web's Exit button stops cleanly and doesn't hit it either. It's a timing window, not something specific to moonlight-web.

Possibly the same bug as games-on-whales/wolf#364 (Wolf crashing when sessions join a lobby): the reports there show the same GST_INTER_PIPE_IS_ILISTENER assertions and, in one trace, malloc(): unaligned tcache chunk detected right after a producer switch.

Change

  • interpipesrc removes itself on dispose. The new gst_inter_pipe_forget_listener() drops its listeners-table entry and asks every node, through a new forget_listener vfunc on GstInterPipeINode, to drop that exact object. A different listener registered under the same name is kept. The node list is copied first, so no node lock is taken while holding nodes_mutex.
  • interpipesink's add_listener() replaces the entry when a different object registers under a taken name, instead of keeping the stale one.

Testing

  • Same Wolf image with only libgstinterpipe.so rebuilt from this branch: closing the tab now logs Dropping listener …, freed while still registered, there's no assertion flood, memory stays flat, and rejoining the lobby from Wolf UI streams again.
  • Existing tests: 15/15 pass (meson test, built against the GStreamer in the Wolf image).

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/gst-interpipe/pull/3 **Author:** [@gorylpe](https://github.com/gorylpe) **Created:** 9/30/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `fix/stale-listeners` --- ### 📝 Commits (1) - [`757a441`](https://github.com/games-on-whales/gst-interpipe/commit/757a44120b37b63328acc0c57138410162c091b0) Drop freed interpipesrc listeners from nodes ### 📊 Changes **6 files changed** (+119 additions, -2 deletions) <details> <summary>View changed files</summary> 📝 `gst/interpipe/gstinterpipe.c` (+32 -0) 📝 `gst/interpipe/gstinterpipe.h` (+9 -0) 📝 `gst/interpipe/gstinterpipeinode.c` (+13 -0) 📝 `gst/interpipe/gstinterpipeinode.h` (+15 -0) 📝 `gst/interpipe/gstinterpipesink.c` (+38 -2) 📝 `gst/interpipe/gstinterpipesrc.c` (+12 -0) </details> ### 📄 Description When a `listen-to` switch races an `interpipesrc`'s teardown, the source can stay registered on a node after it is freed. The node then pushes every buffer to freed memory: `gst_inter_pipe_ilistener_push_buffer()` fails its type check and the ref taken in `gst_inter_pipe_sink_push_to_listener()` is never released. With raw 1080p60 video that's ~500 MB/s until the OOM killer steps in. A second effect: when the stream resumes, its new source has the same name, `add_listener()` keeps the stale entry as "already registered", and the resumed stream freezes. ## Reproducing with Wolf - Wolf `ghcr.io/games-on-whales/wolf:stable` (a1edc1b), GStreamer 1.26.7, NVIDIA legacy pipeline (`WOLF_USE_ZERO_COPY=FALSE`) - Client: [moonlight-web-stream](https://github.com/MrCreativ3001/moonlight-web-stream) v2.10.0, 1080p60 H.264 over WebRTC 1. Connect with moonlight-web, start Steam from Wolf UI (Wolf creates a lobby and moves the stream into it). 2. Close the browser tab instead of using Exit. 3. About 10 s later Wolf logs the stream as over, and its RSS starts growing. Reproduced 3 of 3 times this way. Log from one of them (Wolf timestamps, UTC): ``` 16:19:21.329 [LOBBY] Moonlight stream 1467…441 over, leaving lobby 921af6e4-… 16:19:21.329 [LOBBY] Session 1467…441 leaving lobby 921af6e4-… ERROR interpipesink gstinterpipesink.c:1048:gst_inter_pipe_sink_add_listener:<1467…441_video> Failed to reconfigure WARN interpipe gstinterpipe.c:169:gst_inter_pipe_listen_node: Could not add listener interpipesrc_1467…441_video to node 1467…441_video ERROR interpipesink gstinterpipesink.c:1104:gst_inter_pipe_sink_remove_listener:<921af6e4-…_video> Listener interpipesrc_1467…441_video is not registered in node 921af6e4-…_video ERROR interpipesrc gstinterpipesrc.c:275:gst_inter_pipe_src_set_property:<interpipesrc_1467…441_video> Could not listen to node 1467…441_video 16:19:21.353 Stopped container: /Wolf-UI_1467…441 ** CRITICAL **: gst_inter_pipe_ilistener_push_buffer: assertion 'GST_INTER_PIPE_IS_ILISTENER (self)' failed (~100/s from here) 16:19:48 supervisord: exited: wolf (terminated by SIGKILL; not expected) (memory cgroup oom_kill) ``` Wolf's `leave_lobby()` switches the stream's `interpipesrc` back to the session's own node while the stream's pipelines are going to EOS. The switch fails (`Failed to reconfigure`), and the fallback in `gst_inter_pipe_src_listen_node()` leaves the global listeners table and the lobby node's table out of step. After that nothing removes the source from the lobby's node, not even when the source is freed. In another run, measured: 1.1 GiB → 6.5 GiB in 11 s. The desktop Moonlight client didn't trigger it (Ctrl+Alt+Shift+Q): that is a clean pause, the session's own producer stays up and the switch back succeeds. Closing a moonlight-web tab drops the connection late, when its WebRTC peer times out, while Wolf stops the session's Wolf UI producer at the same moment (`Stopped container` above), so the switch lands on a producer that is being torn down. moonlight-web's Exit button stops cleanly and doesn't hit it either. It's a timing window, not something specific to moonlight-web. Possibly the same bug as games-on-whales/wolf#364 (Wolf crashing when sessions join a lobby): the reports there show the same `GST_INTER_PIPE_IS_ILISTENER` assertions and, in one trace, `malloc(): unaligned tcache chunk detected` right after a producer switch. ## Change - `interpipesrc` removes itself on `dispose`. The new `gst_inter_pipe_forget_listener()` drops its listeners-table entry and asks every node, through a new `forget_listener` vfunc on `GstInterPipeINode`, to drop that exact object. A different listener registered under the same name is kept. The node list is copied first, so no node lock is taken while holding `nodes_mutex`. - `interpipesink`'s `add_listener()` replaces the entry when a different object registers under a taken name, instead of keeping the stale one. ## Testing - Same Wolf image with only `libgstinterpipe.so` rebuilt from this branch: closing the tab now logs `Dropping listener …, freed while still registered`, there's no assertion flood, memory stays flat, and rejoining the lobby from Wolf UI streams again. - Existing tests: 15/15 pass (`meson test`, built against the GStreamer in the Wolf image). --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/gst-interpipe#3
No description provided.