Path: blob/trunk/rb/lib/selenium/webdriver/common/child_process.rb
1865 views
# frozen_string_literal: true12# Licensed to the Software Freedom Conservancy (SFC) under one3# or more contributor license agreements. See the NOTICE file4# distributed with this work for additional information5# regarding copyright ownership. The SFC licenses this file6# to you under the Apache License, Version 2.0 (the7# "License"); you may not use this file except in compliance8# with the License. You may obtain a copy of the License at9#10# http://www.apache.org/licenses/LICENSE-2.011#12# Unless required by applicable law or agreed to in writing,13# software distributed under the License is distributed on an14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY15# KIND, either express or implied. See the License for the16# specific language governing permissions and limitations17# under the License.1819module Selenium20module WebDriver21#22# @api private23#2425class ChildProcess26TimeoutError = Class.new(StandardError)2728SIGTERM = 'TERM'29SIGKILL = 'KILL'3031POLL_INTERVAL = 0.13233attr_accessor :detach34attr_writer :io3536def self.build(*command)37new(*command)38end3940def initialize(*command)41@command = command42@detach = false43@pid = nil44@status = nil45end4647def io48@io ||= Platform.null_device49end5051def start52options = {%i[out err] => io}53options[:pgroup] = true unless Platform.windows? # NOTE: this is a bug only in Windows 75455WebDriver.logger.debug("Starting process: #{@command} with #{options}", id: :process)56@pid = Process.spawn(*@command, options)57WebDriver.logger.debug(" -> pid: #{@pid}", id: :process)5859Process.detach(@pid) if detach60end6162def stop(timeout = 3)63return unless @pid64return if exited?6566terminate_and_wait_else_kill(timeout)67rescue Errno::ECHILD, Errno::ESRCH => e68# Process exited earlier than terminate/kill could catch69WebDriver.logger.debug(" -> process: #{@pid} does not exist (#{e.class.name})", id: :process)70end7172def alive?73@pid && !exited?74end7576def exited?77return false unless @pid7879WebDriver.logger.debug("Checking if #{@pid} is exited:", id: :process)80_, @status = waitpid2(@pid, Process::WNOHANG | Process::WUNTRACED) if @status.nil?81return false if @status.nil?8283exit_code = @status.exitstatus || @status.termsig84WebDriver.logger.debug(" -> exit code is #{exit_code.inspect}", id: :process)8586!!exit_code87rescue Errno::ECHILD, Errno::ESRCH88WebDriver.logger.debug(" -> process: #{@pid} already finished", id: :process)89true90end9192def poll_for_exit(timeout)93WebDriver.logger.debug("Polling #{timeout} seconds for exit of #{@pid}", id: :process)9495end_time = Time.now + timeout96sleep POLL_INTERVAL until exited? || Time.now > end_time9798raise TimeoutError, " -> #{@pid} still alive after #{timeout} seconds" unless exited?99end100101def wait102return if exited?103104_, @status = waitpid2(@pid)105end106107private108109def terminate_and_wait_else_kill(timeout)110WebDriver.logger.debug("Sending TERM to process: #{@pid}", id: :process)111terminate(@pid)112poll_for_exit(timeout)113114WebDriver.logger.debug(" -> stopped #{@pid}", id: :process)115rescue TimeoutError, Errno::EINVAL116WebDriver.logger.debug(" -> sending KILL to process: #{@pid}", id: :process)117kill(@pid)118wait119WebDriver.logger.debug(" -> killed #{@pid}", id: :process)120end121122def terminate(pid)123Process.kill(SIGTERM, pid)124rescue Errno::ECHILD, Errno::ESRCH125# Process does not exist, nothing to terminate126end127128def kill(pid)129Process.kill(SIGKILL, pid)130rescue Errno::ECHILD, Errno::ESRCH131# Process does not exist, nothing to kill132end133134def waitpid2(pid, flags = 0)135Process.waitpid2(pid, flags)136end137end # ChildProcess138end # WebDriver139end # Selenium140141142