Skip to content

Conversation

@pjebs
Copy link
Contributor

@pjebs pjebs commented Oct 28, 2025

Description

Fixed timeout middleware

Needs to be checked

Fixes #3394

Changes introduced

List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.

  • Benchmarks: Describe any performance benchmarks and improvements related to the changes.
  • Documentation Update: Detail the updates made to the documentation and links to the changed files.
  • Changelog/What's New: Include a summary of the additions for the upcoming release notes.
  • Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
  • API Alignment with Express: Explain how the changes align with the Express API.
  • API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
  • Examples: Provide examples demonstrating the new features or changes in action.

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • Enhancement (improvement to existing features and functionality)
  • Documentation update (changes to documentation)
  • Performance improvement (non-breaking change which improves efficiency)
  • Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

@pjebs pjebs requested a review from a team as a code owner October 28, 2025 23:33
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Warning

Rate limit exceeded

@pjebs has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 970c9de and 9d42576.

📒 Files selected for processing (2)
  • middleware/timeout/timeout.go (2 hunks)
  • middleware/timeout/timeout_test.go (2 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The timeout middleware was refactored to run handlers in a goroutine and use a select on the handler result channel vs. time.After. On timeout the middleware returns immediately (optionally invoking OnTimeout) and no longer uses context.WithTimeout or cancels the handler.

Changes

Cohort / File(s) Summary
Async timeout redesign
middleware/timeout/timeout.go
Replaced context.WithTimeout approach with goroutine + channel and select on time.After. Handler result is received asynchronously; on timeout the middleware returns immediately and invokes OnTimeout when configured. Removed context import; added time and utils imports; removed prior runHandler wrapper.
Test updates
middleware/timeout/timeout_test.go
Added/modified runHandler implementation used by tests, updated tests to simulate timeouts via time.Sleep and context.DeadlineExceeded, added timer cleanup (defer timer.Stop()), removed obsolete TestRunHandler_DefaultOnTimeout, adjusted TestTimeout_CustomHandler and TestRunHandler_CustomOnTimeout.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Middleware
    participant Handler
    rect rgb(220,255,220)
    Note over Middleware: New async flow (goroutine + select)
    Client->>Middleware: HTTP request
    Middleware->>Handler: start handler in goroutine
    Middleware->>Middleware: select { handler result | time.After(timeout) }
    alt timeout fires first
        Middleware-->>Client: return fiber.ErrRequestTimeout (invoke OnTimeout if set)
        Note right of Handler: handler continues in background (not awaited)
    else handler completes first
        Handler-->>Middleware: result via channel
        Middleware-->>Client: return handler result
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review concurrency: goroutine spawning, channel buffering/close, potential leaks.
  • Verify OnTimeout invocation paths and error mapping to fiber.ErrRequestTimeout.
  • Confirm non-positive timeout short-circuit and removed context behavior don't break callers.
  • Check tests for reliable timing (flaky/time-dependent assertions).

Possibly related issues

Possibly related PRs

Suggested labels

v3, codex

Suggested reviewers

  • sixcolors
  • efectn
  • gaby
  • ReneWerner87

Poem

🐇 I hopped in quick with channels bright and keen,
Goroutines running where contexts had been.
When time runs out I shout, "Return!" with cheer,
OnTimeout nudges — the client gets answer near.
Hop, skip, and code — our requests are swift and seen.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description is largely incomplete and fails to meet the requirements of the provided template. While the description correctly references the linked issue (#3394), it provides only minimal information ("Fixed timeout middleware" and "Needs to be checked"). The substantive sections of the template—including a detailed description of changes, type of change selection, and all checklist items—remain unfilled. None of the change categories (benchmarks, documentation, changelog, migration guide, API alignment, examples) are addressed, and the type of change classification is not selected. The description appears to be submitted as a template without meaningful content added by the author.
Title Check ❓ Inconclusive The pull request title "fix up timeout middleware" does reference the main component being modified (timeout middleware) and is directly related to the changeset. However, the title uses vague language that lacks specificity about what is actually being fixed. The significant redesign involves changing from synchronous to asynchronous timeout handling with immediate response on timeout expiry, but this fundamental shift is not conveyed in the title. The phrasing "fix up" is generic and doesn't clearly communicate the nature of the change to someone scanning the project history.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The code changes directly implement the objectives specified in linked issue #3394. The issue requests that the timeout middleware be redesigned to return a timeout error immediately when the deadline expires, rather than waiting for the handler to complete. The changes accomplish this by replacing the synchronous context.WithTimeout approach with an asynchronous model where the handler runs in a goroutine and a select statement waits for either the handler result or a timeout signal. When the timeout occurs, the middleware can now return fiber.ErrRequestTimeout immediately to the client without waiting for handler completion, which is the core objective of the linked issue.
Out of Scope Changes Check ✅ Passed All changes in this pull request are directly scoped to the linked issue #3394. The modifications are limited to two files in the timeout middleware package: timeout.go (implementing the asynchronous redesign) and timeout_test.go (updating tests for the new implementation). No changes appear in unrelated packages, utilities, or features. The changes are focused exclusively on addressing the specific architectural problem identified in the issue, with no extraneous modifications or scope creep detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @pjebs, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the timeout middleware to enhance its robustness and control over request processing. The changes replace the previous context.WithTimeout approach with a more explicit goroutine and select statement, allowing for precise termination and handling of requests that exceed the configured timeout duration. This ensures that Fiber applications can manage request timeouts more reliably, providing consistent error responses and status codes.

Highlights

  • Timeout Mechanism Refactor: The core timeout logic in the middleware has been completely rewritten, moving from context.WithTimeout to a goroutine and select statement with time.After for more explicit and direct timeout management.
  • Dependency Update: The context package import has been removed from middleware/timeout/timeout.go, and time and github.com/gofiber/utils/v2 packages have been added to support the new timeout implementation.
  • Error Handling Improvement: Timeout error handling now directly interacts with the underlying fasthttp.RequestCtx using ctx.RequestCtx().TimeoutErrorWithResponse and ctx.RequestCtx().TimeoutErrorWithCode for more precise control over timeout responses.
  • Code Structure and Testing: The runHandler helper function has been moved from the main middleware file to the test file, indicating it's no longer part of the core middleware logic. Test cases have also been simplified to directly simulate timeouts.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request attempts to refactor the timeout middleware. However, the new implementation introduces a critical flaw by removing the use of context.WithTimeout, which can lead to leaked goroutines when a timeout occurs. The handler continues to run in the background even after a timeout response has been sent. My review focuses on fixing this by reintroducing a context-based cancellation mechanism combined with a non-blocking select pattern, which was likely the original intent of the refactor. I've also pointed out a minor issue in the test code where a line becomes unreachable due to the changes.

Comment on lines 28 to 53
err := make(chan error)
go func() {
err <- h(ctx)
}()

err := runHandler(ctx, h, cfg)

if errors.Is(tCtx.Err(), context.DeadlineExceeded) && err == nil {
if cfg.OnTimeout != nil {
return cfg.OnTimeout(ctx)
}
return fiber.ErrRequestTimeout
}
return err
}
}

