celluloid / reel

UNMAINTAINED: See celluloid/celluloid#779 - Celluloid::IO-powered web server
https://celluloid.io
MIT License
595 stars 87 forks source link

Celluloid::ActorProxy#tap can crash Reel #128

Closed benlangfeld closed 10 years ago

benlangfeld commented 10 years ago

Take an initial working example:

require 'reel'
require 'json'

class Foo
  include Celluloid

  def bar
    raise 'FooBar'
  end
end

Reel::Server.supervise('0.0.0.0', 10000) do |connection|
  connection.each_request do |request|
    begin
      foo = Foo.new
      foo.bar

      request.respond :ok, { message: "All ok" }.to_json
    rescue => e
      request.respond :not_found, { error: e.message }.to_json
    end
  end
end

sleep

The following happens:

➭ curl http://localhost:10000/foo    
{"error":"FooBar"}%
E, [2014-01-03T15:50:44.503965 #13641] ERROR -- : Foo crashed!
RuntimeError: FooBar
    foo.rb:8:in `bar'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `public_send'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:67:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:322:in `block in handle_message'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'

Great! Now tap hard on the Foo (yeah...) and it all goes to hell:

require 'reel'
require 'json'

class Foo
  include Celluloid

  def bar
    raise 'FooBar'
  end
end

Reel::Server.supervise('0.0.0.0', 10000) do |connection|
  connection.each_request do |request|
    begin
      Foo.new.tap do |foo|
        foo.bar
      end

      request.respond :ok, { message: "All ok" }.to_json
    rescue => e
      request.respond :not_found, { error: e.message }.to_json
    end
  end
end

sleep
➭ curl http://localhost:10000/foo                                                                                                                                     
{"error":"task was terminated"}%
E, [2014-01-03T15:37:47.965940 #11332] ERROR -- : Foo crashed!
RuntimeError: FooBar
    foo.rb:8:in `bar'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `public_send'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:67:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:322:in `block in handle_message'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'
E, [2014-01-03T15:37:47.966102 #11332] ERROR -- : Reel::Server crashed!
RuntimeError: FooBar
    foo.rb:8:in `bar'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `public_send'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:67:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:322:in `block in handle_message'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'
    (celluloid):0:in `remote procedure call'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:92:in `value'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/proxies/sync_proxy.rb:33:in `method_missing'
    foo.rb:16:in `block (3 levels) in <main>'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:146:in `call'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:146:in `dispatch'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:325:in `block in handle_message'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'
    /usr/local/opt/rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'
W, [2014-01-03T15:37:47.966211 #11332]  WARN -- : Terminating task: type=:call, meta={:method_name=>:tap}, status=:invokeblock
W, [2014-01-03T15:37:47.966898 #11332]  WARN -- : Terminating task: type=:call, meta={:method_name=>:run}, status=:iowait
W, [2014-01-03T15:37:47.967326 #11332]  WARN -- : Terminating task: type=:call, meta={:method_name=>:handle_connection}, status=:callwait

/cc @bklang

tarcieri commented 10 years ago

It's likely due to #tap executing on the caller, which in this case is Reel. The block is crashing, and taking the entire actor with it.

Indeed adding execute_block_on_receiver :tap to Foo appears to solve the problem. Perhaps this should be a default for Celluloid.

If you don't like this behavior, I'd suggest opening a Celluloid issue rather than a Reel one.