Skip to content

Commit

Permalink
Remove ChildProcess gem dependency
Browse files Browse the repository at this point in the history
Process.spawn is cross-platform in Ruby 2.7+ and ChildProcess is not
actively maintained at the moment. Fixes #11251.
  • Loading branch information
p0deje committed Nov 11, 2022
1 parent 558e0a0 commit 75018a3
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 36 deletions.
6 changes: 3 additions & 3 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@ pin_browsers()

http_archive(
name = "rules_ruby",
sha256 = "966ef280a3ecb24867b2f67d73bb3ad3c59b4a7beefb67abbcd79b0dff0a5679",
strip_prefix = "rules_ruby_simple-078c24fd29a9e1717990fa2fce15e1cb053f18a3",
url = "https://github.com/p0deje/rules_ruby_simple/archive/078c24fd29a9e1717990fa2fce15e1cb053f18a3.zip",
sha256 = "bb0bffb0285ff8fa9a967fc2580ddf3b511818a6baf269dcd6c5c6076c4921d8",
strip_prefix = "rules_ruby-c3cefa71d0111a04c9ce0672c65d376262d8d975",
url = "https://github.com/p0deje/rules_ruby/archive/c3cefa71d0111a04c9ce0672c65d376262d8d975.zip",
)

load(
Expand Down
13 changes: 5 additions & 8 deletions rb/lib/selenium/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# specific language governing permissions and limitations
# under the License.

require 'childprocess'
require 'selenium/webdriver/common/child_process'
require 'selenium/webdriver/common/socket_poller'
require 'net/http'

Expand Down Expand Up @@ -237,7 +237,7 @@ def stop_process

begin
@process.poll_for_exit(5)
rescue ChildProcess::TimeoutError
rescue WebDriver::Common::ChildProcess::TimeoutError
@process.stop
end
rescue Errno::ECHILD
Expand All @@ -252,16 +252,13 @@ def process
properties = @additional_args.dup - @additional_args.delete_if { |arg| arg[/^-D/] }
args = ['-jar', @jar, @role, '--port', @port.to_s]
server_command = ['java'] + properties + args + @additional_args
cp = ChildProcess.build(*server_command)
cp = WebDriver::Common::ChildProcess.build(*server_command)
WebDriver.logger.debug("Executing Process #{server_command}")

io = cp.io

if @log.is_a?(String)
@log_file = File.open(@log, 'w')
io.stdout = io.stderr = @log_file
cp.io = @log
elsif @log
io.inherit!
cp.io = :out
end

cp.detach = @background
Expand Down
1 change: 0 additions & 1 deletion rb/lib/selenium/webdriver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
# specific language governing permissions and limitations
# under the License.

require 'childprocess'
require 'tmpdir'
require 'fileutils'
require 'date'
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,4 @@
require 'selenium/webdriver/common/element'
require 'selenium/webdriver/common/shadow_root'
require 'selenium/webdriver/common/websocket_connection'
require 'selenium/webdriver/common/child_process'
114 changes: 114 additions & 0 deletions rb/lib/selenium/webdriver/common/child_process.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
module Common

#
# @api private
#

class ChildProcess
TimeoutError = Class.new(StandardError)

SIGTERM = 'TERM'
SIGKILL = 'KILL'

POLL_INTERVAL = 0.1

attr_accessor :detach
attr_writer :io

def self.build(*command)
new(*command)
end

def initialize(*command)
@command = command
@detach = false
@pid = nil
@status = nil
end

def io
@io ||= Platform.null_device
end

def start
options = {%i(out err) => io}
options[:pgroup] = true unless Platform.windows? # NOTE: this is a bug only in Windows 7

WebDriver.logger.debug("Starting process: #{@command} with #{options}")
@pid = Process.spawn(*@command, options)
WebDriver.logger.debug(" -> pid: #{@pid}")

Process.detach(@pid) if detach
end

def stop(timeout = 3)
return unless @pid
return if exited?

WebDriver.logger.debug("Sending TERM to process: #{@pid}")
Process.kill(SIGTERM, @pid)
poll_for_exit(timeout)

WebDriver.logger.debug(" -> stopped #{@pid}")
rescue TimeoutError
WebDriver.logger.debug(" -> sending KILL to process: #{@pid}")
Process.kill(SIGKILL, @pid)
wait
WebDriver.logger.debug(" -> killed #{@pid}")
end

def alive?
@pid && !exited?
end

def exited?
return unless @pid

WebDriver.logger.debug("Checking if #{@pid} is exited")
_, @status = Process.waitpid2(@pid, Process::WNOHANG | Process::WUNTRACED) if @status.nil?
return if @status.nil?

WebDriver.logger.debug(" -> exit code is #{@status.exitstatus}")
@status.exited?
end

def poll_for_exit(timeout)
WebDriver.logger.debug("Polling #{timeout} seconds for exit of #{@pid}")

end_time = Time.now + timeout
sleep POLL_INTERVAL until exited? || Time.now > end_time

raise TimeoutError, " -> #{@pid} still alive after #{timeout} seconds" unless exited?
end

def wait
return if exited?

_, @status = Process.waitpid2(@pid)
end

end # ChildProcess
end # Common
end # WebDriver
end # Selenium
12 changes: 2 additions & 10 deletions rb/lib/selenium/webdriver/common/service_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,8 @@ def uri

def build_process(*command)
WebDriver.logger.debug("Executing Process #{command}")
@process = ChildProcess.build(*command)
if WebDriver.logger.debug?
@process.io.stdout = @process.io.stderr = WebDriver.logger.io
elsif Platform.jruby?
# Apparently we need to read the output of drivers on JRuby.
@process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
end
@process = Common::ChildProcess.build(*command)
@process.io = WebDriver.logger.io if WebDriver.logger.debug?

@process
end
Expand All @@ -104,16 +99,13 @@ def find_free_port

def start_process
@process = build_process(@executable_path, "--port=#{@port}", *@extra_args)
# NOTE: this is a bug only in Windows 7
@process.leader = true unless Platform.windows?
@process.start
end

def stop_process
return if process_exited?

@process.stop STOP_TIMEOUT
@process.io.stdout.close if Platform.jruby? && !WebDriver.logger.debug?
end

def stop_server
Expand Down
3 changes: 1 addition & 2 deletions rb/lib/selenium/webdriver/common/socket_lock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ def release

def can_lock?
@server = TCPServer.new(Platform.localhost, @port)
ChildProcess.close_on_exec @server

@server.close_on_exec = true
true
rescue SocketError, Errno::EADDRINUSE, Errno::EBADF => e
WebDriver.logger.debug("#{self}: #{e.message}")
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
require 'selenium/webdriver/support/event_firing_bridge'
require 'selenium/webdriver/support/abstract_event_listener'
require 'selenium/webdriver/support/block_event_listener'
require 'selenium/webdriver/support/child_process'
require 'selenium/webdriver/support/escaper'
require 'selenium/webdriver/support/select'
require 'selenium/webdriver/support/color'
Expand Down
114 changes: 114 additions & 0 deletions rb/lib/selenium/webdriver/support/child_process.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
module Support

#
# @api private
#

class ChildProcess
TimeoutError = Class.new(StandardError)

SIGTERM = 'TERM'
SIGKILL = 'KILL'

POLL_INTERVAL = 0.1

attr_accessor :detach
attr_writer :io

def self.build(*command)
new(*command)
end

def initialize(*command)
@command = command
@detach = false
@pid = nil
@status = nil
end

def io
@io ||= Platform.null_device
end

def start
options = {%i(out err) => io}
options[:pgroup] = true unless Platform.windows? # NOTE: this is a bug only in Windows 7

WebDriver.logger.debug("Starting process: #{@command} with #{options}")
@pid = Process.spawn(*@command, options)
WebDriver.logger.debug(" -> pid: #{@pid}")

Process.detach(@pid) if detach
end

def stop(timeout = 3)
return unless @pid
return if exited?

WebDriver.logger.debug("Sending TERM to process: #{@pid}")
Process.kill(SIGTERM, @pid)
poll_for_exit(timeout)

WebDriver.logger.debug(" -> stopped #{@pid}")
rescue TimeoutError
WebDriver.logger.debug(" -> sending KILL to process: #{@pid}")
Process.kill(SIGKILL, @pid)
wait
WebDriver.logger.debug(" -> killed #{@pid}")
end

def alive?
@pid && !exited?
end

def exited?
return unless @pid

WebDriver.logger.debug("Checking if #{@pid} is exited")
_, @status = Process.waitpid2(@pid, Process::WNOHANG | Process::WUNTRACED) if @status.nil?
return if @status.nil?

WebDriver.logger.debug(" -> exit code is #{@status.exitstatus}")
@status.exited?
end

def poll_for_exit(timeout)
WebDriver.logger.debug("Polling #{timeout} seconds for exit of #{@pid}")

end_time = Time.now + timeout
sleep POLL_INTERVAL until exited? || Time.now > end_time

raise TimeoutError, " -> #{@pid} still alive after #{timeout} seconds" unless exited?
end

def wait
return if exited?

_, @status = Process.waitpid2(@pid)
end

end # ChildProcess
end # Support
end # WebDriver
end # Selenium
3 changes: 0 additions & 3 deletions rb/selenium-webdriver.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,10 @@ Gem::Specification.new do |s|
s.bindir = 'bin'
s.require_paths = ['lib']

s.add_runtime_dependency 'childprocess', ['>= 0.5', '< 5.0']
s.add_runtime_dependency 'rexml', ['~> 3.2', '>= 3.2.5']
s.add_runtime_dependency 'rubyzip', ['>= 1.2.2', '< 3.0']
s.add_runtime_dependency 'websocket', ['~> 1.0']

# childprocess requires ffi on windows but doesn't declare it in its dependencies
s.add_development_dependency 'ffi'
s.add_development_dependency 'pry', ['~> 0.14']
s.add_development_dependency 'rack', ['~> 2.0']
s.add_development_dependency 'rake'
Expand Down

0 comments on commit 75018a3

Please sign in to comment.