celluloid / reel

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

reel 0.5.0: Reel::StateError: already processing a request when client is killed #150

Closed rakvat closed 9 years ago

rakvat commented 10 years ago

I have a minimal reel server copied from the "Subclass Form" reel example that does some work before sending the response. When I run it with reel 0.5.0. and http 0.6.1, send curl requests to the server and do 'pkill -f curl' while requests are running, the reel server crashes with 'Reel::StateError: already processing a request'. The same setup with reel 0.4.0 and http 0.5.0 does not crash.

tarcieri commented 10 years ago

This is due to the introduction of pipelining, which requires that you completely consume the request before trying to process the next.

Perhaps Reel should do this implicitly, or let you turn pipelining off.

Until then, you need to consume the entire request body.

rakvat commented 10 years ago

Is there a way to make sure that the entire request body is consumed? Does this also work if a curl client is killed or a browser rendering a web application that uses a reel server as back-end refreshes at the "wrong" moment?

tarcieri commented 10 years ago

You can call #to_str on the body. There should probably be a #flush method that does the same thing (and an option to auto-flush)

rakvat commented 10 years ago

As far as I understand calling to_str on the body does not help as the server crashes before accessing the body. (Line 14 in the following gist: https://gist.github.com/rakvat/b0ff391fdd531873c8f6)

tarcieri commented 10 years ago

The first request doesn't go through?

My guess is you're trying to read the next request before the first request's body has been consumed.

myronmarston commented 10 years ago

I'm seeing similar behavior. I tried out your barebones hello world example and hit it with apache bench to try out some concurrency. It crashed:

*** Starting server on http://127.0.0.1:1234
E, [2014-06-06T17:09:33.156908 #27473] ERROR -- : Reel::Server::HTTP crashed!
Reel::StateError: already processing a request
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/reel-0.5.0/lib/reel/connection.rb:55:in `request'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/reel-0.5.0/lib/reel/connection.rb:72:in `each_request'
    lib/reel_example.rb:13:in `block in <main>'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/reel-0.5.0/lib/reel/server.rb:56:in `call'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/reel-0.5.0/lib/reel/server.rb:56:in `handle_connection'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `public_send'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `dispatch'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/calls.rb:122:in `dispatch'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:322:in `block in handle_message'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'
    /Users/myron/moz/shares_r_us/bundle/ruby/2.1.0/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'
W, [2014-06-06T17:09:33.157470 #27473]  WARN -- : Terminating task: type=:call, meta={:method_name=>:run}, status=:iowait

I tried adding request.body.to_str as recommended here and it didn't help.

rakvat commented 10 years ago

I debugged the issue with reel-0.5.0 and option spy: true. (The setup is still parallel curl requests, pkill -f curl, reel crashes.) There are many requests going through before I kill curl. The normal flow in lib/reel/connection.rb is 1) client connects, 2) initialize sets current_request to nil, 3) each_request calls request which sets current_request to a request object, 4) connection#respond gets called which calls current_request.handle_response and then resets current_request to nil.

When I kill curl (simulating a browser refresh) the flow in connection.rb is 1), 2), 3) as above, 4) current_request.handle_response raises a Broken Pipe Exception. Thus current_request is not reset to nil. The next time each_request calls request a StateError is raised as current_request is not nil.

I hope this helps.