Skip to content

wip feat: improve middleware performance by 50%#1209

Open
sansyrox wants to merge 1 commit intomainfrom
fix/middleware-performance
Open

wip feat: improve middleware performance by 50%#1209
sansyrox wants to merge 1 commit intomainfrom
fix/middleware-performance

Conversation

@sansyrox
Copy link
Copy Markdown
Member

@sansyrox sansyrox commented Jul 11, 2025

Description

This PR fixes #

Summary

This PR does....

PR Checklist

Please ensure that:

  • The PR contains a descriptive title
  • The PR contains a descriptive summary of the changes
  • You build and test your changes before submitting a PR.
  • You have added relevant documentation
  • You have added relevant tests. We prefer integration tests wherever possible

Pre-Commit Instructions:

Summary by CodeRabbit

  • Refactor
    • Optimized middleware chain execution to enhance request and response processing performance.

@vercel
Copy link
Copy Markdown

vercel bot commented Jul 11, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
robyn Ready Ready Preview, Comment Mar 28, 2026 7:33pm

@recurseml
Copy link
Copy Markdown

recurseml bot commented Jul 11, 2025

✨ No issues found! Your code is sparkling clean! ✨

Need help? Join our Discord for support!
https://discord.gg/qEjHQk64Z9

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Jul 11, 2025

Merging this PR will not alter performance

✅ 189 untouched benchmarks


Comparing fix/middleware-performance (cdd6889) with main (3e04c65)

Open in CodSpeed

@sansyrox sansyrox force-pushed the fix/middleware-performance branch from 72ede91 to cdd6889 Compare March 28, 2026 19:31
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

This PR introduces middleware chain execution functions in src/executors/mod.rs that batch-process before and after middleware sequences with optimizations for synchronous-only chains, and refactors src/server.rs to use single-call chain executors instead of per-middleware loops.

Changes

