-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
fix up timeout middleware #3826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
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 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. 📒 Files selected for processing (2)
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughThe 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
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. Comment |
Summary of ChangesHello @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 Highlights
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this 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.
middleware/timeout/timeout.go
Outdated
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
middleware/timeout/timeout_test.go
Outdated
| } | ||
| time.Sleep(100 * time.Millisecond) | ||
| return context.DeadlineExceeded | ||
| return c.SendString("should not reach") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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 contextUpdate 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.DeadlineExceededmakes the subsequentreturn 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
📒 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.gomiddleware/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
runHandlerduplicates the custom‑error/OnTimeout flow. If production logic changes, update this helper to match to avoid false positives.
middleware/timeout/timeout.go
Outdated
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify select; use a timer and stop it to avoid leaks; handle custom‑error path consistently
- The surrounding
foris unnecessary; both branches return. - Prefer
time.NewTimeranddefer timer.Stop()to avoid retaining timers when the handler finishes first. - When a custom “timeout-like” error occurs, consider freezing the response if
OnTimeoutwrote 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.ResponseforOnTimeoutand passing that toTimeoutErrorWithResponse, or - Documenting that
OnTimeoutmust not modifyctx(log/metrics only), and callingTimeoutErrorWithCodefirst, 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 fLength of output: 31
🏁 Script executed:
#!/bin/bash
# Display the full context of timeout.go to understand the handler flow
cat middleware/timeout/timeout.goLength of output: 1595
🏁 Script executed:
#!/bin/bash
# Search for how OnTimeout is called and in what context
rg -n "OnTimeout" middleware/timeout/ -C 5Length 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 -iLength 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 2Length 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 3Length 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 -5Length of output: 36
🏁 Script executed:
#!/bin/bash
# Get the repository structure to understand context better
ls -la middleware/ | head -20Length of output: 1103
🏁 Script executed:
#!/bin/bash
# Check for any middleware that spawns goroutines
rg -n "^\s*go\s+" middleware/timeout/timeout.goLength 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.goLength 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 -10Length 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 fasthttpLength of output: 36
🏁 Script executed:
#!/bin/bash
# Look at how fiber wraps fasthttp's timeout handling
rg -n "TimeoutErrorWithResponse\|TimeoutErrorWithCode" middleware/ -B 3 -A 3Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find ctx type definition and its methods
rg -n "func.*\(ctx.*\).*TimeoutError" middleware/ -C 2Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check for RequestCtx documentation or usage patterns
rg -n "RequestCtx\(\)" middleware/timeout/ -C 3Length of output: 681
🏁 Script executed:
#!/bin/bash
# Look for the return statement and control flow after OnTimeout
sed -n '40,55p' middleware/timeout/timeout.goLength 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 -20Length 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.goLength of output: 1595
🏁 Script executed:
#!/bin/bash
# Get line count to understand file size
wc -l middleware/timeout/timeout.goLength 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.goLength 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 5Length 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 2Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the exact implementation of TimeoutErrorWithResponse
rg -n "TimeoutErrorWithResponse" --type go -C 5Length of output: 731
🏁 Script executed:
#!/bin/bash
# Find the handler function signature
rg -n "func.*handler" middleware/timeout/timeout.go -C 3Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for goroutine spawning in timeout context
rg -n "go\s*\(" middleware/timeout/timeout.go -C 3Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if RequestCtx is modified after OnTimeout call
sed -n '45,60p' middleware/timeout/timeout.goLength of output: 516
🏁 Script executed:
#!/bin/bash
# Look for any fasthttp imports
rg -n "import\|fasthttp" middleware/timeout/timeout.go | head -20Length of output: 0
🏁 Script executed:
#!/bin/bash
# Get complete context - show first 100 lines of file
head -100 middleware/timeout/timeout.goLength 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 5Length 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.goLength 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 -nLength of output: 2050
🏁 Script executed:
#!/bin/bash
# Get the complete implementation to understand the flow
cat middleware/timeout/timeout.go | head -150Length 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 2Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for channel operations or synchronization
rg -n "<-\|chan\|->\|select" middleware/timeout/timeout.goLength 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.goLength of output: 23
🏁 Script executed:
#!/bin/bash
# Find line 45-51 exactly and surrounding context
awk 'NR>=40 && NR<=60' middleware/timeout/timeout.goLength 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 -30Length of output: 300
🏁 Script executed:
#!/bin/bash
# Full file dump for analysis
cat middleware/timeout/timeout.goLength 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.goLength 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 -30Length of output: 199
🏁 Script executed:
#!/bin/bash
# Verify the handler execution context
awk 'NR>=1 && NR<=150' middleware/timeout/timeout.goLength of output: 1595
🏁 Script executed:
#!/bin/bash
# Get basic file information first
file middleware/timeout/timeout.go && wc -l middleware/timeout/timeout.goLength of output: 42
🏁 Script executed:
#!/bin/bash
# Simple head to check file exists and readable
head -20 middleware/timeout/timeout.goLength 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 -20Length of output: 612
🏁 Script executed:
#!/bin/bash
# Search for timeout.go in entire repo
find . -name "timeout.go" 2>/dev/nullLength 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/nullLength of output: 97
🏁 Script executed:
#!/bin/bash
# Try to identify the repo structure
find . -name "*.go" -path "*middleware*" 2>/dev/null | head -5Length of output: 178
🏁 Script executed:
#!/bin/bash
# Find any middleware files
find . -type d -name middleware 2>/dev/nullLength of output: 30
🏁 Script executed:
#!/bin/bash
# Simple directory listing
lsLength 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 -20Length of output: 112
🏁 Script executed:
#!/bin/bash
# Check for hidden directories and git
ls -laLength of output: 4422
🏁 Script executed:
#!/bin/bash
# Get the repository details
cat README.md 2>/dev/null | head -30Length of output: 0
🏁 Script executed:
#!/bin/bash
# Try general fiber or middleware patterns
find . -type f -name "*.go" 2>/dev/null | head -30Length of output: 640
🏁 Script executed:
#!/bin/bash
# Find Makefile or build configuration
find . -name Makefile -o -name "*.mk" 2>/dev/null | head -5Length 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 -30Length of output: 606
🏁 Script executed:
#!/bin/bash
# Verify repository was cloned - check git
git log --oneline 2>/dev/null | head -5Length 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 timeoutLength of output: 99
🏁 Script executed:
#!/bin/bash
# General search for timeout in Go files
rg -l "timeout" . --type go 2>/dev/null | head -10Length of output: 296
🏁 Script executed:
#!/bin/bash
# Search for OnTimeout in any Go file
rg "OnTimeout" . --type go 2>/dev/nullLength of output: 2869
🏁 Script executed:
#!/bin/bash
# Look for TimeoutError in Go files
rg "TimeoutError" . --type go 2>/dev/null | head -5Length 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/nullLength of output: 30
🏁 Script executed:
#!/bin/bash
# Search for fiber in dependencies
rg "github.com/gofiber/fiber" . 2>/dev/null | head -3Length 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 -20Length 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 dLength 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 -10Length of output: 662
🏁 Script executed:
#!/bin/bash
# List all directories
ls -d */ 2>/dev/null || find . -maxdepth 2 -type dLength 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/nullLength of output: 253
🏁 Script executed:
#!/bin/bash
# Look for any configuration or implementation
rg "fiber.Context" . --type go 2>/dev/null | head -5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Final attempt - show everything at root
ls -la && pwdLength 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 -10Length 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 timeoutLength of output: 94
🏁 Script executed:
#!/bin/bash
# Try git diff to see actual changes
git diff HEAD~1 --name-only 2>/dev/null | head -10Length 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 -5Length 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 -20Length of output: 0
🏁 Script executed:
#!/bin/bash
# Examine what branches exist
git for-each-ref --format='%(refname:short)' 2>/dev/null | head -20Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if we're on a PR branch
git log -1 --pretty=format:"%B" 2>/dev/nullLength 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 -20Length of output: 229
🏁 Script executed:
#!/bin/bash
# Specific search for timeout package
find . -name "*.go" | xargs grep -l "package.*timeout" 2>/dev/nullLength of output: 99
🏁 Script executed:
#!/bin/bash
# Search for the code snippet mentioned in the review
rg "OnTimeout.*nil" . --type go 2>/dev/nullLength of output: 465
🏁 Script executed:
#!/bin/bash
# Look for TimeoutErrorWithResponse call
rg "TimeoutErrorWithResponse" . --type go 2>/dev/nullLength of output: 106
🏁 Script executed:
#!/bin/bash
# Try another approach - grep recursively
grep -r "OnTimeout" . --include="*.go" 2>/dev/null | head -5Length of output: 191
🏁 Script executed:
#!/bin/bash
# Search the complete directory tree
find . -type f -name "*.go" 2>/dev/null | wc -lLength of output: 3
🏁 Script executed:
#!/bin/bash
# Get everything at depth 1
ls -la ./*/ 2>/dev/null | head -50Length of output: 2644
🏁 Script executed:
#!/bin/bash
# Dump all directory names up to depth 4
find . -type d -not -path '*/\.*' 2>/dev/null | head -50Length of output: 882
🏁 Script executed:
#!/bin/bash
# Show git references
git rev-parse HEAD 2>/dev/null && git branch 2>/dev/nullLength of output: 65
🏁 Script executed:
#!/bin/bash
# Try to get the actual changed file paths from git
git show --pretty="" --name-only 2>/dev/nullLength of output: 64
🏁 Script executed:
#!/bin/bash
# Worst case - brute force search
grep -r "if cfg.OnTimeout" . 2>/dev/null | head -3Length 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/nullLength of output: 0
🏁 Script executed:
#!/bin/bash
# Try even simpler - search for "OnTimeout" anywhere
grep -r "OnTimeout" . --include="*.go" 2>/dev/nullLength of output: 2869
🏁 Script executed:
#!/bin/bash
# Check if repository is empty or not cloned
git rev-list --all 2>/dev/null | wc -lLength of output: 4
🏁 Script executed:
#!/bin/bash
# Show git log with files
git log --name-status 2>/dev/null | head -50Length 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/nullLength 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 -30Length 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 -50Length 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 timeoutLength of output: 332
🏁 Script executed:
#!/bin/bash
# If files are staged
git ls-files 2>/dev/null | grep timeoutLength of output: 120
🏁 Script executed:
#!/bin/bash
# Check git index
git diff-index --cached --name-only HEAD 2>/dev/null | head -10Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check what git sees
git status --porcelain 2>/dev/null | head -20Length 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 -5Length of output: 5331
🏁 Script executed:
#!/bin/bash
# Check the PR files from Git
git show --name-only HEAD 2>/dev/null | head -20Length 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}")
EOFLength 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 || pwdLength 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 timeoutLength 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
EOFLength 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}")
EOFLength 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
doneLength 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 -20Length 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:
- Line 29 spawned a goroutine executing
h(ctx)(the handler) - Line 46 calls
OnTimeout(ctx)from the main goroutine while the handler may still be running OnTimeoutis afiber.Handlerthat can modifyctx- 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(orTimeoutErrorWithCode) first to mark the context as timed out, preventing further modifications - Or document that
OnTimeoutcallbacks must not modifyctx(logs/metrics only) - Or pass a separate
Responseobject toOnTimeoutinstead of the sharedctx - 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.
There was a problem hiding this 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.Aftercreates a timer that isn't stopped when the handler finishes first, potentially leaking resources under high load. Usetime.NewTimerwithdefer 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 accessingctx. Per fasthttp documentation,RequestCtxis unsafe for concurrent access—onlyTimeoutError*methods may be called concurrently.Call
TimeoutErrorWithCodeorTimeoutErrorWithResponsebefore invokingOnTimeout, or document thatOnTimeoutmust not modifyctx(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 publicNewfunction instead to avoid maintaining parallel implementations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.gomiddleware/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
|
@pjebs There's a race condition happening in the implementation. |
|
@gaby I'm not sure if it can be fixed without knowing fasthttp better. |
|
When the timeout occurs the handler should not have influence over the response or anything for that matter. |
| if cfg.OnTimeout != nil { | ||
| return cfg.OnTimeout(ctx) | ||
| err := cfg.OnTimeout(ctx) | ||
| ctx.RequestCtx().TimeoutErrorWithResponse(&ctx.RequestCtx().Response) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
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.
|
@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 |
|
The middleware should work like this:
The handler returning errors influencing the final response part is creating race conditions. |
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.
Type of change
Please delete options that are not relevant.
Checklist
Before you submit your pull request, please make sure you meet these requirements:
/docs/directory for Fiber's documentation.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