Skip to content

feat: improve process termination and directory cleanup logic - #616

Open
route wants to merge 6 commits into
mainfrom
fix-process-termination
Open

feat: improve process termination and directory cleanup logic#616
route wants to merge 6 commits into
mainfrom
fix-process-termination

Conversation

@route

@route route commented Aug 20, 2026

Copy link
Copy Markdown
Member
  • Add send_signal method for robust PID and process group signaling.
  • Implement remove_directory with retries and backoff to handle transient errors.

@ZilvinasKucinskas ZilvinasKucinskas left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this head against real process groups on macOS and Debian. The blocking issue below reproduces on the current code and passes with the suggested loop. The candidate also passes Ferrum's full suite and lint; the async API note is optional.

Comment thread lib/ferrum/browser/process.rb Outdated
::Process.kill("USR1", pid)
send_signal(pid, "TERM")
start = Utils::ElapsedTime.monotonic_time
while ::Process.wait(pid, ::Process::WNOHANG).nil?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns as soon as the process-group leader exits. If Chrome exits on TERM while a renderer or zygote stays alive, wait reaps the leader and group KILL is never sent. I reproduced this on macOS and Debian with a child in the same group that ignores TERM (child_alive=true on this commit).

Please track leader reaping separately and keep polling the group until it exits or the timeout sends KILL:

leader_exited = false
loop do
  leader_exited ||= !::Process.wait(pid, ::Process::WNOHANG).nil?
  break if leader_exited && !process_group_alive?(pid)

  sleep(WAIT_KILLED)
  next unless Utils::ElapsedTime.timeout?(start, KILL_TIMEOUT)

  send_signal(pid, "KILL")
  ::Process.wait(pid) unless leader_exited
  break
end

process_group_alive? can use Process.kill(0, -pid), treating ESRCH as false and EPERM as true. Please add one real subprocess regression; the current mocks always return nil and miss this case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats an oversight likely from my side, looking at your patch gist. ported it over too.

Comment thread lib/ferrum/browser/process.rb Outdated
#
# @return [void]
#
def self.remove_directory(path, retries: REMOVE_DIR_RETRIES, delay: REMOVE_DIR_RETRY_DELAY)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: with the 2s browser timeout plus 1.5s directory backoff, quit can block for about 3.5s, or 5.5s with Xvfb. Could we expose an opt-in Browser#quit(wait: false) / Process#stop(wait: false)?

Please keep wait: true as the default and keep restart synchronous, so Ferrum and Cuprite behavior stays unchanged. The async path should close CDP synchronously, run browser group -> Xvfb -> user-data-directory cleanup through Utils::Thread.spawn(abort_on_exception: false), and return the thread or handle so callers can join it before process exit or fixed-port reuse. We currently carry this as an application patch and could remove it.

route added 6 commits August 21, 2026 14:39
- Add `send_signal` method for robust PID and process group signaling.
- Implement `remove_directory` with retries and backoff to handle transient errors.
- Add `process_group_alive?` method to detect the state of a process group.
- Enhance process termination logic to handle scenarios where group members ignore TERM signals.
- Add tests to verify process group termination behavior.
- Enhance `#quit` and `#stop` methods to support non-blocking cleanup with `wait: false`.
- Introduce `sync_stop` and `async_stop` for handling synchronous and asynchronous logic.
- Move process termination, signal handling, and directory cleanup methods to `Process::Killer`.
- Simplify `Process` class by delegating termination and cleanup to the new module.
@route
route force-pushed the fix-process-termination branch from 9dde5f8 to e1c4737 Compare August 21, 2026 11:39
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