{"record":{"id":"b7471e1cd0252fef","repo":"SeleniumHQ/selenium","slug":"pid-still-alive-after-timeout-seconds","errorCode":null,"errorMessage":"  ->  #{@pid} still alive after #{timeout} seconds","messagePattern":"  ->  #(.+?) still alive after #(.+?) seconds","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"rb/lib/selenium/webdriver/common/child_process.rb","lineNumber":100,"sourceCode":"        _, @status = waitpid2(@pid, Process::WNOHANG | Process::WUNTRACED) if @status.nil?\n        return false if @status.nil?\n\n        exit_code = @status.exitstatus || @status.termsig\n        WebDriver.logger.debug(\"  -> exit code is #{exit_code.inspect}\", id: :process)\n\n        !!exit_code\n      rescue Errno::ECHILD, Errno::ESRCH\n        WebDriver.logger.debug(\"  -> process: #{@pid} already finished\", id: :process)\n        true\n      end\n\n      def poll_for_exit(timeout)\n        WebDriver.logger.debug(\"Polling #{timeout} seconds for exit of #{@pid}\", id: :process)\n\n        end_time = Time.now + timeout\n        sleep POLL_INTERVAL until exited? || Time.now > end_time\n\n        raise TimeoutError, \"  ->  #{@pid} still alive after #{timeout} seconds\" unless exited?\n      end\n\n      def wait\n        return if exited?\n\n        _, @status = waitpid2(@pid)\n      end\n\n      private\n\n      def terminate_and_wait_else_kill(timeout)\n        WebDriver.logger.debug(\"Sending TERM to process: #{@pid}\", id: :process)\n        terminate(@pid)\n        poll_for_exit(timeout)\n\n        WebDriver.logger.debug(\"  -> stopped #{@pid}\", id: :process)\n      rescue TimeoutError, Errno::EINVAL\n        WebDriver.logger.debug(\"    -> sending KILL to process: #{@pid}\", id: :process)","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/SeleniumHQ/selenium/blob/aa36b38e696a0909e973bdf5e2f9031ffe842c4b/rb/lib/selenium/webdriver/common/child_process.rb#L82-L118","documentation":"ChildProcess#poll_for_exit waits for a spawned child (e.g. driver/server process) to exit, polling at POLL_INTERVAL up to the timeout. If the process is still alive past end_time, it raises TimeoutError. This is the process-level companion to socket timeouts.","triggerScenarios":"Calling process.poll_for_exit(timeout) (typically via stop/wait on a managed process) where the child does not terminate within the timeout — e.g. an unresponsive driver or server process that ignores SIGTERM.","commonSituations":"Driver/browser/server process hung, OS slow to deliver signals, too-small timeout on a loaded CI box, zombie processes, container not forwarding signals.","solutions":["Increase the timeout passed to poll_for_exit / the managed process stop call.","Ensure signals (TERM/KILL) reach the child; in containers run with a proper init (tini) or share the PID namespace correctly.","Reap orphaned processes from prior runs before starting new ones.","Investigate why the child ignores shutdown — capture its logs before killing."],"exampleFix":"// before\nprocess.poll_for_exit(5)\n// after\nprocess.poll_for_exit(30)","handlingStrategy":"retry","validationCode":"# No pre-check can force a child to exit; pick a timeout appropriate to the environment.\ntimeout = ci? ? 60 : 10","typeGuard":null,"tryCatchPattern":"begin\n  process.poll_for_exit(timeout)\nrescue TimeoutError => e\n  warn \"child alive after #{timeout}s: #{e.message}\"\n  process.kill rescue nil\nend","preventionTips":["Use a generous poll timeout on slow/CI hosts.","Ensure containers forward signals (use tini/dumb-init or share PID namespace).","Track spawned PIDs so you can force-kill on timeout.","Reap orphaned driver/server processes between runs."],"tags":["process","timeout","child-process","ruby"],"backgroundTag":null,"analyzedSha":"aa36b38e696a0909e973bdf5e2f9031ffe842c4b","analyzedAt":"2026-08-14T02:32:32.244Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}