Skip to content

Instantly share code, notes, and snippets.

@mloughran
Last active September 27, 2015 17:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mloughran/1308932 to your computer and use it in GitHub Desktop.
Save mloughran/1308932 to your computer and use it in GitHub Desktop.
Firefox orphaned WebSocket connection testcase

The example code uses ruby and the bundler dependency system which you can install with

gem install bundler

Install dependencies using bundler

bundle install

Start simple websocket server

ruby echo-ws.rb

In another terminal start the web app

ruby app.rb

To reproduce

  • Open http://localhost:4567/
  • Click 'Test page'
  • Verify that connection is opened in echo-ws
  • Click HOME
  • Note that first connection is closed, but a mysterious new connection is opened

This is the expected output by echo-ws.rb

Opened 1
Closed 1
Opened 2

Affected browsers

Firefox 9: Tried to workaround this by using a timeout but it doesn't work unless the timeout quite large.

The transition seems to happen at around 105ms. Using a 103ms timer, connection usually creates a ghost conn, 104ms about half the time, and at 105ms it almost never does (note: only tested on OS X Lion). You can verify by changing the onclose function in test.erb to

ws.onclose = function() {
  setTimeout(connect, 103);
};

Firefox 13: If anything this seems even more broken than 9.

Timeout 0 case: onclose is called (with wasClean = true) when you click to navigate away from the page, then a new connection is opened (by the onclose handler). This connection then appears to be closed by firefox almost instantly (presumably this is the workaround to this bug, so that the ghost connection is not left lying about).

Timeout of 90ms in onclose: Exactly the same behaviour as case 0ms above.

Timeout of 100ms in onclose: Sometimes behaves as 0ms, sometimes as 103ms.

Timeout of 103ms in onclose: Inconsistent behaviour - there is definitely a race condition in firefox here. Usually this works perfectly (i.e. existing connection is closed and new one isn't opened), but sometimes a new connection is established, and it's left around as a ghost connection after exiting the page. This is basically the behaviour I saw when we initially reported this issue, but it's even worse: The TCP connection actually stays around even when the browser tab is closed, and is only closed when Firefox quits!

My hunch based on all this experimentation is that if a WebSocket connection is in the process of opening when the page is finally cleaned up by firefox (which seems to be after ~ 105ms), that the connection is orphaned forever. This could potentially leave Firefox amassing large numbers of ghost WebSocket connections.

Firefox 17: Same behaviour as Firefox 13, including leaving the WebSocket open after the browser tab is closed with some values of timeout (described above)

require 'bundler/setup'
require 'sinatra'
set :views, settings.root
get '/' do
# THIS IS CRITICAL
sleep 0.1
erb :index
end
get '/test.html' do
erb :test
end
require 'bundler/setup'
require 'em-websocket'
i = 0
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
i += 1
ws.onopen { puts "Opened #{i}"}
ws.onmessage { |msg| ws.send "Pong: #{msg}" }
ws.onclose { puts "Closed #{i}" }
# ws.onerror { |e| puts "Error: #{e.message}" }
end
source :rubygems
gem 'em-websocket'
gem 'sinatra'
GEM
remote: http://rubygems.org/
specs:
addressable (2.2.8)
em-websocket (0.3.6)
addressable (>= 2.1.1)
eventmachine (>= 0.12.9)
eventmachine (0.12.10)
rack (1.3.5)
rack-protection (1.1.4)
rack
sinatra (1.3.1)
rack (~> 1.3, >= 1.3.4)
rack-protection (~> 1.1, >= 1.1.2)
tilt (~> 1.3, >= 1.3.3)
tilt (1.3.3)
PLATFORMS
ruby
DEPENDENCIES
em-websocket
sinatra
<!DOCTYPE html>
<head>
<title>Firefox orphaned connection test</title>
<body>
<h1>Home</h1>
<p>This page does not open a websocket connection</p>
<a href="/test.html">Test page</a>
</body>
<!DOCTYPE html>
<head>
<title>Firefox orphaned connection test</title>
<script type="text/javascript">
var Socket = window['MozWebSocket'] || window['WebSocket']
function log(msg) {
var node = document.createElement('p');
node.innerHTML = msg;
document.getElementById('log').appendChild(node);
}
if (Socket) {
(function connect() {
var ws = new Socket('ws://localhost:8080/')
ws.onopen = function() { log('onopen'); };
ws.onclose = function() {
connect();
// setTimeout(connect, 103);
};
ws.onmessage = function(evt) { log('onmessage: '+evt.data); };
})();
}
</script>
</head>
<body>
<h1>Test page</h1>
<p>This page opens a websocket connection to ws://localhost:8080/</p>
<a href="/">HOME</a>
<div id="log">
</div>
</body>
@3rd-Eden
Copy link

@mloughran do you happen to have a bug report reference for this issue?

EDIT: https://bugzilla.mozilla.org/show_bug.cgi?id=765738

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment