Which timeout layer should cancel a long-running Rails request without corrupting the DB connection
0 reputation · 03 Dec 2021, 00:19 UTC
0 reputation · 03 Dec 2021, 00:19 UTC
A Rails application needs a consistent policy for aborting requests that exceed an acceptable duration while keeping the PostgreSQL connection pool usable for subsequent work. The design involves Rack::Timeout for the Rack stack, ActiveSupport::Timeout::permit for wrapping blocking sections, and database-level cancellation via the pg gem’s PG::Connection#cancel.
Rack::Timeout can abort a request after a configurable idle or total time and raises Rack::Timeout::RequestTimeoutError. It operates in middleware and does not apply to background job processes. ActiveSupport::Timeout::permit can wrap blocking operations and raises ActiveSupport::Timeout::Error after a specified duration. Database query cancellation is possible with PG::Connection#cancel, which sends a SIGINT to the PostgreSQL backend, but it requires a native C extension connection and is noted as not thread-safe, with potential PG::UnableToSend errors under concurrent use. ActiveJob provides no built-in cooperative cancellation.
With these documented behaviors, which timeout mechanism should be the authoritative signal for a request that is blocked on ActiveRecord? Does Rack::Timeout guarantee cleanup of an in-flight database query or connection state? When is ActiveSupport::Timeout::permit appropriate relative to Rack::Timeout middleware ordering for graceful cancellation?
35275 reputation · 03 Dec 2021, 06:22 UTC
The authoritative signal for canceling a long-running Rails request that is blocked on ActiveRecord should be ActiveSupport::Timeout::permit. It raises a Ruby exception that can be rescued to explicitly cancel the underlying PostgreSQL query via PG::Connection#cancel, returning the connection to the pool in a clean state.
Rack::Timeout aborts the entire request thread by raising Rack::Timeout::RequestTimeoutError; it does not invoke any database‑specific cleanup, so an in‑flight query may remain active unless the application catches the error and issues a cancel.PG::Connection#cancel sends a PostgreSQL backend SIGINT, but it is not thread‑safe and must be called from the same thread that holds the native connection.ActiveSupport::Timeout::permit times out, rescuing ActiveSupport::Timeout::Error and calling PG::Connection#cancel returns the connection to the pool idle, as verified by pool statistics and PostgreSQL logs.ActiveSupport::Timeout::permit(seconds).ActiveSupport::Timeout::Error.ActiveRecord::Base.connection.raw_connection) and call #cancel on it.Rack::Timeout as a outer safety net; its rescue block should also invoke PG::Connection#cancel (or check‑in the connection) to avoid leaving a corrupted connection.If you are using a connection wrapper or middleware that swallows ActiveSupport::Timeout::Error before your rescue block runs, the explicit cancel will not happen. Verify that the timeout exception propagates to the layer where you perform the cancel.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.