// runHandler executes the handler and returns fiber.ErrRequestTimeout if it
// sees a deadline exceeded error or one of the custom "timeout-like" errors.
func runHandler(c fiber.Ctx, h fiber.Handler, cfg Config) error {
err := h(c)
if err != nil && (errors.Is(err, context.DeadlineExceeded) || (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors))) {
if cfg.OnTimeout != nil {
if toErr := cfg.OnTimeout(c); toErr != nil {
return toErr
for {
select {
case err := <-err:
if err != nil && (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors)) {
if cfg.OnTimeout != nil {
if toErr := cfg.OnTimeout(ctx); toErr != nil {
return toErr
}
}
return fiber.ErrRequestTimeout
}
return err
case <-time.After(timeout):
if cfg.OnTimeout != nil {
err := cfg.OnTimeout(ctx)
ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
return err
}
ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
return fiber.ErrRequestTimeout
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The new implementation introduces a critical issue: it can lead to leaked goroutines.

The handler h(ctx) is started in a new goroutine. If a timeout occurs in the select statement (case <-time.After(timeout)), the server will return a timeout response to the client, but the goroutine running the handler is not cancelled and will continue to run in the background until it completes. This can lead to resource exhaustion (e.g., holding database connections, memory) and unpredictable behavior, as the handler might complete its work even though the request has already timed out.

The previous implementation correctly used context.WithTimeout to create a context that would be cancelled on timeout. This cancellation signal could be propagated to the handler, allowing it to gracefully terminate its work.

To fix this, you should reintroduce context.WithTimeout and use its Done() channel in the select statement. This combines the non-blocking benefit of using a goroutine with the crucial cancellation signal for the handler.

Additionally, the for {} loop is redundant as every path in the select statement returns, meaning the loop will only execute once.

		// Create a context that is cancelled when the timeout expires
		parentCtx := ctx.Context()
		tCtx, cancel := context.WithTimeout(parentCtx, timeout)
		defer cancel()

		// Set the new context for the handler
		ctx.SetContext(tCtx)
		// Restore the original context after the handler returns
		defer ctx.SetContext(parentCtx)

		errChan := make(chan error, 1)
		go func() {
			errChan <- h(ctx)
		}()

		select {
		case err := <-errChan:
			// Handler completed before timeout.
			// We still need to check for custom timeout errors.
			if err != nil && len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors) {
				if cfg.OnTimeout != nil {
					return cfg.OnTimeout(ctx)
				}
				return fiber.ErrRequestTimeout
			}
			return err

		case <-tCtx.Done():
			// Timeout occurred.
			if !errors.Is(tCtx.Err(), context.DeadlineExceeded) {
				// Context was cancelled for a different reason.
				return tCtx.Err()
			}

			// Deadline was exceeded.
			if cfg.OnTimeout != nil {
				return cfg.OnTimeout(ctx)
			}
			ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
			return fiber.ErrRequestTimeout
		}

package timeout

import (
"context"
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The context package was removed, but it's essential for correctly handling request timeouts and cancellations in Go. The new implementation without it introduces a critical bug (leaked goroutines). Please re-add the context import as it's required for the proper fix I've suggested in my other comment.

}
time.Sleep(100 * time.Millisecond)
return context.DeadlineExceeded
return c.SendString("should not reach")
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This line is unreachable because the function will always return on the line above. It can be removed to improve code clarity.

Copy link
Contributor

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

🧹 Nitpick comments (2)
middleware/timeout/timeout.go (1)

11-14: Doc comment is stale; implementation no longer replaces the request’s context

Update to reflect the async goroutine/select approach (no context.WithTimeout). This avoids misleading users.

middleware/timeout/timeout_test.go (1)

173-176: Remove unreachable code

return context.DeadlineExceeded makes the subsequent return c.SendString("should not reach") unreachable. Clean it up.

-        return context.DeadlineExceeded
-        return c.SendString("should not reach")
+        return context.DeadlineExceeded
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83cc132 and 73a6df4.

📒 Files selected for processing (2)
  • middleware/timeout/timeout.go (2 hunks)
  • middleware/timeout/timeout_test.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Apply formatting using gofumpt (Make target: format)
Optimize struct field alignment using betteralign (Make target: betteralign)
Modernize code using gopls modernize (Make target: modernize)

Files:

  • middleware/timeout/timeout.go
  • middleware/timeout/timeout_test.go
🧬 Code graph analysis (2)
middleware/timeout/timeout.go (1)
constants.go (2)
  • ErrRequestTimeout (126-126)
  • StatusRequestTimeout (81-81)
middleware/timeout/timeout_test.go (2)
middleware/timeout/config.go (1)
  • Config (10-26)
constants.go (1)
  • ErrRequestTimeout (126-126)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint
  • GitHub Check: repeated
  • GitHub Check: Compare
🔇 Additional comments (1)
middleware/timeout/timeout_test.go (1)

24-37: Test helper mirrors production logic; OK, but keep behavior in sync

runHandler duplicates the custom‑error/OnTimeout flow. If production logic changes, update this helper to match to avoid false positives.

Comment on lines 32 to 53
for {
select {
case err := <-err:
if err != nil && (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors)) {
if cfg.OnTimeout != nil {
if toErr := cfg.OnTimeout(ctx); toErr != nil {
return toErr
}
}
return fiber.ErrRequestTimeout
}
return err
case <-time.After(timeout):
if cfg.OnTimeout != nil {
err := cfg.OnTimeout(ctx)
ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
return err
}
ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
return fiber.ErrRequestTimeout
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Simplify select; use a timer and stop it to avoid leaks; handle custom‑error path consistently

  • The surrounding for is unnecessary; both branches return.
  • Prefer time.NewTimer and defer timer.Stop() to avoid retaining timers when the handler finishes first.
  • When a custom “timeout-like” error occurs, consider freezing the response if OnTimeout wrote one, otherwise the default 408 handler may overwrite it.
-        for {
-            select {
-            case err := <-err:
-                if err != nil && (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors)) {
-                    if cfg.OnTimeout != nil {
-                        if toErr := cfg.OnTimeout(ctx); toErr != nil {
-                            return toErr
-                        }
-                    }
-                    return fiber.ErrRequestTimeout
-                }
-                return err
-            case <-time.After(timeout):
-                if cfg.OnTimeout != nil {
-                    err := cfg.OnTimeout(ctx)
-                    ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
-                    return err
-                }
-                ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
-                return fiber.ErrRequestTimeout
-            }
-        }
+        timer := time.NewTimer(timeout)
+        defer timer.Stop()
+        select {
+        case err := <-errCh:
+            if err != nil && len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors) {
+                if cfg.OnTimeout != nil {
+                    if toErr := cfg.OnTimeout(ctx); toErr != nil {
+                        // Preserve custom body written by OnTimeout.
+                        ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
+                        return toErr
+                    }
+                    // Freeze OnTimeout response to avoid default handler overwrite.
+                    ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
+                    return nil
+                }
+                ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
+                return fiber.ErrRequestTimeout
+            }
+            return err
+        case <-timer.C:
+            if cfg.OnTimeout != nil {
+                if toErr := cfg.OnTimeout(ctx); toErr != nil {
+                    ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
+                    return toErr
+                }
+                ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
+                return nil
+            }
+            ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
+            return fiber.ErrRequestTimeout
+        }

Note: fasthttp requires calling TimeoutError* before returning when other goroutines still hold ctx; modifications after the call are ignored. This refactor maintains that guarantee. (pkg.go.dev)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In middleware/timeout/timeout.go around lines 32-53, remove the unnecessary
for-loop and replace the time.After usage with a time.NewTimer(timeout) stored
in a variable and defer timer.Stop() so timers aren't leaked; keep a single
select that returns from both branches. On the channel error branch, treat the
custom "timeout-like" error path consistently: when cfg.OnTimeout is set call it
and then, if it may have written a response, call
ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response) before
returning any error; if cfg.OnTimeout is nil but the error matches cfg.Errors
return fiber.ErrRequestTimeout. On the timer branch call cfg.OnTimeout if
present, then call
ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response) (or
TimeoutErrorWithCode(...) if no custom handler) before returning to guarantee
fasthttp freezes the response.

