Skip to content

Commit eca4c49

Browse files
authored
feat: add rate limiter middleware and update to Go 1.24
feat: add rate limiter middleware and update to Go 1.24
2 parents c38a59a + cea3dfd commit eca4c49

18 files changed

Lines changed: 335 additions & 105 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
runs-on: ubuntu-latest
1212

1313
steps:
14-
- name: set up go 1.23
14+
- name: set up go 1.24
1515
uses: actions/setup-go@v3
1616
with:
17-
go-version: "1.23"
17+
go-version: "1.24"
1818
id: go
1919

2020
- name: checkout
@@ -29,9 +29,9 @@ jobs:
2929
go build -race
3030
3131
- name: golangci-lint
32-
uses: golangci/golangci-lint-action@v3
32+
uses: golangci/golangci-lint-action@v7
3333
with:
34-
version: latest
34+
version: v2.1.6
3535
skip-pkg-cache: true
3636

3737
- name: install goveralls

.golangci.yml

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,83 @@
1-
linters-settings:
2-
govet:
3-
shadow: true
4-
gocyclo:
5-
min-complexity: 15
6-
maligned:
7-
suggest-new: true
8-
goconst:
9-
min-len: 2
10-
min-occurrences: 2
11-
misspell:
12-
locale: US
13-
lll:
14-
line-length: 140
15-
gocritic:
16-
enabled-tags:
17-
- performance
18-
- style
19-
- experimental
20-
disabled-checks:
21-
- wrapperFunc
22-
1+
version: "2"
2+
run:
3+
concurrency: 4
234
linters:
5+
default: none
246
enable:
25-
- staticcheck
26-
- revive
27-
- govet
28-
- unconvert
29-
- gosec
30-
- unparam
31-
- typecheck
32-
- ineffassign
33-
- stylecheck
34-
- gochecknoinits
35-
- copyloopvar
36-
- gocritic
37-
- nakedret
38-
- gosimple
39-
- prealloc
40-
- unused
417
- contextcheck
428
- copyloopvar
439
- decorder
4410
- errorlint
4511
- exptostd
4612
- gochecknoglobals
47-
- gofmt
48-
- goimports
13+
- gochecknoinits
14+
- gocritic
15+
- gosec
16+
- govet
17+
- ineffassign
4918
- intrange
19+
- nakedret
5020
- nilerr
21+
- prealloc
5122
- predeclared
23+
- revive
24+
- staticcheck
5225
- testifylint
5326
- thelper
54-
fast: false
55-
disable-all: true
56-
57-
58-
run:
59-
concurrency: 4
60-
61-
issues:
62-
exclude-rules:
63-
- text: "G114: Use of net/http serve function that has no support for setting timeouts"
64-
linters:
65-
- gosec
66-
- linters:
67-
- unparam
68-
- revive
69-
path: _test\.go$
70-
text: "unused-parameter"
71-
- linters:
72-
- prealloc
73-
path: _test\.go$
74-
text: "Consider pre-allocating"
75-
- linters:
76-
- gosec
77-
- intrange
78-
path: _test\.go$
79-
exclude-use-default: false
27+
- unconvert
28+
- unparam
29+
- unused
30+
- nestif
31+
settings:
32+
goconst:
33+
min-len: 2
34+
min-occurrences: 2
35+
gocritic:
36+
disabled-checks:
37+
- wrapperFunc
38+
enabled-tags:
39+
- performance
40+
- style
41+
- experimental
42+
gocyclo:
43+
min-complexity: 15
44+
govet:
45+
enable:
46+
- shadow
47+
lll:
48+
line-length: 140
49+
misspell:
50+
locale: US
51+
exclusions:
52+
generated: lax
53+
rules:
54+
- linters:
55+
- gosec
56+
text: 'G114: Use of net/http serve function that has no support for setting timeouts'
57+
- linters:
58+
- revive
59+
- unparam
60+
path: _test\.go$
61+
text: unused-parameter
62+
- linters:
63+
- prealloc
64+
path: _test\.go$
65+
text: Consider pre-allocating
66+
- linters:
67+
- gosec
68+
- intrange
69+
path: _test\.go$
70+
paths:
71+
- third_party$
72+
- builtin$
73+
- examples$
74+
formatters:
75+
enable:
76+
- gofmt
77+
- goimports
78+
exclusions:
79+
generated: lax
80+
paths:
81+
- third_party$
82+
- builtin$
83+
- examples$

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ The package supports middleware pattern similar to HTTP middleware in Go. Middle
225225
- Retries with backoff
226226
- Timeouts
227227
- Panic recovery
228+
- Rate limiting
228229
- Metrics and logging
229230
- Error handling
230231

@@ -242,7 +243,10 @@ p.Use(middleware.Recovery[string](func(p interface{}) {
242243
}))
243244

