1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Prevent connections from entering Reactor after shutdown begins (#2377)

When a `Puma::Server` instance starts to shutdown, it sets the instance
variable `@queue_requests` to `false`, then starts to shut down the
Reactor. The intent here is that after the Reactor shuts down, threads
won't have the option of sending their connections to the Reactor (the
connection must either be closed, assuming we've written a response to
the socket, or the thread must wait until the client has finished
writing its request, then write a response, then finally close the
connection). This works most of the time just fine.

The problem is that there are races in the implementation of the
`ThreadPool` where it's possible for a thread to see that
`@queue_requests` is `true` before the `Server` shutdown has started,
then later try to add the request to the Reactor, not knowing that it's
already shutting down.

Depending on the precise order of operations, one possible outcome is
that the `ThreadPool` executes `@reactor.add` after the `@reactor` has
closed its `@trigger` pipe, resulting in the error `Error reached top of
thread-pool: closed stream (IOError)`. Clients experience connection
reset errors as a result.

The fix introduced in this commit is to add a `Mutex` that makes it
impossible for a thread in the `ThreadPool` to add a client connection
to the `@reactor` after `@queue_requests` has been set to `false`.

Co-Authored-By: Leo Belyi <leonid.belyi@mycase.com>

Co-authored-by: Leo Belyi <leonid.belyi@mycase.com>
This commit is contained in:
Chris LaRose 2020-09-23 07:34:10 -07:00 committed by GitHub
parent e041d0739b
commit 7d4df2931a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 20 additions and 13 deletions

View file

@ -4,7 +4,7 @@
* Your feature goes here (#Github Number) * Your feature goes here (#Github Number)
* Bugfixes * Bugfixes
* Your bugfix goes here (#Github Number) * Prevent connections from entering Reactor after shutdown begins (#2377)
* Refactor * Refactor
* Change Events#ssl_error signature from (error, peeraddr, peercert) to (error, ssl_socket) (#2375) * Change Events#ssl_error signature from (error, peeraddr, peercert) to (error, ssl_socket) (#2375)

View file

@ -95,6 +95,8 @@ module Puma
@precheck_closing = true @precheck_closing = true
@requests_count = 0 @requests_count = 0
@shutdown_mutex = Mutex.new
end end
def inherit_binder(bind) def inherit_binder(bind)
@ -247,12 +249,13 @@ module Puma
@events.connection_error e, client @events.connection_error e, client
else else
if process_now process_now ||= @shutdown_mutex.synchronize do
process_client client, buffer next true unless @queue_requests
else client.set_timeout @first_data_timeout
client.set_timeout @first_data_timeout @reactor.add client
@reactor.add client false
end end
process_client client, buffer if process_now
end end
process_now process_now
@ -338,7 +341,9 @@ module Puma
@events.fire :state, @status @events.fire :state, @status
if queue_requests if queue_requests
@queue_requests = false @shutdown_mutex.synchronize do
@queue_requests = false
end
@reactor.clear! @reactor.clear!
@reactor.shutdown @reactor.shutdown
end end
@ -418,11 +423,13 @@ module Puma
end end
unless client.reset(check_for_more_data) unless client.reset(check_for_more_data)
return unless @queue_requests @shutdown_mutex.synchronize do
close_socket = false return unless @queue_requests
client.set_timeout @persistent_timeout close_socket = false
@reactor.add client client.set_timeout @persistent_timeout
return @reactor.add client
return
end
end end
end end
end end