Comment on lines 45 to 51
if cfg.OnTimeout != nil {
err := cfg.OnTimeout(ctx)
ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
return err
}
ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
return fiber.ErrRequestTimeout
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Potential data race: OnTimeout writes to ctx while handler goroutine may still access it

Per fasthttp docs, it’s unsafe to modify/read RequestCtx from concurrent goroutines; only TimeoutError* may be called concurrently. Today, OnTimeout(ctx) mutates the response before TimeoutErrorWithResponse, possibly racing with h(ctx). Consider either:

  • Building a separate fasthttp.Response for OnTimeout and passing that to TimeoutErrorWithResponse, or
  • Documenting that OnTimeout must not modify ctx (log/metrics only), and calling TimeoutErrorWithCode first, or
  • Wrapping the handler to buffer writes (heavier change).

At minimum, add a package‑level note warning about this constraint. (pkg.go.dev)


I'll now verify the review comment by examining the code and fasthttp's concurrency constraints.

Now let me search for information about fasthttp's concurrency constraints:


🏁 Script executed:

#!/bin/bash
# Find and examine the complete timeout.go file
find . -name "timeout.go" -path "*/middleware/timeout/*" -type f

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Display the full context of timeout.go to understand the handler flow
cat middleware/timeout/timeout.go

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Search for how OnTimeout is called and in what context
rg -n "OnTimeout" middleware/timeout/ -C 5

Length of output: 6306


🏁 Script executed:

#!/bin/bash
# Check for any comments about concurrency or thread safety in the file
rg -n "concurren|thread|race|safe|goroutine" middleware/timeout/timeout.go -i

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for handler wrapping or goroutine spawning
rg -n "go \|handler\|goroutine" middleware/timeout/timeout.go -i -C 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Examine the struct definition for Config and related types
ast-grep --pattern 'type Config struct { $$$ }'

Length of output: 86100


🏁 Script executed:

#!/bin/bash
# Find all calls to TimeoutError methods
rg -n "TimeoutError" middleware/timeout/timeout.go -C 3

Length of output: 381


🏁 Script executed:

#!/bin/bash
# Check if there are any tests that might clarify the concurrent access behavior
find . -name "*timeout*test*" -o -name "*test*timeout*" | head -5

Length of output: 36


🏁 Script executed:

#!/bin/bash
# Get the repository structure to understand context better
ls -la middleware/ | head -20

Length of output: 1103


🏁 Script executed:

#!/bin/bash
# Check for any middleware that spawns goroutines
rg -n "^\s*go\s+" middleware/timeout/timeout.go

Length of output: 16


🏁 Script executed:

#!/bin/bash
# Look at the full implementation from line 30-60 to understand the context
sed -n '30,60p' middleware/timeout/timeout.go

Length of output: 867


🏁 Script executed:

#!/bin/bash
# Search for comments about the timeout handler's synchronization
rg -n "handler|ctx|timeout" middleware/timeout/timeout.go | grep -i "comment\|//" | head -10

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Check version of fasthttp being used
find . -name "go.mod" -type f -exec grep -l "fasthttp" {} \;

Length of output: 8


🏁 Script executed:

#!/bin/bash
# Display the go.mod to see fasthttp version
cat go.mod | grep fasthttp

Length of output: 36


🏁 Script executed:

