Path: blob/trunk/rb/lib/selenium/webdriver/common/service_manager.rb
4062 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# Base class implementing default behavior of service_manager object,23# responsible for starting and stopping driver implementations.24#25# @api private26#27class ServiceManager28START_TIMEOUT = 2029SOCKET_LOCK_TIMEOUT = 4530STOP_TIMEOUT = 203132#33# End users should use a class method for the desired driver, rather than using this directly.34#35# @api private36#3738def initialize(config)39@executable_path = config.executable_path40@host = Platform.localhost41@port = config.port42@io = config.log43@extra_args = config.args44@shutdown_supported = config.shutdown_supported4546raise Error::WebDriverError, "invalid port: #{@port}" if @port < 147end4849def start50raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?5152Platform.exit_hook { stop } # make sure we don't leave the server running5354socket_lock.locked do55find_free_port56start_process57connect_until_stable58end59end6061def stop62return unless @shutdown_supported63return if process_exited?6465stop_server66@process.poll_for_exit STOP_TIMEOUT67rescue ChildProcess::TimeoutError, Errno::ECONNREFUSED68nil # noop69ensure70stop_process71end7273def uri74@uri ||= URI.parse("http://#{@host}:#{@port}")75end7677private7879def build_process(*command)80WebDriver.logger.debug("Executing Process #{command}", id: :driver_service)81@process = ChildProcess.build(*command)82if ENV.key?('SE_DEBUG')83if @io && @io != WebDriver.logger.io84WebDriver.logger.warn('SE_DEBUG is set; overriding user-specified driver log output to use stderr',85id: :se_debug)86end87@io = WebDriver.logger.io88end89@process.io = @io if @io9091@process92end9394def connect_to_server95Net::HTTP.start(@host, @port) do |http|96http.open_timeout = STOP_TIMEOUT / 297http.read_timeout = STOP_TIMEOUT / 29899yield http100end101end102103def find_free_port104@port = PortProber.above(@port)105end106107def start_process108@process = build_process(@executable_path, "--port=#{@port}", *@extra_args)109@process.start110end111112def stop_process113return if process_exited?114115@process.stop STOP_TIMEOUT116end117118def stop_server119connect_to_server do |http|120headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup121WebDriver.logger.debug('Sending shutdown request to server', id: :driver_service)122http.get('/shutdown', headers)123end124end125126def process_running?127defined?(@process) && @process&.alive?128end129130def process_exited?131@process.nil? || @process.exited?132end133134def connect_until_stable135deadline = current_time + START_TIMEOUT136137loop do138error = check_connection_error139return unless error140141raise Error::WebDriverError, "#{cannot_connect_error_text}: #{error}" if current_time > deadline142143sleep 0.1144end145end146147def check_connection_error148response = Net::HTTP.start(@host, @port, open_timeout: 0.5, read_timeout: 1) do |http|149http.get('/status', {'Connection' => 'close'})150end151152return "status returned #{response.code}\n#{response.body}" unless response.is_a?(Net::HTTPSuccess)153154status = JSON.parse(response.body)155ready = status['ready'] || status.dig('value', 'ready')156"driver not ready: #{response.body}" unless ready157rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT,158Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout,159EOFError, SocketError, Net::HTTPBadResponse, JSON::ParserError => e160"#{e.class}: #{e.message}"161end162163def current_time164Process.clock_gettime(Process::CLOCK_MONOTONIC)165end166167def cannot_connect_error_text168"unable to connect to #{@executable_path} #{@host}:#{@port}"169end170171def socket_lock172@socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)173end174end # Service175end # WebDriver176end # Selenium177178179