3

I have a Pinoccio microcontroller (absolutely awesome, try them). The microcontroller opens a socket to it's server. I am writing that TCP Socket server in a Ruby application, in which I will use Celluloid::IO. As my guide, I am following this implementation in Node, called pinoccio-server

I wrote some test code, to try to communicate with Pinoccio microcontroller. I can read from it without a problem, but when I write data back to the socket, I never get the behavior I expect. Here is the code, can someone tell me if I'm misusing Celluloid::IO or sockets?

https://gist.github.com/roder/ab211f2f58ad6c90a3a9

roder
  • 566
  • 6
  • 13
  • There is one thing that immediately looks weird to me. `@server.accept` usually is a blocking call, meaning it will wait for a connection before returning the socket. You are executing this asynchronously in a loop, which means that you start essentially start a ton of threads all waiting on the socket. It seems like the correct thing to do is accept the connection and then handle it asynchronously. Also, one problem you might be having is the loop that does `socket.readpartial(4096)`. Maybe you should try calling `socket.close` to ensure the client knows the read the reaminder. – Max Oct 09 '14 at 06:41
  • 1
    @Max Ruby calculates arguments first, therefore `async.handle_connection` won't be called until `@server.accept` returns a value – Daniel Vartanov Jan 14 '15 at 14:56

1 Answers1

0

At the time if this question, the syntax was right. But now it's not, as of 0.17.0 of Celluloid ... but that's not the answer, that's just a preface. I've forked and edited your gist:

https://gist.github.com/digitalextremist/7dc74b03587cd4b3b7dd

Here is the new gist:

require 'celluloid/current'
require 'celluloid/io'
require 'json'

class TestServer
  include Celluloid::IO
  finalizer :shutdown

  def initialize(host, port)
    puts "*** Starting echo server on #{host}:#{port}"

    # Since we included Celluloid::IO, we're actually making a
    # Celluloid::IO::TCPServer here
    @server = TCPServer.new(host, port)
  end

  def shutdown
    @server.close rescue nil
  end

  def run
    loop {
      async.handle_connection(@server.accept)
    }
  end

  def handle_connection(socket)
    addr = *socket.peeraddr
    puts "*** Received connection from #{addr[3]}:#{addr[1]}"

    # This is just a test, followed this format:
    #  https://github.com/soldair/pinoccio-server/blob/master/troop.js#L163
    cmd = {
      :type => "command",
      :to => 1,
      :timeout => 10000,
      :command => "led.on"
    }
    json_cmd = "#{cmd.to_json}\n"
    socket.write json_cmd # The light never turns on. Why?
    puts json_cmd

    loop {
      puts socket.readpartial(4096)
    }
  rescue EOFError
    puts "*** #{addr[3]}:#{addr[1]} disconnected"
  rescue => ex
    echo "Trouble with socket: [#{ex.class}] #{ex.message}"
  ensure
    socket.close rescue nil
  end
end

TestServer.supervise(as: :test_server, args: ["0.0.0.0", 1234])
#de Not needed. Killed by Celluloid.shutdown already...
#de trap("INT") { supervisor.terminate; exit }

#de Avoid starting the server in the constructor, then sleeping.
#de Could end up with a race condition, and/or botch instantiation.
#de But with that being said, you need to detect crashes... so:
loop {
  begin
    Celluloid[:test_server].run
  rescue => ex
    echo "Trouble with supervised server: [#{ex.class}] #{ex.message}"
    echo "Waiting 1.26 seconds for actor to be reinstantiated."
    sleep 1.26
  end
}

Notable changes:

  1. Invoke Celluloid first, and let it spin up properly.
  2. No longer start the server in initialize.
  3. Catch the death of the server, and restart it after the actor is reinstantiated.
  4. Become compliant with the new API.
  5. Forcefully, silently close server at end.
  6. Forcefully, silently close socket at disconnect.
  7. Avoid duplicating shutdown already started by Celluloid itself.
  8. Avoid using the hostname, which is often not there. Show numeric address.
  9. Catch the type of error that'd be caused by a write, and show you the reason.
  10. Closure of the socket will always happen, regardless of how the socket dies.

The code you originally started from should have worked, or at least it ought to have given you a clear reason why it wasn't working. The above code is now compatible with Celluloid as of 0.17.0 and same for the new Celluloid::IO ... if you have any further problems, please include the specific error.

Note: You will need to possibly detect that the server crashed and is still holding open the port you expect to be available, and that is not included in the example... but an example of that is given in the Celluloid::IO test suite:

digitalextremist
  • 5,952
  • 3
  • 43
  • 62