#!/bin/bash
# Look at how fiber wraps fasthttp's timeout handling
rg -n "TimeoutErrorWithResponse\|TimeoutErrorWithCode" middleware/ -B 3 -A 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find ctx type definition and its methods
rg -n "func.*\(ctx.*\).*TimeoutError" middleware/ -C 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for RequestCtx documentation or usage patterns
rg -n "RequestCtx\(\)" middleware/timeout/ -C 3

Length of output: 681


🏁 Script executed:

#!/bin/bash
# Look for the return statement and control flow after OnTimeout
sed -n '40,55p' middleware/timeout/timeout.go

Length of output: 420


🏁 Script executed:

#!/bin/bash
# Find where the timeout handler is registered/called from
rg -n "Handle|Timeout|timeout" middleware/timeout/timeout.go | head -20

Length of output: 804


🏁 Script executed:

#!/bin/bash
# Check the surrounding context of the code snippet (lines 45-51)
sed -n '1,70p' middleware/timeout/timeout.go

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Get line count to understand file size
wc -l middleware/timeout/timeout.go

Length of output: 32


🏁 Script executed:

#!/bin/bash
# Search for any sync primitives or locking mechanisms
rg -n "sync\.|Mutex\|RWMutex\|Lock\|Unlock" middleware/timeout/timeout.go

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if OnTimeout callback receives a copy or reference
rg -n "OnTimeout\s*func" middleware/timeout/timeout.go -A 5 -B 5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for type Config definition to understand OnTimeout signature
rg -n "OnTimeout" middleware/timeout/timeout.go -B 20 | grep -A 20 "type Config"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check fiber.Context to understand if ctx is a pointer or value
rg -n "fiber.Context" middleware/timeout/timeout.go -C 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the exact implementation of TimeoutErrorWithResponse
rg -n "TimeoutErrorWithResponse" --type go -C 5

Length of output: 731


🏁 Script executed:

#!/bin/bash
# Find the handler function signature
rg -n "func.*handler" middleware/timeout/timeout.go -C 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for goroutine spawning in timeout context
rg -n "go\s*\(" middleware/timeout/timeout.go -C 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if RequestCtx is modified after OnTimeout call
sed -n '45,60p' middleware/timeout/timeout.go

Length of output: 516


🏁 Script executed:

#!/bin/bash
# Look for any fasthttp imports
rg -n "import\|fasthttp" middleware/timeout/timeout.go | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get complete context - show first 100 lines of file
head -100 middleware/timeout/timeout.go

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Understand when TimeoutError is called - from what goroutine?
rg -n "TimeoutError" middleware/timeout/timeout.go -A 2 -B 5

Length of output: 401


🏁 Script executed:

#!/bin/bash
# Check if h(ctx) is called before or after timeout handling
grep -n "h(ctx)\|h(\|handler(" middleware/timeout/timeout.go

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Look for the actual timeout implementation and when OnTimeout is invoked
sed -n '1,100p' middleware/timeout/timeout.go | cat -n

Length of output: 2050


🏁 Script executed:

#!/bin/bash
# Get the complete implementation to understand the flow
cat middleware/timeout/timeout.go | head -150

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Search for context.WithCancel or timeout context setup
rg -n "context\|WithTimeout\|WithCancel" middleware/timeout/timeout.go -C 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for channel operations or synchronization
rg -n "<-\|chan\|->\|select" middleware/timeout/timeout.go

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's a separate goroutine handling the handler
grep -n "go func\|go h\(" middleware/timeout/timeout.go

Length of output: 23


🏁 Script executed:

#!/bin/bash
# Find line 45-51 exactly and surrounding context
awk 'NR>=40 && NR<=60' middleware/timeout/timeout.go

Length of output: 611


🏁 Script executed:

#!/bin/bash
# Look at the timeout handler structure to find where it spawns goroutines
grep -n "func\|for\|go " middleware/timeout/timeout.go | head -30

Length of output: 300


🏁 Script executed:

#!/bin/bash
# Full file dump for analysis
cat middleware/timeout/timeout.go

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Alternative approach - check structure around OnTimeout invocation
awk '/OnTimeout.*\(/ {start=NR-10} start && NR<=start+30 {print NR": "$0}' middleware/timeout/timeout.go

Length of output: 846


🏁 Script executed:

#!/bin/bash
# Try to find the exact context where RequestCtx is used
grep -n "RequestCtx()\|ctx\." middleware/timeout/timeout.go | head -30

Length of output: 199


🏁 Script executed:

#!/bin/bash
# Verify the handler execution context
awk 'NR>=1 && NR<=150' middleware/timeout/timeout.go

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Get basic file information first
file middleware/timeout/timeout.go && wc -l middleware/timeout/timeout.go

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Simple head to check file exists and readable
head -20 middleware/timeout/timeout.go

Length of output: 577


🏁 Script executed:

#!/bin/bash
# List files in middleware directory
ls -la middleware/timeout/

Length of output: 300


🏁 Script executed:

#!/bin/bash
# Verify directory structure
pwd && find middleware -name "*.go" -path "*/timeout/*"

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check if middleware/timeout exists
test -d middleware/timeout && echo "Directory exists" || echo "Directory not found"

Length of output: 16


🏁 Script executed:

#!/bin/bash
# List all go files in middleware
find middleware -name "*.go" | head -20

Length of output: 612


🏁 Script executed:

#!/bin/bash
# Search for timeout.go in entire repo
find . -name "timeout.go" 2>/dev/null

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Check repository root
ls -la | grep -E "go.mod|README|middleware"

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Examine current directory structure
ls -la ./

Length of output: 4422


🏁 Script executed:

#!/bin/bash
# Alternative search for the timeout file
locate timeout.go 2>/dev/null || find . -name "*timeout*" -type f 2>/dev/null

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Try to identify the repo structure
find . -name "*.go" -path "*middleware*" 2>/dev/null | head -5

Length of output: 178


🏁 Script executed:

#!/bin/bash
# Find any middleware files
find . -type d -name middleware 2>/dev/null

Length of output: 30


🏁 Script executed:

#!/bin/bash
# Simple directory listing
ls

Length of output: 733


🏁 Script executed:

