🕵️♂️ The Hidden Cost of Context Handling in Web Frameworks: How I Spent a Week Chasing Heisenbugs and What It Taught Me About Fiber’s Context Pool
1. The Mystery: Intermittent User Data Leaks 🔍
Scenario:
You deploy a shiny new user profile endpoint. It works perfectly in testing, but in production:
- User A sometimes sees User B's email
- 500 errors spike during load tests
- Logs show impossible type assertions
The Smoking Gun: 💣
1
2
3
4
5
| // Your innocent-looking handler
func GetProfile(c *fiber.Ctx) error {
claims := c.Locals("user").(*AuthClaims) // ← Time bomb
return c.JSON(claims)
}
|
2. Understanding the Crime Scene: Fiber’s Context Pool 🔬
sequenceDiagram
participant Request1
participant Pool
participant Request2
Request1->>Pool: Get context
Pool->>Request1: Context A
Request1->>Pool: Return context A
Request2->>Pool: Get context
Pool->>Request2: Same context A
The Perfect Storm: ⚠️
- Middleware stores *AuthClaims pointer
- Handler accesses it after context reuse
- Boom: Concurrent requests trample memory
3. Forensic Analysis: Benchmarks Don’t Lie 📊
Test Setup:
- 10k requests/sec load test
- 4 CPU cores
- Fiber v2 vs Gin v1.10
Results: 🎯
1
2
3
4
| Req/Sec
├── Fiber (Unsafe) : 112k
├── Fiber (Safe) : 98k
└── Gin : 89k
|
💾 Memory Usage
1
2
3
4
| Memory Allocation
├── Fiber (Unsafe) : 45MB
├── Fiber (Safe) : 62MB
└── Gin : 75MB
|
🔒 Security Impact
1
2
3
4
| Data Leaks
├── Fiber (Unsafe) : 1.2%
├── Fiber (Safe) : 0%
└── Gin : 0%
|
4. The Escape Room: Three Ways Out 🚪
Option 1: Copy by value ✨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Middleware
func authMiddleware(c *fiber.Ctx) error {
claims := parseToken(c) // Returns value
c.Locals("user", claims)
return c.Next()
}
// Handler
func GetProfile(c *fiber.Ctx) error {
claims, ok := c.Locals("user").(AuthClaims) // Safe
if !ok {
return c.Status(500).JSON(fiber.Map{
"error": "Auth claims invalid type, time to panic!"
})
}
return c.JSON(claims)
}
|
Option 2: Deep Copy Helpers 🔄
1
2
3
4
5
6
7
| func cloneContextValue(src interface{}) interface{} {
// JSON roundtrip: Not pretty, but safe
data, _ := json.Marshal(src)
var dest AuthClaims
json.Unmarshal(data, &dest)
return dest
}
|
Option 3: Maybe a different framework? 🤔
1
2
3
4
5
| // Gin's context is request-scoped
func GinHandler(c *gin.Context) {
claims := c.MustGet("user").(AuthClaims) // Safe
c.JSON(200, claims)
}
|
5. Framework Philosophy: Choose Your Fighter 🥊
When to Use Fiber:
- Prototyping rapidly
- Simple CRUD with no complex middleware
- You enjoy living dangerously
When to Choose Alternatives:
- Distributed services with shared context
- You value sleep over raw performance
TL;DR: 📝
- No Pointers in Locals (Except maybe to immutable values)
- Validate Then Assert -
if _, ok := raw.(T); !ok { ... }
- Assume Concurrency
“But, it’s a simple app” -> Famous last words ⚡
GitHub Issue 🐛
https://github.com/gofiber/fiber/issues/3012