Cohort / File(s) Summary
Middleware Chain Executors
src/executors/mod.rs
Added execute_before_middleware_chain() and execute_after_middleware_chain() async functions. Both optimize by detecting all-sync middleware to execute in a single Python::with_gil block; async chains fall back to sequential per-middleware execution. Before chain returns early on Response; after chain constrains extraction to Response and errors on request returns.
Server Middleware Integration
src/server.rs
Updated imports to use new chain executors. Refactored before/after middleware control flows: replaced per-middleware iteration loops with single-call chain execution. Before middleware chain immediately returns on early response; after middleware applies response updates and errors internally. Removed early-response pre-check before const-router cache selection.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Server as Server
    participant BeforeChain as Before Chain
    participant Middleware as Middleware(s)
    participant AfterChain as After Chain
    participant HTTP as HTTP Handler

    Client->>Server: Request
    Server->>BeforeChain: execute_before_middleware_chain()
    
    rect rgba(100, 150, 200, 0.5)
        Note over BeforeChain,Middleware: Detect all_sync?
        alt All Synchronous
            BeforeChain->>BeforeChain: Single GIL block
            BeforeChain->>Middleware: Execute all
        else Has Async
            loop Each Middleware
                BeforeChain->>Middleware: await execute_middleware_function()
            end
        end
    end
    
    alt Early Response from Chain
        Middleware-->>BeforeChain: Response
        BeforeChain-->>Server: MiddlewareReturn::Response
        Server-->>Client: Response
    else Request Continues
        Middleware-->>BeforeChain: Request
        BeforeChain-->>Server: MiddlewareReturn::Request
        Server->>HTTP: execute_http_function()
        HTTP-->>Server: Response
        Server->>AfterChain: execute_after_middleware_chain()
        
        rect rgba(150, 150, 100, 0.5)
            AfterChain->>AfterChain: Detect all_sync?
            alt All Synchronous
                AfterChain->>Middleware: Execute all in GIL
            else Has Async
                loop Each Middleware
                    AfterChain->>Middleware: await execution
                end
            end
        end
        
        Middleware-->>AfterChain: Response
        AfterChain-->>Server: Updated Response
        Server-->>Client: Response
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Middleware chains, now bundled with care,
GIL optimized through sync-detection's flair,
Before and after in one swift embrace,
Early responses exit with perfect grace!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely a placeholder template with no actual content filled in; issue reference and summary remain unfilled. Complete the description template by filling in the issue number, providing a detailed summary of the middleware chain refactoring changes, and confirming the PR checklist items.
Title check ❓ Inconclusive The title mentions a performance improvement but uses vague terminology ('wip' and 'improve') and lacks specificity about what changed. Replace with a more specific title like 'Refactor middleware execution to use batched chain processing' that clearly describes the actual implementation change.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/middleware-performance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/executors/mod.rs`:
- Around line 263-298: The after-middleware chain currently only forwards a
Response, breaking the (request, response) contract used by
execute_after_middleware_function; update execute_after_middleware_chain to
accept and pass both &Request and &Response to middleware invocations (use
get_function_output and execute_middleware_function with both references so
after_request(req, resp) callbacks can access request-scoped data), ensure you
handle the returned MiddlewareReturn the same way (only accept
MiddlewareReturn::Response) and update the caller in server.rs to pass &request
into this helper accordingly.

In `@src/server.rs`:
- Around line 517-520: The current early return in index() when
execute_before_middleware_chain returns MiddlewareReturn::Response(r) skips
global response header application and the after-middleware chain; instead,
change the handling so index() continues down the common response path: if
execute_before_middleware_chain yields MiddlewareReturn::Response, store that
Response as the "short-circuit" response while also preserving the most-recent
mutated Request (from MiddlewareReturn::Request or a sidecar), then proceed
through the same global header application and execute_after_middleware_chain on
the response before returning; update the match on
execute_before_middleware_chain (and any variables around before_middlewares) to
propagate both the latest Request and the Response into the standard response
flow rather than returning ResponseType::Standard immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a573752f-50fa-4ab3-a2d6-3721ec7f5e1f

📥 Commits

Reviewing files that changed from the base of the PR and between 3e04c65 and cdd6889.

📒 Files selected for processing (2)
  • src/executors/mod.rs
  • src/server.rs

Comment on lines +263 to +298
pub async fn execute_after_middleware_chain(
input: &Response,
middlewares: &[FunctionInfo],
) -> Result<MiddlewareReturn> {
let mut current_response = input.clone();

// Check if all middlewares are sync to optimize GIL usage
let all_sync = middlewares.iter().all(|m| !m.is_async);

if all_sync {
// Execute all sync middlewares in a single GIL acquisition
Python::with_gil(|py| -> Result<MiddlewareReturn> {
for middleware in middlewares {
let output = get_function_output(middleware, py, &current_response)?;

// After middleware should return Response
match output.extract::<Response>() {
Ok(response) => current_response = response,
Err(e) => return Err(e.into()),
}
}

Ok(MiddlewareReturn::Response(current_response))
})
} else {
// Fall back to individual execution for mixed sync/async middlewares
for middleware in middlewares {
current_response = match execute_middleware_function(&current_response, middleware).await? {
MiddlewareReturn::Response(r) => r,
MiddlewareReturn::Request(_) => {
return Err(anyhow::anyhow!("After middleware returned a request"))
}
};
}

Ok(MiddlewareReturn::Response(current_response))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Preserve the (request, response) contract for after middleware.

This chain runner only passes a Response: the sync path calls get_function_output(...), and the async path calls execute_middleware_function(...). That regresses the behavior implemented by execute_after_middleware_function at Lines 181-217, which passes both request and response. Any after_request(req, resp) middleware will now fail at runtime, and those callbacks can no longer inspect request-scoped data such as path params.

🔧 Suggested fix
-pub async fn execute_after_middleware_chain(
-    input: &Response,
+pub async fn execute_after_middleware_chain(
+    request: &Request,
+    input: &Response,
     middlewares: &[FunctionInfo],
 ) -> Result<MiddlewareReturn> {
     let mut current_response = input.clone();
@@
     if all_sync {
         Python::with_gil(|py| -> Result<MiddlewareReturn> {
             for middleware in middlewares {
-                let output = get_function_output(middleware, py, &current_response)?;
+                let output = get_function_output_with_two_args(
+                    middleware,
+                    py,
+                    request,
+                    &current_response,
+                )?;
@@
     } else {
         for middleware in middlewares {
-            current_response = match execute_middleware_function(&current_response, middleware).await? {
+            current_response = match execute_after_middleware_function(
+                request,
+                &current_response,
+                middleware,
+            )
+            .await? {
                 MiddlewareReturn::Response(r) => r,
                 MiddlewareReturn::Request(_) => {
                     return Err(anyhow::anyhow!("After middleware returned a request"))
                 }
             };
         }

src/server.rs will need to pass &request into this helper as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/executors/mod.rs` around lines 263 - 298, The after-middleware chain
currently only forwards a Response, breaking the (request, response) contract
used by execute_after_middleware_function; update execute_after_middleware_chain
to accept and pass both &Request and &Response to middleware invocations (use
get_function_output and execute_middleware_function with both references so
after_request(req, resp) callbacks can access request-scoped data), ensure you
handle the returned MiddlewareReturn the same way (only accept
MiddlewareReturn::Response) and update the caller in server.rs to pass &request
into this helper accordingly.

Comment on lines +517 to +520
request = match execute_before_middleware_chain(&request, &before_middlewares).await {
Ok(MiddlewareReturn::Request(r)) => r,
Ok(MiddlewareReturn::Response(r)) => {
return ResponseType::Standard(r);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid returning short-circuited before-middleware responses here.

This exits index() before the common response path at Lines 567-609, so responses produced by before middleware no longer receive global response headers or the after-middleware chain. That will show up on auth/preflight-style short circuits as missing CORS/security headers. If you keep these responses on the normal path, the chain result also needs to preserve the latest mutated Request.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server.rs` around lines 517 - 520, The current early return in index()
when execute_before_middleware_chain returns MiddlewareReturn::Response(r) skips
global response header application and the after-middleware chain; instead,
change the handling so index() continues down the common response path: if
execute_before_middleware_chain yields MiddlewareReturn::Response, store that
Response as the "short-circuit" response while also preserving the most-recent
mutated Request (from MiddlewareReturn::Request or a sidecar), then proceed
through the same global header application and execute_after_middleware_chain on
the response before returning; update the match on
execute_before_middleware_chain (and any variables around before_middlewares) to
propagate both the latest Request and the Response into the standard response
flow rather than returning ResponseType::Standard immediately.

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.

1 participant