#!/bin/bash
# Look for PR or diff context
find . -name ".git" -o -name "CHANGLOG*" -o -name "*middleware*" 2>/dev/null | head -20

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check for hidden directories and git
ls -la

Length of output: 4422


🏁 Script executed:

#!/bin/bash
# Get the repository details
cat README.md 2>/dev/null | head -30

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Try general fiber or middleware patterns
find . -type f -name "*.go" 2>/dev/null | head -30

Length of output: 640


🏁 Script executed:

#!/bin/bash
# Find Makefile or build configuration  
find . -name Makefile -o -name "*.mk" 2>/dev/null | head -5

Length of output: 10


🏁 Script executed:

#!/bin/bash
# List all .go files at root
ls -la *.go 2>/dev/null || echo "No go files at root"

Length of output: 3029


🏁 Script executed:

#!/bin/bash
# Get depth of directory tree
du -d 3 -h | head -30

Length of output: 606


🏁 Script executed:

#!/bin/bash
# Verify repository was cloned - check git
git log --oneline 2>/dev/null | head -5

Length of output: 322


🏁 Script executed:

#!/bin/bash
# Check current HEAD and branch
git status 2>/dev/null || echo "Not a git repository"

Length of output: 63


🏁 Script executed:

#!/bin/bash
# Get repository name from git config
git config --get remote.origin.url 2>/dev/null || echo "No git remote"

Length of output: 36


🏁 Script executed:

#!/bin/bash
# List all files to understand structure
find . -type f -name "*.go" | grep -i timeout

Length of output: 99


🏁 Script executed:

#!/bin/bash
# General search for timeout in Go files
rg -l "timeout" . --type go 2>/dev/null | head -10

Length of output: 296


🏁 Script executed:

#!/bin/bash
# Search for OnTimeout in any Go file
rg "OnTimeout" . --type go 2>/dev/null

Length of output: 2869


🏁 Script executed:

#!/bin/bash
# Look for TimeoutError in Go files
rg "TimeoutError" . --type go 2>/dev/null | head -5

Length of output: 257


🏁 Script executed:

#!/bin/bash
# Find middleware directory if it exists
find . -type d -name middleware -o -type d -name "middleware-*" 2>/dev/null

Length of output: 30


🏁 Script executed:

#!/bin/bash
# Search for fiber in dependencies
rg "github.com/gofiber/fiber" . 2>/dev/null | head -3

Length of output: 182


🏁 Script executed:

#!/bin/bash
# Check go.mod for all dependencies
cat go.mod 2>/dev/null | grep -E "require|fiber|fasthttp" | head -20

Length of output: 166


🏁 Script executed:

#!/bin/bash
# List repo structure at top 3 levels
tree -L 3 -I 'vendor|node_modules' 2>/dev/null || find . -maxdepth 3 -type d

Length of output: 7584


🏁 Script executed:

#!/bin/bash
# Try to find PR branch info
git branch -a 2>/dev/null || echo "No branches available"

Length of output: 83


🏁 Script executed:

#!/bin/bash
# Check git log to understand the context
git log --oneline --all 2>/dev/null | head -10

Length of output: 662


🏁 Script executed:

#!/bin/bash
# List all directories
ls -d */ 2>/dev/null || find . -maxdepth 2 -type d

Length of output: 67


🏁 Script executed:

#!/bin/bash
# Quick search for Go files in src or pkg
ls src/ pkg/ internal/ 2>/dev/null || echo "No standard dirs"

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Try searching with different patterns
rg "RequestCtx.*TimeoutError" --type go 2>/dev/null

Length of output: 253


🏁 Script executed:

#!/bin/bash
# Look for any configuration or implementation
rg "fiber.Context" . --type go 2>/dev/null | head -5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Final attempt - show everything at root
ls -la && pwd

Length of output: 4441


🏁 Script executed:

#!/bin/bash
# Check if there's a refs or objects folder indicating git
ls -la .git/ 2>/dev/null | head -10

Length of output: 526


🏁 Script executed:

#!/bin/bash
# Get the actual file that was changed in PR
git show --name-only 2>/dev/null | grep -i timeout

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Try git diff to see actual changes
git diff HEAD~1 --name-only 2>/dev/null | head -10

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Check commits touching timeout
git log --all --pretty=format:"%h %s" 2>/dev/null | grep -i timeout | head -5

Length of output: 313


🏁 Script executed:

#!/bin/bash
# List files changed in the PR or recent commit
git diff --cached --name-only 2>/dev/null || git show --stat 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Examine what branches exist
git for-each-ref --format='%(refname:short)' 2>/dev/null | head -20

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if we're on a PR branch
git log -1 --pretty=format:"%B" 2>/dev/null

Length of output: 25


🏁 Script executed:

#!/bin/bash
# Try to infer structure from imports
find . -name "*.go" -exec head -10 {} \; 2>/dev/null | grep -E "^package|^import" | head -20

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Specific search for timeout package
find . -name "*.go" | xargs grep -l "package.*timeout" 2>/dev/null

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Search for the code snippet mentioned in the review
rg "OnTimeout.*nil" . --type go 2>/dev/null

Length of output: 465


🏁 Script executed:

#!/bin/bash
# Look for TimeoutErrorWithResponse call
rg "TimeoutErrorWithResponse" . --type go 2>/dev/null

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Try another approach - grep recursively
grep -r "OnTimeout" . --include="*.go" 2>/dev/null | head -5

Length of output: 191


🏁 Script executed:

#!/bin/bash
# Search the complete directory tree
find . -type f -name "*.go" 2>/dev/null | wc -l

Length of output: 3


🏁 Script executed:

#!/bin/bash
# Get everything at depth 1
ls -la ./*/  2>/dev/null | head -50

Length of output: 2644


🏁 Script executed:

#!/bin/bash
# Dump all directory names up to depth 4
find . -type d -not -path '*/\.*' 2>/dev/null | head -50

Length of output: 882


🏁 Script executed:

#!/bin/bash
# Show git references
git rev-parse HEAD 2>/dev/null && git branch 2>/dev/null

Length of output: 65


🏁 Script executed:

#!/bin/bash
# Try to get the actual changed file paths from git
git show --pretty="" --name-only 2>/dev/null

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Worst case - brute force search
grep -r "if cfg.OnTimeout" . 2>/dev/null | head -3

