feat(middleware): add concurrency_limit#11
Merged
Conversation
Third and last of the defensive middleware. Caps the number of in-flight requests using golang.org/x/sync/semaphore and rejects overflow immediately with 503 response.CodeServiceUnavailable. Immediate rejection via semaphore.TryAcquire is intentional: under load we would rather shed traffic fast than let clients hog connections waiting for a slot. Per Phase 2 instruction §14 no Retry-After header is emitted. maxInFlight <= 0 disables the limiter entirely (pass-through), matching the convention used by body_limit and rate_limit. The parameter name maxInFlight (not max) avoids shadowing the Go 1.21+ built-in max function. Not yet wired into app.New(); all three defensive middleware plus an end-to-end chain test will land together in the next PR. Tests cover capacity passthrough, beyond-capacity rejection with the standard error body, recovery after slot release, multiple-slot independence (capacity 2, third rejected while two in flight), and the disabled paths for zero and negative maxInFlight. The concurrent tests synchronize via a gate channel + signal channel so the race detector sees clean ordering.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
PR 5/9 of the Phase 2 series. Adds the third and final defensive middleware: a global in-flight request cap using `golang.org/x/sync/semaphore`. Overflow is rejected immediately with `503` + `response.CodeServiceUnavailable`; requests do not queue.
Not wired into `app.New()`. PR 6 will insert `body_limit` + `rate_limit` + `concurrency_limit` into the chain in §6 order and add an end-to-end integration test, completing the defensive stack.
Key design decisions
`TryAcquire` (immediate rejection), not `Acquire` (queue)
`semaphore.Weighted` supports both. This middleware uses `TryAcquire` so overflowing requests get `503` instantly rather than blocking on a slot. Rationale: under load we want to shed traffic fast. Queueing hogs connections and delays client retry/backoff logic, and in the worst case compounds the overload. Phase 2 instruction §14 matches this choice ("超過時は 503") and explicitly says no `Retry-After` header is needed in Phase 2.
`maxInFlight <= 0` disables
Matches the convention established by `body_limit` and `rate_limit`. Useful for tests and opt-in no-limit local dev.
Parameter name `maxInFlight` (not `max`)
Go 1.21+ introduced `max` as a predeclared built-in; using `max` as a parameter name shadows it and `revive` flags it. `maxInFlight` is more descriptive anyway.
Test plan
6 tests, all race-clean:
The concurrent tests synchronize via a gate channel (`release`) + signal channel (`inHandler`) rather than `time.Sleep`, so the race detector sees clean ordering and the tests do not rely on timing.
Local checks
Dependencies added
```
golang.org/x/sync v0.20.0
```
Per `docs/development_rules.md` §3, an explicitly-allowed dependency for concurrency limiting.
Phase 2 context
With this PR, all three defensive middleware will be complete and ready for chain wiring in PR 6. Intended to be squash-merged.