244245
// Add validation before processing
245-
p.Use(middleware.Validate([string]validator))
246+
p.Use(middleware.Validator[string](validator))
247+
248+
// Add rate limiting
249+
p.Use(middleware.RateLimiter[string](10, 5)) // 10 requests/sec with burst of 5
246250
```
247251

248252
Custom middleware:

examples/collector_errors/go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
6+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7+
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
8+
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

examples/collector_errors/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func main() {
9999
fmt.Printf("Processing time: %v\n", stats.ProcessingTime.Round(time.Millisecond))
100100
fmt.Printf("Total time: %v\n", stats.TotalTime.Round(time.Millisecond))
101101

102-
// Calculate average duration for successes and failures
102+
// calculate average duration for successes and failures
103103
var totalSuccessDuration, totalFailureDuration time.Duration
104104
for _, s := range successes {
105105
totalSuccessDuration += s.Duration
@@ -136,7 +136,7 @@ func main() {
136136
results := errorsByType[errType]
137137
fmt.Printf("\n• %s (%d occurrences):\n", errType, len(results))
138138

139-
// Sort results by timestamp
139+
// sort results by timestamp
140140
sort.Slice(results, func(i, j int) bool {
141141
return results[i].Timestamp.Before(results[j].Timestamp)
142142
})

examples/direct_chain/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func ProcessStrings(ctx context.Context, input []string) ([]finalData, error) {
105105
results = append(results, v)
106106
}
107107

108-
// Print debug statistics
108+
// print debug statistics
109109
fmt.Printf("\nProcessing statistics:\n")
110110
fmt.Printf("Total items submitted: %d\n", submitted.Load())
111111
fmt.Printf("Items passed filter (>2 'a's): %d\n", filtered.Load())

examples/middleware/README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This example demonstrates how to use middleware in [go-pkgz/pool](https://github
1313
- Input validation before processing
1414
- Automatic retries for failed tasks
1515
- Panic recovery for robustness
16+
- Rate limiting for flow control
1617
- Structured logging for observability
1718

1819
3. Real-world patterns:
@@ -26,6 +27,7 @@ This example demonstrates how to use middleware in [go-pkgz/pool](https://github
2627
- Task validation before processing
2728
- Automatic retries with exponential backoff
2829
- Panic recovery with custom handler
30+
- Rate limiting with token bucket algorithm
2931
- Structured JSON logging
3032
- Performance metrics collection
3133
- Configurable worker count and retry attempts
@@ -72,10 +74,11 @@ The implementation demonstrates several key concepts:
7274
2. Middleware composition:
7375
```go
7476
pool.New[Task](workers, makeWorker()).Use(
75-
middleware.Validate(validator), // validate first
76-
middleware.Retry[Task](retries), // then retry on failure
77+
middleware.Validator(validator), // validate first
78+
middleware.Retry[Task](retries), // then retry on failure
7779
middleware.Recovery[Task](handler), // recover from panics
78-
customLogger, // log everything
80+
middleware.RateLimiter[Task](5, 3), // rate limit to 5/sec
81+
customLogger, // log everything
7982
)
8083
```
8184

@@ -114,6 +117,20 @@ The implementation demonstrates several key concepts:
114117
"duration_ms": 100,
115118
"error": "failed to process task 2"
116119
}
120+
{
121+
"time": "2025-02-12T10:00:00Z",
122+
"level": "INFO",
123+
"msg": "submitting rate-limited tasks"
124+
}
125+
{
126+
"time": "2025-02-12T10:00:00Z",
127+
"level": "INFO",
128+
"msg": "pool finished",
129+
"processed": 14,
130+
"errors": 2,
131+
"total_time": "3.2s",
132+
"duration": "2.1s"
133+
}
117134
```
118135

119136
## Architecture
@@ -137,4 +154,5 @@ Each component is isolated and has a single responsibility, making the code easy
137154
- The first middleware wraps the outermost layer
138155
- Built-in middleware handles common patterns
139156
- Custom middleware can add any functionality
157+
- Rate limiting is shared across all workers in the pool
140158
- Structured logging as an example of cross-cutting concern

examples/middleware/go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ go 1.24
44

55
require github.com/go-pkgz/pool v0.7.0
66

7-
require golang.org/x/sync v0.11.0 // indirect
7+
require (
8+
golang.org/x/sync v0.14.0 // indirect
9+
golang.org/x/time v0.11.0 // indirect
10+
)
811

912
replace github.com/go-pkgz/pool => ../..

examples/middleware/go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
44
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
66
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7-
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
8-
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
7+
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
8+
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
9+
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
10+
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
911
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1012
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

examples/middleware/main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ func runPool(ctx context.Context, p *pool.WorkerGroup[Task], cfg config) error {
7979
p.Submit(task)
8080
}
8181

82+
// demonstrate rate limiting
83+
cfg.logger.Info("submitting rate-limited tasks")
84+
start := time.Now()
85+
for i := 0; i < 10; i++ {
86+
p.Submit(Task{ID: fmt.Sprintf("rate-%d", i), Priority: 3, Payload: "rate limited task"})
87+
}
88+
8289
// close pool and wait for completion
8390
if err := p.Close(ctx); err != nil {
8491
return err
@@ -87,7 +94,7 @@ func runPool(ctx context.Context, p *pool.WorkerGroup[Task], cfg config) error {
8794
// print final metrics
8895
metrics := p.Metrics().GetStats()
8996
cfg.logger.Info("pool finished", "processed", metrics.Processed, "errors", metrics.Errors,
90-
"total_time", metrics.TotalTime.String())
97+
"total_time", metrics.TotalTime.String(), "duration", time.Since(start).String())
9198

9299
return nil
93100
}
@@ -99,7 +106,8 @@ func makePool(cfg config) *pool.WorkerGroup[Task] {
99106
middleware.Recovery[Task](func(p interface{}) { // recover from panics
100107
cfg.logger.Error("panic recovered", "error", fmt.Sprint(p))
101108
}),
102-
makeStructuredLogger(cfg.logger), // custom structured logging
109+
middleware.RateLimiter[Task](5, 3), // rate limit: 5 tasks/second with burst of 3
110+
makeStructuredLogger(cfg.logger), // custom structured logging
103111
)
104112
}
105113

0 commit comments

Comments
 (0)