Length of output: 182


🏁 Script executed:

#!/bin/bash
# Search with ripgrep for the exact code snippet
rg "if cfg\.OnTimeout.*\{\s*err := cfg\.OnTimeout" . 2>/dev/null

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Try even simpler - search for "OnTimeout" anywhere
grep -r "OnTimeout" . --include="*.go" 2>/dev/null

Length of output: 2869


🏁 Script executed:

#!/bin/bash
# Check if repository is empty or not cloned
git rev-list --all 2>/dev/null | wc -l

Length of output: 4


🏁 Script executed:

#!/bin/bash
# Show git log with files
git log --name-status 2>/dev/null | head -50

Length of output: 1674


🏁 Script executed:

#!/bin/bash
# Extract file from git show if available
git show HEAD:middleware/timeout/timeout.go 2>/dev/null || echo "File not in HEAD"

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Try to locate any timeout-related files
find . -iname "*timeout*" 2>/dev/null

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Check if files exist per the review context
test -f "middleware/timeout/timeout.go" && cat "middleware/timeout/timeout.go" || echo "File path does not exist at middleware/timeout/timeout.go"

Length of output: 1595


🏁 Script executed:

#!/bin/bash
# Try alternate Go path structures common in modules
test -f "pkg/middleware/timeout/timeout.go" && cat "pkg/middleware/timeout/timeout.go" || test -f "internal/middleware/timeout/timeout.go" && cat "internal/middleware/timeout/timeout.go" || echo "File not found in standard paths"

Length of output: 32


🏁 Script executed:

#!/bin/bash
# Last resort - dump entire fs structure up to 1MB
du -h -s * 2>/dev/null | sort -h | head -30

Length of output: 532


🏁 Script executed:

#!/bin/bash
# Look for any go.mod to understand module structure
cat go.mod 2>/dev/null || find . -name "go.mod" -exec cat {} \; 2>/dev/null | head -50

Length of output: 930


🏁 Script executed:

#!/bin/bash
# Show workspace or module information
go list -m 2>/dev/null || echo "Not a go module directory"

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Try to get files from git differently
git ls-tree -r HEAD 2>/dev/null | grep timeout

Length of output: 332


🏁 Script executed:

#!/bin/bash
# If files are staged
git ls-files 2>/dev/null | grep timeout

Length of output: 120


🏁 Script executed:

#!/bin/bash
# Check git index
git diff-index --cached --name-only HEAD 2>/dev/null | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check what git sees
git status --porcelain 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Final diagnostic
echo "=== PWD ===" && pwd && echo "=== LS ===" && ls -la && echo "=== GO MOD ===" && cat go.mod 2>/dev/null | head -20 && echo "=== GIT ===" && git log --oneline 2>/dev/null | head -5

Length of output: 5331


🏁 Script executed:

#!/bin/bash
# Check the PR files from Git
git show --name-only HEAD 2>/dev/null | head -20

Length of output: 212


🏁 Script executed:

#!/bin/bash
# One more attempt at listing everything available
find . -name "timeout.go" -o -name "timeout_test.go" 2>/dev/null || (echo "Files in current directory:" && find . -maxdepth 2 -type f -name "*.go" 2>/dev/null | head -20)

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Try Python to explore directory
python3 << 'EOF'
import os
import subprocess

# Get current directory
cwd = os.getcwd()
print(f"Current directory: {cwd}")

# List first 50 files
files = []
for root, dirs, filenames in os.walk('.'):
    for f in filenames:
        files.append(os.path.join(root, f))
    if len(files) > 50:
        break

print("Sample files found:")
for f in sorted(files)[:50]:
    print(f)

# Try git
try:
    result = subprocess.run(['git', 'log', '--oneline', '-1'], capture_output=True, text=True)
    print(f"\nGit last commit: {result.stdout}")
except Exception as e:
    print(f"Git error: {e}")
EOF

Length of output: 858


🏁 Script executed:

#!/bin/bash
# Check if we have any files at all 
python3 -c "import os; print('Files in repo:', sum([len(files) for r,d,files in os.walk('.')]))"

Length of output: 18


🏁 Script executed:

#!/bin/bash
# Verify the repository state
git rev-parse --show-toplevel 2>/dev/null || pwd

Length of output: 18


🏁 Script executed:

#!/bin/bash
# Use git to get the reviewed file
git show HEAD~0:middleware/timeout/timeout.go 2>/dev/null | head -60 || git ls-tree -r HEAD | grep timeout

Length of output: 1557


🏁 Script executed:

#!/bin/bash
# Search Python repositories for reference
python3 << 'EOF'
import subprocess
import os

try:
    # Get git status
    result = subprocess.run(['git', 'status'], capture_output=True, text=True, cwd='.')
    print("Git status output:")
    print(result.stdout[:500])
    print(result.stderr[:500])
except Exception as e:
    print(f"Error: {e}")

# Check directory structure
print("\n\nDirectory tree (first 100 entries):")
count = 0
for root, dirs, files in os.walk('.'):
    for f in files + dirs:
        print(os.path.join(root, f))
        count += 1
        if count > 100:
            break
    if count > 100:
        break
EOF

Length of output: 2571


🏁 Script executed:

#!/bin/bash
# Check GitHub Actions or other CI config for clues
find . -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep -l "timeout\|middleware" 2>/dev/null || echo "No CI config found"

Length of output: 18


🏁 Script executed:

#!/bin/bash
# Look at environment or paths
env | grep -E "GO|PATH|PWD"

Length of output: 299


🏁 Script executed:

#!/bin/bash
# Try to use git directly with Python to understand the repo
python3 << 'EOF'
import subprocess
import json

# Try git commands
cmds = [
    ['git', 'rev-parse', '--show-toplevel'],
    ['git', 'log', '--oneline', '-5'],
    ['git', 'branch', '-a'],
    ['git', 'diff', 'HEAD~1', '--stat'],
]

for cmd in cmds:
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
        print(f"\n{' '.join(cmd)}:")
        print(result.stdout if result.stdout else result.stderr)
    except Exception as e:
        print(f"Error running {cmd}: {e}")
EOF

Length of output: 721


