Skip to content

Comments

Add support for rack.response_finished (#3681)#9

Open
MitchLewis930 wants to merge 1 commit intopr_059_beforefrom
pr_059_after
Open

Add support for rack.response_finished (#3681)#9
MitchLewis930 wants to merge 1 commit intopr_059_beforefrom
pr_059_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 30, 2026

PR_059

Summary by CodeRabbit

  • New Features

    • Apps can register post-response callbacks that run after a response finishes, receiving the final env, status, headers, and error for cleanup and logging.
    • Introduced a new response-finished indicator used by these callbacks.
  • Tests

    • Added tests validating callback invocation order and correct behavior on success and error.

* Add support for `rack.response_finished`

It is very similar to `rack.after_reply`, but is part of the Rack spec.

It can't just be an alias because the spec state it has to
invoke callbacks in reverse order.

Fix: puma#3631
Ref: rack/rack#1777
Ref: rack/rack#1802

Co-Authored-By: Robert Laurin <robert.laurin@shopify.com>

* request.rb - more rename `e` to `error` changes

---------

Co-authored-by: Robert Laurin <robert.laurin@shopify.com>
Co-authored-by: MSP-Greg <Greg.mpls@gmail.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds support for the Rack rack.response_finished callback: new constant and environment key, request handling to collect and invoke post-response callbacks (with error propagation), and tests covering normal and error cases.

Changes

Cohort / File(s) Summary
Constants & Env Init
lib/puma/const.rb, lib/puma/binder.rb
Added RACK_RESPONSE_FINISHED constant and initialized RACK_AFTER_REPLY and RACK_RESPONSE_FINISHED entries in the Rack environment (proto_env).
Request handling & callbacks
lib/puma/request.rb
Introduced local error capture in handle_request, ensured env[RACK_RESPONSE_FINISHED] defaults to an array, and added post-response execution of rack.response_finished callbacks (invoked in reverse order, receiving (env, status, headers, error), with per-callback exceptions debug-logged).
Tests
test/test_rack_server.rb
Added test_rack_response_finished and test_rack_response_finished_on_error to assert callback invocation order, arguments, and error passing. Updated server initialization in tests to supply Puma::LogWriter.null.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant "Puma Request\nHandler" as Handler
    participant "Rack App" as App
    participant "Response\nCallbacks" as Callbacks

    Client->>Handler: HTTP request
    Handler->>App: call env
    App-->>Handler: status, headers, body
    Handler->>Client: stream/send response

    Note over Handler: Response already sent

    Handler->>Callbacks: run env['rack.response_finished'] (reverse)
    loop each callback
        Callbacks->>Callbacks: invoke callback(env,status,headers,error)
        Callbacks-->>Handler: callback errors handled via debug logging
    end
    Handler->>Handler: finalize request
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I nibbled code and hopped with glee,
Callbacks queued for after-response tea,
In reverse they leap, no errors to dread,
A quiet finish before bed. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is incomplete and missing critical information required by the template, including problem statement, motivation, alternative approaches, and checklist items. Replace the description with details about the original problem, related issues, decision rationale, and complete the provided checklist to document testing, documentation updates, and compliance with guidelines.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding support for the rack.response_finished feature, which aligns with all modifications across the codebase.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into pr_059_before

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr_059_after

Comment @coderabbitai help to get the list of available commands and usage tips.

@MitchLewis930
Copy link
Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/puma/request.rb`:
- Around line 150-158: The callbacks in the RACK_RESPONSE_FINISHED handling can
receive nil because local variables status and headers aren't set on early
returns in handle_request (e.g., closed_socket? and the 413 branch); ensure
status and headers are initialized and updated on those paths so callbacks get
the real response values: initialize status and headers before calling
handle_request (or set them inside the closed_socket? and 413 branches) and
propagate those values into the response_finished.each callback invocation (the
response_finished block that calls o.call(env, status, headers, error)); update
any early-return branches to assign the correct numeric/status and header hash
rather than leaving them nil.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b2b58e and 1b08ed7.

📒 Files selected for processing (4)
  • lib/puma/binder.rb
  • lib/puma/const.rb
  • lib/puma/request.rb
  • test/test_rack_server.rb
🧰 Additional context used
🧬 Code graph analysis (2)
test/test_rack_server.rb (1)
lib/puma/log_writer.rb (1)
  • null (56-59)
lib/puma/request.rb (2)
lib/puma/log_writer.rb (8)
  • error (98-101)
  • unknown_error (135-137)
  • log (62-68)
  • call (12-146)
  • call (14-18)
  • call (15-17)
  • call (20-24)
  • call (21-23)
lib/puma/server.rb (1)
  • lowlevel_error (564-581)
🔇 Additional comments (8)
lib/puma/binder.rb (1)

47-51: LGTM!

The addition of RACK_AFTER_REPLY and RACK_RESPONSE_FINISHED to proto_env is correct. Initializing them to nil here allows the request handler to lazily initialize them to empty arrays via ||= [] only when needed.

lib/puma/const.rb (1)

231-231: LGTM!

The constant follows the established pattern for Rack environment keys and aligns with the Rack specification for rack.response_finished.

lib/puma/request.rb (3)

55-55: LGTM!

Good addition of the error variable initialization to capture exceptions for the rack.response_finished callbacks.


124-132: LGTM!

The variable rename from e to error is consistent and enables proper error propagation to the rack.response_finished callbacks.


96-96: LGTM!

Lazy initialization to an empty array follows the same pattern as RACK_AFTER_REPLY on line 95.

test/test_rack_server.rb (3)

60-60: LGTM!

The server initialization now explicitly passes a null log writer, which is appropriate for test isolation.


199-223: LGTM!

Good test coverage for the rack.response_finished callback mechanism:

  • Verifies callbacks receive correct parameters (env, status, headers, error)
  • Confirms LIFO execution order ([3, 2, 1])
  • Validates that exceptions in callbacks don't prevent subsequent callbacks from executing

225-249: LGTM!

Good coverage for the error path, verifying that exceptions raised by the app are properly propagated to rack.response_finished callbacks with the correct status (500) and error object.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +150 to +158
if response_finished = env[RACK_RESPONSE_FINISHED]
response_finished.reverse_each do |o|
begin
o.call(env, status, headers, error)
rescue StandardError => e
@log_writer.debug_error e
end
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

status and headers may be nil for early return paths.

If handle_request returns early at line 57 (closed_socket?) or line 60 (413 response), the status and headers local variables are never assigned. The callbacks would then receive nil for both parameters.

Notably, for the 413 case, a response is actually sent to the client, but the callback receives nil instead of the actual status (413).

Consider initializing status and headers before potential early returns, or updating them in those paths:

🛠️ Possible approach
     def handle_request(client, requests)
       env = client.env
       io_buffer = client.io_buffer
       socket  = client.io   # io may be a MiniSSL::Socket
       app_body = nil
       error = nil
+      status = nil
+      headers = nil
 
       return false if closed_socket?(socket)
 
       if client.http_content_length_limit_exceeded
+        status = 413
+        headers = {}
         return prepare_response(413, {}, ["Payload Too Large"], requests, client)
       end
🤖 Prompt for AI Agents
In `@lib/puma/request.rb` around lines 150 - 158, The callbacks in the
RACK_RESPONSE_FINISHED handling can receive nil because local variables status
and headers aren't set on early returns in handle_request (e.g., closed_socket?
and the 413 branch); ensure status and headers are initialized and updated on
those paths so callbacks get the real response values: initialize status and
headers before calling handle_request (or set them inside the closed_socket? and
413 branches) and propagate those values into the response_finished.each
callback invocation (the response_finished block that calls o.call(env, status,
headers, error)); update any early-return branches to assign the correct
numeric/status and header hash rather than leaving them nil.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants