-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
🚀 feat: Add Load-Shedding Middleware #3264
Closed
Closed
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
337f77a
feat: add load-shedding middleware with unit tests
4af52fb
Merge branch 'gofiber:main' into main
ErfanMomeniii 3b38506
fix: update to avoid race condition
98f76cf
Merge branch 'main' of https://github.com/ErfanMomeniii/fiber
c8703bb
fix: update to avoid race condition
9f64a7b
fix: update to avoid race condition
b9b4251
Merge branch 'main' into main
gaby bd4dc0c
chore: update
6d7fbc2
Merge branch 'main' of https://github.com/ErfanMomeniii/fiber
9196f2e
Merge branch 'main' into main
gaby 5be40f1
Merge branch 'main' into main
ErfanMomeniii 6287ea1
docs: add loadshedding middleware to what's new document
ErfanMomeniii d0607b5
Merge branch 'main' into main
ErfanMomeniii f4682bd
docs: add loadshedding middleware
a476945
docs: fix lint errors
ErfanMomeniii beb5896
docs: fix lint errors
ErfanMomeniii 0fd8df7
docs: fix lint errors
ErfanMomeniii a192bd1
fix: update
ErfanMomeniii 4c8a17e
Update docs
gaby File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
id: loadshedding | ||
--- | ||
|
||
# Load Shedding | ||
|
||
Load Shedding middleware for [Fiber](https://github.com/gofiber/fiber) is designed to enhance server stability by | ||
enforcing timeouts on request processing. It helps manage server load effectively by gracefully handling requests that | ||
exceed a specified timeout duration. This is especially useful in high-traffic scenarios, where preventing server | ||
overload is critical to maintaining service availability and performance. | ||
|
||
## Features | ||
|
||
- **Request Timeout Enforcement**: Ensures that no request exceeds the specified processing time. | ||
- **Customizable Response**: Allows you to define a specific response for timed-out requests. | ||
- **Exclusion Criteria**: Provides flexibility to exclude specific requests from load-shedding logic. | ||
- **Improved Stability**: Helps prevent server crashes under heavy load by shedding excess requests. | ||
|
||
## Use Cases | ||
|
||
- **High-Traffic Scenarios**: Protect critical resources by shedding long-running or resource-intensive requests. | ||
- **Health Check Protection**: Exclude endpoints like `/health` to ensure critical monitoring remains unaffected. | ||
- **Dynamic Load Management**: Use exclusion logic to prioritize or deprioritize requests dynamically. | ||
|
||
--- | ||
|
||
## Signature | ||
|
||
```go | ||
func New(timeout time.Duration, loadSheddingHandler fiber.Handler, exclude func (fiber.Ctx) bool) fiber.Handler | ||
``` | ||
|
||
## Config | ||
|
||
| Property | Type | Description | Default | | ||
|:----------------------|:------------------------------------|:-----------------------------------------------------------------------------------------------|:---------| | ||
| `timeout` | `time.Duration` | Maximum allowed processing time for a request. | Required | | ||
| `loadSheddingHandler` | `fiber.Handler` | Handler invoked for requests that exceed the timeout. | Required | | ||
| `exclude` | `func(fiber.Ctx) bool` | Optional function to exclude specific requests from load-shedding logic. | `nil` | | ||
|
||
## Example Usage | ||
|
||
Import the middleware package and integrate it into your Fiber application: | ||
|
||
```go | ||
import ( | ||
"time" | ||
"github.com/gofiber/fiber/v3" | ||
"github.com/gofiber/fiber/v3/middleware/loadshedding" | ||
) | ||
|
||
func main() { | ||
app := fiber.New() | ||
|
||
// Basic usage with a 5-second timeout | ||
app.Use(loadshedding.New(5*time.Second, func(c fiber.Ctx) error { | ||
return c.Status(fiber.StatusServiceUnavailable).SendString("Service unavailable due to high load") | ||
}, nil)) | ||
|
||
// Advanced usage with exclusion logic for specific endpoints | ||
app.Use(loadshedding.New(3*time.Second, func(c fiber.Ctx) error { | ||
return c.Status(fiber.StatusServiceUnavailable).SendString("Request timed out") | ||
}, func(c fiber.Ctx) bool { | ||
return c.Path() == "/health" | ||
})) | ||
|
||
app.Get("/", func(c fiber.Ctx) error { | ||
time.Sleep(4 * time.Second) // Simulating a long-running request | ||
return c.SendString("Hello, world!") | ||
}) | ||
|
||
app.Listen(":3000") | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package loadshedding | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/gofiber/fiber/v3" | ||
) | ||
|
||
// New creates a middleware handler enforces a timeout on request processing to manage server load. | ||
// If a request exceeds the specified timeout, a custom load-shedding handler is executed. | ||
func New(timeout time.Duration, loadSheddingHandler fiber.Handler, exclude func(fiber.Ctx) bool) fiber.Handler { | ||
return func(c fiber.Ctx) error { | ||
// Skip load-shedding logic for requests matching the exclusion criteria | ||
if exclude != nil && exclude(c) { | ||
return c.Next() | ||
} | ||
|
||
// Create a context with a timeout for the current request | ||
ctx, cancel := context.WithTimeout(c.Context(), timeout) | ||
defer cancel() | ||
|
||
// Set the new context with a timeout | ||
c.SetContext(ctx) | ||
|
||
// Process the request and capture any error | ||
err := c.Next() | ||
|
||
// Create a channel to signal when request processing completes | ||
done := make(chan error, 1) | ||
|
||
// Send the result of the request processing to the channel | ||
go func() { | ||
done <- err | ||
}() | ||
|
||
// Handle either request completion or timeout | ||
select { | ||
case <-ctx.Done(): // Triggered if the timeout expires | ||
return loadSheddingHandler(c) | ||
case err := <-done: // Triggered if request processing completes | ||
return err | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package loadshedding_test | ||
|
||
import ( | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/gofiber/fiber/v3" | ||
"github.com/gofiber/fiber/v3/middleware/loadshedding" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// Helper handlers | ||
func successHandler(c fiber.Ctx) error { | ||
return c.SendString("Request processed successfully!") | ||
} | ||
|
||
func timeoutHandler(c fiber.Ctx) error { | ||
time.Sleep(2 * time.Second) // Simulate a long-running request | ||
return c.SendString("This should not appear") | ||
} | ||
|
||
func loadSheddingHandler(c fiber.Ctx) error { | ||
return c.Status(fiber.StatusServiceUnavailable).SendString("Service Overloaded") | ||
} | ||
|
||
func excludedHandler(c fiber.Ctx) error { | ||
return c.SendString("Excluded route") | ||
} | ||
|
||
// go test -run Test_LoadSheddingExcluded | ||
func Test_LoadSheddingExcluded(t *testing.T) { | ||
t.Parallel() | ||
app := fiber.New() | ||
|
||
// Middleware with exclusion | ||
app.Use(loadshedding.New( | ||
1*time.Second, | ||
loadSheddingHandler, | ||
func(c fiber.Ctx) bool { return c.Path() == "/excluded" }, | ||
)) | ||
app.Get("/", successHandler) | ||
app.Get("/excluded", excludedHandler) | ||
|
||
// Test excluded route | ||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/excluded", nil)) | ||
require.NoError(t, err) | ||
require.Equal(t, fiber.StatusOK, resp.StatusCode) | ||
} | ||
|
||
// go test -run Test_LoadSheddingTimeout | ||
func Test_LoadSheddingTimeout(t *testing.T) { | ||
t.Parallel() | ||
app := fiber.New() | ||
|
||
// Middleware with a 1-second timeout | ||
app.Use(loadshedding.New( | ||
1*time.Second, // Middleware timeout | ||
loadSheddingHandler, | ||
nil, | ||
)) | ||
app.Get("/", timeoutHandler) | ||
|
||
// Create a custom request | ||
req := httptest.NewRequest(fiber.MethodGet, "/", nil) | ||
|
||
// Test timeout behavior | ||
resp, err := app.Test(req, fiber.TestConfig{ | ||
Timeout: 3 * time.Second, // Ensure the test timeout exceeds middleware timeout | ||
}) | ||
require.NoError(t, err) | ||
require.Equal(t, fiber.StatusServiceUnavailable, resp.StatusCode) | ||
} | ||
|
||
// go test -run Test_LoadSheddingSuccessfulRequest | ||
func Test_LoadSheddingSuccessfulRequest(t *testing.T) { | ||
t.Parallel() | ||
app := fiber.New() | ||
|
||
// Middleware with sufficient time for request to complete | ||
app.Use(loadshedding.New( | ||
2*time.Second, | ||
loadSheddingHandler, | ||
nil, | ||
)) | ||
app.Get("/", successHandler) | ||
|
||
// Test successful request | ||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil)) | ||
require.NoError(t, err) | ||
require.Equal(t, fiber.StatusOK, resp.StatusCode) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
what is the difference to the timeout middleware? could you also store a handler in the config that handles these cases?