🏁 Script executed:

#!/bin/bash
# Last attempt - brute force file search with timing
echo "Searching for Go files with 'timeout' in name or content..."
time find . -type f -name "*.go" 2>/dev/null | while read f; do
  if grep -q "timeout\|OnTimeout" "$f" 2>/dev/null; then
    echo "Found in: $f"
    head -n 60 "$f"
    break
  fi
done

Length of output: 1395


🏁 Script executed:

#!/bin/bash
# Check if repo was properly cloned to the PR branch
git log --all --graph --oneline 2>/dev/null | head -20

Length of output: 1148


🌐 Web query:

fasthttp RequestCtx concurrent goroutine access safety constraints

💡 Result:

Short answer

  • RequestCtx is not safe for concurrent access. You must not read/modify a RequestCtx (or its Request/Response members) from multiple goroutines. [1][2]

Key constraints and recommendations

  • Do not copy RequestCtx values. Copying is forbidden. [1][2]
  • It is unsafe to modify/read RequestCtx from concurrently running goroutines. The only supported exception is calling ctx.TimeoutError() from another goroutine. [1][2]
  • If you need to use request data after the handler returns (or from another goroutine), copy the data you need (e.g., header values, body bytes, or use Request.Copy/CopyTo patterns or make explicit copies) instead of keeping references to RequestCtx internals. If you cannot avoid holding references, call ctx.TimeoutError() before returning. [1][2]
  • Prefer acquiring and releasing standalone Request/Response objects (AcquireRequest/ReleaseRequest) or copying values out to independent buffers when handing work to background goroutines. [1]

Sources

  • fasthttp RequestCtx docs (pkg.go.dev). [1]
  • valyala/fasthttp repository / README and issues (concurrency guidance). [2]

References
[1] https://pkg.go.dev/github.com/valyala/fasthttp
[2] https://github.com/valyala/fasthttp


🌐 Web query:

fasthttp TimeoutError concurrent access documentation

💡 Result:

Short answer: fasthttp’s RequestCtx is not safe for concurrent access — the only supported exception is calling RequestCtx.TimeoutError while other goroutines may still access the RequestCtx. Call TimeoutError before returning from your handler if other goroutines will use the context. [1][2][3]

Sources

  • fasthttp docs on pkg.go.dev (RequestCtx concurrency note). [1]
  • Alternate pkg.go.dev rendering with same warning. [2]
  • Project FAQ / guidance (explicitly: call TimeoutError before returning if other goroutines access RequestCtx) and related issue discussions about timeouts under high concurrency. [3]

🌐 Web query:

Go fasthttp context modification timeout handler race condition

💡 Result:

Short answer: fasthttp requires you NOT to let other goroutines access RequestCtx (or its members) after the request handler returns — otherwise a race/panic can occur when a timeout handler marks the ctx timed-out while another goroutine is still touching it. Either eliminate cross-goroutine references to ctx or explicitly call ctx.TimeoutError (or use TimeoutHandler) before returning so the library can mark the ctx and ignore further modifications. See the fasthttp docs and README for details and examples. [1][2]

Why this happens (brief):

  • RequestCtx and many of its members are only valid while the handler is running; fasthttp re-uses/returns buffers after the handler returns, so concurrent access by a stray goroutine races with the server reusing memory. The docs warn that TimeoutError MUST be called before returning if other goroutines still hold ctx references. [1][2]

Mitigations (concise):

  • Do not capture *fasthttp.RequestCtx or its internal byte-slices in goroutines that outlive the handler. Copy values you need (strings/bytes) before spawning goroutines. [1]
  • Use fasthttp.TimeoutHandler to automatically handle deadlines, or call ctx.TimeoutError / TimeoutErrorWithCode / TimeoutErrorWithResponse before returning when you know background goroutines may still touch ctx. The TimeoutError call marks the ctx and causes further modifications to be ignored. [1]
  • Prefer eliminating ctx references from background work (use channels, context.Context you create, or copy request data). Use sync primitives (WaitGroup/cancel) to coordinate. [1][2]

References:

  • fasthttp API docs (RequestCtx.TimeoutError, notes about validity until handler returns). [1]
  • fasthttp README / migration notes about data races and using TimeoutError / TimeoutHandler. [2]

Sources:
[1] fasthttp package docs — RequestCtx.TimeoutError and usage notes.
[2] fasthttp README / migration guidance on races and TimeoutHandler.


Confirmed: Real data race between OnTimeout callback and handler goroutine

The review comment correctly identifies a data race. fasthttp's RequestCtx is not safe for concurrent access — the only supported exception is calling RequestCtx.TimeoutError while other goroutines may still access the RequestCtx.

In the code at lines 44–48, when the timeout expires:

  1. Line 29 spawned a goroutine executing h(ctx) (the handler)
  2. Line 46 calls OnTimeout(ctx) from the main goroutine while the handler may still be running
  3. OnTimeout is a fiber.Handler that can modify ctx
  4. Line 47 then calls TimeoutErrorWithResponse (the safe operation)

The problem is that OnTimeout(ctx) runs before the safe TimeoutErrorWithResponse call, allowing a race between the handler's writes and OnTimeout's writes to the same RequestCtx.

Recommended fixes:

  • Call TimeoutErrorWithResponse (or TimeoutErrorWithCode) first to mark the context as timed out, preventing further modifications
  • Or document that OnTimeout callbacks must not modify ctx (logs/metrics only)
  • Or pass a separate Response object to OnTimeout instead of the shared ctx
  • Add a package-level comment warning about concurrent access constraints
🤖 Prompt for AI Agents
middleware/timeout/timeout.go around lines 45–51: the OnTimeout callback runs
before marking the fasthttp.RequestCtx as timed out, causing a real data race
with the handler; to fix, call ctx.RequestCtx().TimeoutErrorWithResponse(...)
(or TimeoutErrorWithCode) first to mark the context timed out, then invoke
cfg.OnTimeout (or better, invoke cfg.OnTimeout with a copy of the response or a
wrapper that exposes only safe read-only data); alternatively add package-level
docs that OnTimeout must not mutate ctx and only perform logging/metrics if you
prefer not to change call order.

Copy link
Contributor

@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

