Skip to content

Fix resource cleanup on browser shutdown and garbage collection - #702

Closed
dima4p wants to merge 23 commits into
rubycdp:mainfrom
dima4p:main
Closed

dima4p wants to merge 23 commits into
rubycdp:mainfrom
dima4p:main

Conversation

@dima4p

@dima4p dima4p commented Nov 14, 2026

Copy link
Copy Markdown

Problem

Browser shutdown does not always release all resources. Depending on the failure path, Chromium processes or their child processes may remain alive, temporary files may be left behind, and WebSocket resources may not be fully closed.

There is also a problem with garbage-collection cleanup: a Browser finalizer may run in a context where thread synchronization and thread operations are not allowed, causing cleanup to fail.

Changes

This PR addresses these issues across the browser, process, client, and WebSocket cleanup paths.

  • Browser shutdown: Ensure that all cleanup stages are attempted even if an earlier stage raises an exception.
  • WebSocket cleanup: Ensure that the socket and worker resources are closed even when the normal close operation fails or the peer disconnects.
  • Garbage collection: Make the Browser finalizer safe to run in GC context by limiting it to operations that do not require thread synchronization. The worker threads complete their own cleanup.
  • Chromium process termination: Continue terminating the process group when the browser's main process has already been reaped (Errno::ECHILD), so surviving child processes are not left running.
  • Temporary files: Give each browser its own temporary directory on Linux, keeping Chromium temporary files within that browser's user-data directory. This also prevents one browser's cleanup from removing another browser's temporary files.

Regression tests cover the corresponding failure scenarios.

Production verification

After deploying these changes, the SpaceCleaner report has shown zero leftover Chromium processes and zero leftover Ferrum/Chromium temporary-directory entries for two consecutive days.

This provides practical confirmation that the original production problem has been addressed.

@route

route commented Nov 14, 2026

Copy link
Copy Markdown
Member

Change by change explanation:

  • TMPDIR per browser. Ferrum owns one directory, the user-data dir it creates, and quit removes it. Whatever Chrome writes to its own temp location is Chrome's business, and whoever runs the machine cleans that up. Ferrum shouldn't redirect Chrome's TMPDIR. It would also silently override a TMPDIR that users pass through env:.
  • Browser GC finalizer. Process already registers a finalizer that kills Chrome and removes the user-data dir. The new one kills the same process again: run an unquit browser to exit and you'll see two TERMs, one from each finalizer. All it adds is closing the socket slightly earlier, for a browser whose Chrome is about to be killed anyway.
  • ensure chains in Browser#quit and Client#close. Nothing in these paths raises in practice. close_connections only queues close frames, and the specs have to force the error with doubles. Four levels of nested ensure make the code harder to read with no failure behind them.
  • Reader/writer/subscriber cleanup on disconnect. These only matter when Chrome dies and quit is never called. When quit is called (including through restart), the thread, the socket and the subscriber threads are already cleaned up.

The production numbers are good to hear about, but with five changes in one PR plus your own cleaner job, there's no way to tell which change did it. If you can reproduce a leak while calling quit, a Chrome process or a Ferrum user-data dir left behind, please open an issue with a minimal script and we'll fix that specific case.

@route route closed this Nov 14, 2026
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.

2 participants