Feature: Add `after_request_complete` hook
Is your feature request related to a problem? Please describe.
I want a bounded worker lifetime in cluster mode: recycle a worker gracefully after it completes N requests. All I need from Puma for that is a callback that runs once per completed request inside the worker.
There's no such hook for now, so I do that in a Rack middleware instead, like this:
class PumaWorkerRecyclerMiddleware
def call(env)
status, headers, body = @app.call(env)
[status, headers, BodyWrapper.new(body) { record_request }]
end
private def record_request
requests_count = nil
@mutex.synchronize do
@count += 1
requests_count = @count
end
if requests_count > @max_requests_count
Process.kill('TERM', Process.pid) # retire a worker process
end
end
endMy actual code has more lines, though.
Describe the solution you'd like
after_request_complete do |server|
# worker process, request thread, after the response body has been written
# server.requests_count is the per-worker completed-request count
end- Runs after every completed request, whatever the thread count and whether or not other threads are busy.
- Has to be fast. Unlike the
out_of_bandhook, it should not hold off the accept loop. - If the new hook raises, Puma rescues and logs it, the same way
trigger_out_of_band_hookdoes. - Runs inline on the request thread, so Puma doesn't need a new thread for it.
This isn't asking Puma to own any policy. No thresholds, no defaults, nothing that reads memory, nothing that kills a worker. My middleware would go away and config/puma.rb would get one line like this:
after_request_complete do |server|
PumaWorkerRecycler.check(server.requests_count)
endDescribe alternatives you've considered
out_of_band is the closest thing that exists, but it doesn't fit. See the doc and implementation below:
- https://github.com/puma/puma/blob/8085b75e79e3f7f1a96e5b488d74a71f62edd24d/lib/puma/dsl.rb#L1039-L1041
- https://github.com/puma/puma/blob/8085b75e79e3f7f1a96e5b488d74a71f62edd24d/lib/puma/thread_pool.rb#L291-L295
- https://github.com/puma/puma/blob/8085b75e79e3f7f1a96e5b488d74a71f62edd24d/lib/puma/server.rb#L387-L389
Both of those are right for out-of-band GC, which is what the hook is for, so I'm not asking to change it.
Additional context
Pitchfork has this hook under the same name, after_request_complete, described as:
Can be used for out of band work, or to exit unhealthy workers.
Same stance as Puma on not shipping a killer, but with the hook available, the implementation is about five lines.
Also, there were similar requests, but rejected:
- #1656
- #2659
I'm not trying to reopen either. Policy can stay outside Puma.
Puma already recycles workers based on a request count in fork_worker, so a request-count-driven worker lifecycle isn't foreign here. The name follows the before_* / after_* convention from the v7.0 rename.
I'd be happy to discuss the hook idea if you're open to it. Thank you.
Source: puma/puma