♻️ Duplicate comments (3)
middleware/timeout/timeout.go (3)

28-31: Critical: Unbuffered channel causes goroutine leak on timeout.

When the timeout expires first, the handler goroutine will block indefinitely trying to send its result to a channel with no receiver. Use a buffered channel with capacity 1.

Apply this diff:

-		err := make(chan error)
+		errCh := make(chan error, 1)
 		go func() {
-			err <- h(ctx)
+			errCh <- h(ctx)
 		}()

32-51: Major: Timer leak when handler completes before timeout.

time.After creates a timer that isn't stopped when the handler finishes first, potentially leaking resources under high load. Use time.NewTimer with defer timer.Stop().

Apply this diff:

+		timer := time.NewTimer(timeout)
+		defer timer.Stop()
+
 		select {
-		case err := <-err:
+		case err := <-errCh:
 			if err != nil && (len(cfg.Errors) > 0 && isCustomError(err, cfg.Errors)) {
 				if cfg.OnTimeout != nil {
 					if toErr := cfg.OnTimeout(ctx); toErr != nil {
 						return toErr
 					}
 				}
 				return fiber.ErrRequestTimeout
 			}
 			return err
-		case <-time.After(timeout):
+		case <-timer.C:
 			if cfg.OnTimeout != nil {
 				err := cfg.OnTimeout(ctx)
 				ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
 				return err
 			}
 			ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
 			return fiber.ErrRequestTimeout
 		}

44-47: Critical: Data race between OnTimeout and handler goroutine.

OnTimeout(ctx) is invoked while the handler goroutine may still be accessing ctx. Per fasthttp documentation, RequestCtx is unsafe for concurrent access—only TimeoutError* methods may be called concurrently.

Call TimeoutErrorWithCode or TimeoutErrorWithResponse before invoking OnTimeout, or document that OnTimeout must not modify ctx (logging/metrics only).

Example fix (calling TimeoutError first):

 		case <-timer.C:
+			ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
 			if cfg.OnTimeout != nil {
-				err := cfg.OnTimeout(ctx)
-				ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
-				return err
+				// OnTimeout is now safe to call after ctx is frozen; modifications are ignored
+				return cfg.OnTimeout(ctx)
 			}
-			ctx.RequestCtx().TimeoutErrorWithCode(utils.StatusMessage(fiber.StatusRequestTimeout), fiber.StatusRequestTimeout)
 			return fiber.ErrRequestTimeout
🧹 Nitpick comments (1)
middleware/timeout/timeout_test.go (1)

24-37: Consider whether runHandler test helper is necessary.

This helper duplicates the custom error handling logic from timeout.go (lines 34-41). If that logic changes, this test code must be updated separately. Consider testing via the public New function instead to avoid maintaining parallel implementations.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73a6df4 and 970c9de.

📒 Files selected for processing (2)
  • middleware/timeout/timeout.go (2 hunks)
  • middleware/timeout/timeout_test.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Apply formatting using gofumpt (Make target: format)
Optimize struct field alignment using betteralign (Make target: betteralign)
Modernize code using gopls modernize (Make target: modernize)

Files:

  • middleware/timeout/timeout_test.go
  • middleware/timeout/timeout.go
🧬 Code graph analysis (2)
middleware/timeout/timeout_test.go (3)
app.go (2)
  • Handler (40-40)
  • Config (113-418)
middleware/timeout/config.go (1)
  • Config (10-26)
constants.go (1)
  • ErrRequestTimeout (126-126)
middleware/timeout/timeout.go (1)
constants.go (2)
  • ErrRequestTimeout (126-126)
  • StatusRequestTimeout (81-81)
🪛 GitHub Actions: golangci-lint
middleware/timeout/timeout_test.go

[error] 173-173: golangci-lint: unused-parameter: parameter 'c' seems to be unused, consider removing or renaming it as _ (revive)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Compare
  • GitHub Check: repeated

@gaby
Copy link
Member

gaby commented Oct 29, 2025

@pjebs There's a race condition happening in the implementation.

@pjebs
Copy link
Contributor Author

pjebs commented Oct 29, 2025

@gaby I'm not sure if it can be fixed without knowing fasthttp better.
Or even betterm the custom error functionality should be removed. It doesn't belong with this implementation.

@pjebs
Copy link
Contributor Author

pjebs commented Oct 29, 2025

When the timeout occurs the handler should not have influence over the response or anything for that matter.
The only question is if the handler is permitted to continue or is instantly cancelled (that can be sorted at a later day).

if cfg.OnTimeout != nil {
return cfg.OnTimeout(ctx)
err := cfg.OnTimeout(ctx)
ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response)
Copy link
Member

Choose a reason for hiding this comment

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

This is a problem, you have to create a brand new response object not the one from the ctx since it gets recycle.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The function TimeoutErrorWithResponse already creates a new response inside

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cancel()
ctx.SetContext(parent)
err := make(chan error, 1)
go func() {
Copy link
Member

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

@pjebs pjebs Oct 29, 2025

Choose a reason for hiding this comment

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

If the timeout occurs first, the go routine will otherwise block forever because there is nothing to receive from the channel. Hence the buffered channel.

@gaby
Copy link
Member

gaby commented Oct 29, 2025

@pjebs one problem is the response, you have to create a new object.

Look at the fasthttp test case for doing this for example https://github.com/valyala/fasthttp/blob/master/server_test.go#L3148

@pjebs
Copy link
Contributor Author

pjebs commented Oct 29, 2025

The middleware should work like this:

  1. If handler returns before timeout => operate as per normal.
  2. If timeout occurs before handler returns => return a timeout http response (or custom timeout response). Totally ignore the handler. (Ideally, the handler should also be notified perhaps by context cancellation that it's no longer needed).

The handler returning errors influencing the final response part is creating race conditions.

@ReneWerner87 ReneWerner87 added this to v3 Oct 29, 2025
@ReneWerner87 ReneWerner87 added this to the v3 milestone Oct 29, 2025
@ReneWerner87
Copy link
Member

ReneWerner87 commented Oct 29, 2025

there is another timeout middleware PR
#3680 and this should solve #3671

maybe we can combine both in one

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

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

🐛 [Bug]: Timeout middleware needs to be redesigned

3 participants