Gracefully disconnect a WebSocket connection with Celluloid

502 views Asked by At

Situation

I connect to a WebSocket with Chrome's Remote Debugging Protocol, using a Rails application and a class that implements Celluloid, or more specifically, celluloid-websocket-client.

The problem is that I don't know how to disconnect the WebSocket cleanly.

When an error happens inside the actor, but the main program runs, Chrome somehow still makes the WebSocket unavailable, not allowing me to attach again.

Code Example

Here's the code, completely self-contained:

require 'celluloid/websocket/client'

class MeasurementConnection

  include Celluloid

  def initialize(url)
    @ws_client = Celluloid::WebSocket::Client.new url, Celluloid::Actor.current
  end

  # When WebSocket is opened, register callbacks
  def on_open
    puts "Websocket connection opened"
    # @ws_client.close to close it
  end

  # When raw WebSocket message is received
  def on_message(msg)
    puts "Received message: #{msg}"
  end

  # Send a raw WebSocket message
  def send_chrome_message(msg)
    @ws_client.text JSON.dump msg
  end

  # When WebSocket is closed
  def on_close(code, reason)
    puts "WebSocket connection closed: #{code.inspect}, #{reason.inspect}"
  end

end

MeasurementConnection.new ARGV[0].strip.gsub("\"","")
while true
  sleep
end

What I've tried

  • When I uncomment @ws_client.close, I get:

    NoMethodError: undefined method `close' for #<Celluloid::CellProxy(Celluloid::WebSocket::Client::Connection:0x3f954f44edf4)
    

    But I thought this was delegated? At least the .text method works too?

  • When I call terminate instead (to quit the Actor), the WebSocket is still opened in the background.

  • When I call terminate on the MeasurementConnection object that I create in the main code, it makes the Actor appear dead, but still does not free the connection.

How to reproduce

You can test this yourself by starting Chrome with --remote-debugging-port=9222 as command-line argument, then checking curl http://localhost:9222/json and using the webSocketDebuggerUrl from there, e.g.:

ruby chrome-test.rb $(curl http://localhost:9222/json 2>/dev/null | grep webSocket | cut -d ":" -f2-)

If no webSocketDebuggerUrl is available, then something is still connecting to it.

It used to work when I was using EventMachine similar to this example, but not with faye/websocket-client, but em-websocket-client instead. Here, upon stopping the EM loop (with EM.stop), the WebSocket would become available again.

1

There are 1 answers

5
slhck On BEST ANSWER

I figured it out. I used the 0.0.1 version of the celluloid-websocket-client gem which did not delegate the close method.

Using 0.0.2 worked, and the code would look like this:

In MeasurementConnection:

def close
  @ws_client.close
end

In the main code:

m = MeasurementConnection.new ARGV[0].strip.gsub("\"","")
m.close
while m.alive?
  m.terminate
  sleep(0.01)
end