Created
April 29, 2011 23:43
-
-
Save jarib/949241 to your computer and use it in GitHub Desktop.
WebDriver port pool / no-lock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pool = WebDriverPortPool.new(8) | |
port = pool.get | |
driver = Selenium::WebDriver::Driver.new(NoLockFirefoxBridge.new(:port => port)) | |
# do some work | |
driver.quit | |
pool.release port | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class NoLockFirefoxBridge < Selenium::WebDriver::Firefox::Bridge | |
class FakeLock | |
def initialize(*args) | |
end | |
def locked | |
yield | |
end | |
end | |
class NoLockLauncher < Selenium::WebDriver::Firefox::Launcher | |
private | |
def find_free_port | |
end | |
def socket_lock | |
@socket_lock ||= FakeLock.new | |
end | |
end | |
def create_launcher(port, profile) | |
NoLockLauncher.new Selenium::WebDriver::Firefox::Binary.new, port, profile | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "socket" | |
class WebDriverPortPool | |
class TooManyPortsError < StandardError; end | |
class TimeoutError < StandardError; end | |
class BindError < StandardError; end | |
START_PORT = 1024 | |
HOST = "127.0.0.1" | |
RELEASE_TIMEOUT = 20 | |
PORT_INCREMENT = 2 | |
def initialize(capacity) | |
@last_port = START_PORT - 1 | |
@capacity = capacity | |
@servers = [] | |
@running = [] | |
bind | |
end | |
def get | |
if @servers.empty? | |
STDERR.puts "out of ports, finding next" | |
@servers << next_server | |
end | |
s = @servers.shift | |
port = s.addr.fetch(1) | |
s.close | |
wait_while_listening port | |
port | |
end | |
def release(port) | |
raise TooManyPortsError if @servers.size == @capacity | |
server = wait_for_server(port) | |
@servers << server | |
rescue WebDriverPortPool::TimeoutError | |
STDERR.puts "timed out while releasing #{port}" | |
end | |
def stop | |
@servers.each { |s| | |
begin | |
s.close | |
rescue IOError | |
end | |
} | |
end | |
def close_on_exec | |
STDERR.puts "#{self.class}: #{Process.pid} pool closed on exec" | |
@servers.each { |s| ChildProcess.close_on_exec s } | |
end | |
def size | |
@servers.size | |
end | |
private | |
def bind | |
@servers << next_server until size == @capacity | |
end | |
def next_server | |
max_ports = @capacity*100 | |
upper_bound = START_PORT + max_ports | |
@last_port += PORT_INCREMENT | |
until server = try_bind(@last_port) | |
if @last_port > upper_bound | |
raise BindError, "unable to find free port within #{START_PORT}..#{upper_bound} tries, last port tried #{@last_port}" | |
end | |
@last_port += PORT_INCREMENT | |
end | |
server | |
end | |
def try_bind(port) | |
server_for port | |
rescue SocketError, Errno::EADDRINUSE | |
# ok | |
end | |
def server_for(port) | |
s = TCPServer.new(HOST, port) | |
ChildProcess.close_on_exec s # make sure browsers don't inherit the file descriptors | |
s | |
end | |
def wait_for_server(port) | |
wait_until(RELEASE_TIMEOUT) { | |
res = try_bind port | |
# p `lsof -i TCP:#{port}` unless res | |
res | |
} | |
end | |
def wait_while_listening(port) | |
wait_until(RELEASE_TIMEOUT) { | |
not listening?(port, 5) | |
} | |
end | |
def wait_until(timeout, &blk) | |
max_time = Time.now + timeout | |
until res = yield | |
if Time.now >= max_time | |
raise TimeoutError, "timed out" | |
else | |
sleep 0.1 | |
end | |
end | |
res | |
end | |
def listening?(port, timeout = nil) | |
addr = Socket.getaddrinfo(HOST, nil) | |
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0) | |
if timeout | |
secs = Integer(timeout) | |
usecs = Integer((timeout - secs) * 1_000_000) | |
optval = [secs, usecs].pack("l_2") | |
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval | |
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval | |
end | |
sock.connect(Socket.pack_sockaddr_in(port, addr[0][3])) | |
sock.close | |
true | |
rescue => e | |
false | |
end | |
end # WebDriverPortPool |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment