Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/ferrum/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ def close
@pendings.clear
@thread.kill unless @thread.join(1)
@subscriber.close
@ws.force_close
end

#
Expand Down
18 changes: 18 additions & 0 deletions lib/ferrum/client/web_socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,24 @@ def close
@driver_mutex.synchronize { @driver.close }
end

#
# Closes the underlying socket whether or not the close handshake ever
# completed.
#
# {#close} only sends a close frame; the socket itself is closed by
# {#on_close}, which runs when the browser answers with a close frame of
# its own. A browser that is already dead never answers, so without this
# the socket stays open until GC finalizes it -- long enough to exhaust
# the process's file descriptors when browsers are restarted in a loop.
#
# @return [void]
#
def force_close
@sock.close unless @sock.closed?
rescue IOError, SystemCallError
# nop
end

private

def start
Expand Down
2 changes: 2 additions & 0 deletions sig/ferrum/client/web_socket.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ module Ferrum

def close: () -> void

def force_close: () -> void

private

def start: () -> void
Expand Down
10 changes: 10 additions & 0 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ def messages
expect(Ferrum::Utils::ElapsedTime.elapsed_time(start)).to be < 1
end

it "closes the socket when the browser dies before answering the close frame" do
remote.create_page
socket = remote.client.instance_variable_get(:@ws).instance_variable_get(:@sock)
Process.kill("KILL", -remote.process.pid)

remote.quit

expect(socket).to be_closed
end

it "reports an unexpected reader error instead of raising in the main thread" do
page = remote.create_page
driver = remote.client.instance_variable_get(:@ws).instance_variable_get(:@driver)
Expand Down
Loading