Skip to content

Adding a basic token based algo#1

Merged
serroba merged 6 commits into
mainfrom
dev
Dec 31, 2025
Merged

Adding a basic token based algo#1
serroba merged 6 commits into
mainfrom
dev

Conversation

@serroba
Copy link
Copy Markdown
Owner

@serroba serroba commented Dec 31, 2025

No description provided.

Copilot AI review requested due to automatic review settings December 31, 2025 03:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a basic token bucket rate limiting algorithm implementation in Go, including the core limiter logic, comprehensive test coverage, and CI/CD infrastructure.

Key Changes:

  • Implements a token bucket rate limiter with configurable capacity and refill rate
  • Adds test infrastructure with clock injection for deterministic testing
  • Sets up GitHub Actions CI workflow with linting, testing, and coverage checks

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
token/rate.go Core implementation of the token bucket rate limiter with Clock abstraction for testability
token/rate_test.go Unit tests for the Limiter.Allow() method covering various capacity and rate scenarios
go.mod Go module definition for the rate limiter project
.golangci.yml Comprehensive golangci-lint configuration with extensive linter rules enabled
.github/workflows/ci.yml CI pipeline with tests, linting, and 95% coverage requirement
.github/dependabot.yml Automated dependency updates for GitHub Actions and Go modules

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread token/rate_test.go Outdated
Comment on lines +37 to +38
{name: "Test basic", fields: fields{capacity: 0, rate: 1}, want: false},
{name: "Test basic", fields: fields{capacity: 1, rate: 1}, want: true},
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first two test cases have identical names "Test basic". Each test case should have a unique, descriptive name that clearly identifies what scenario is being tested. Consider renaming them to something like "Test with zero capacity" and "Test with capacity of one".

Suggested change
{name: "Test basic", fields: fields{capacity: 0, rate: 1}, want: false},
{name: "Test basic", fields: fields{capacity: 1, rate: 1}, want: true},
{name: "Test with zero capacity", fields: fields{capacity: 0, rate: 1}, want: false},
{name: "Test with capacity of one", fields: fields{capacity: 1, rate: 1}, want: true},

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go
Comment on lines +42 to +49

func (lim *Limiter) Allow() bool {
lim.refill()

if lim.tokens >= 1 {
lim.tokens--

return true
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Limiter is not thread-safe. The Allow method reads and modifies shared state (tokens, lastRefillAt) without synchronization, which can lead to race conditions when accessed from multiple goroutines. Consider adding a mutex (sync.Mutex) to protect access to these fields, especially since rate limiters are commonly used in concurrent scenarios.

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go
Comment on lines +51 to +58

return false
}

func (lim *Limiter) refill() {
t := lim.clock.Now()
if t.Before(lim.lastRefillAt) {
return
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refill method modifies shared state without synchronization, which will cause race conditions in concurrent usage. This method should be protected by the same mutex that guards the Allow method.

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go Outdated
Comment on lines +21 to +30
func NewLimiter(capacity, rate float64) *Limiter {
clock := realClock{}

return &Limiter{
capacity: capacity,
tokens: capacity,
rate: rate,
clock: clock,
lastRefillAt: clock.Now(),
}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NewLimiter function does not validate that capacity and rate are non-negative values. Negative or zero values for these parameters could lead to unexpected behavior. Consider adding validation to ensure these parameters are positive, or document the expected behavior for edge cases.

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go
package token

import "time"

Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public Clock interface lacks documentation. According to Go conventions, exported types should have a comment describing their purpose. Consider adding a comment like "// Clock provides the current time, allowing for time injection in tests."

Suggested change
// Clock provides the current time, allowing for time injection in tests.

Copilot uses AI. Check for mistakes.
Comment thread token/rate_test.go
Comment on lines +44 to +46
},
{
name: "Test after many attempts",
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test should also verify the return value of previous Allow() calls in the loop to ensure the limiter behaves correctly for each attempt, not just the final one. For example, in the "Test After 1 attempt" case, the first Allow() should return true before the second one returns false.

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go Outdated
Comment on lines +32 to +40

func NewLimiterWithClock(capacity, rate float64, clock Clock) *Limiter {
return &Limiter{
capacity: capacity,
tokens: capacity,
rate: rate,
clock: clock,
lastRefillAt: clock.Now(),
}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NewLimiterWithClock function does not validate that capacity and rate are non-negative values, and does not check if clock is nil. Negative values or a nil clock could lead to unexpected behavior or panics. Consider adding validation for these parameters.

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go
lastRefillAt: clock.Now(),
}
}

Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public NewLimiterWithClock function lacks documentation. According to Go conventions, exported functions should have a comment describing their purpose. Consider adding a comment explaining this is primarily for testing purposes and what each parameter represents.

Suggested change
// NewLimiterWithClock creates a Limiter with the given capacity and refill rate,
// using the provided Clock implementation. This is primarily intended for tests
// that need to control the passage of time.

Copilot uses AI. Check for mistakes.
Comment thread token/rate.go
lastRefillAt: clock.Now(),
}
}

Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public Allow method lacks documentation. According to Go conventions, exported methods should have a comment describing their purpose and behavior. Consider adding a comment explaining that it attempts to consume one token and returns whether the operation was successful.

Suggested change
// Allow attempts to consume one token from the limiter and reports whether it succeeded.

Copilot uses AI. Check for mistakes.
Comment thread token/rate_test.go
Comment on lines +22 to +53
func TestLimiter_Allow(t *testing.T) {
type fields struct {
capacity float64
rate float64
}

clock := &testClock{now: time.Now()}

tests := []struct {
name string
fields fields
previousAttempts int
advanceBy time.Duration
want bool
}{
{name: "Test basic", fields: fields{capacity: 0, rate: 1}, want: false},
{name: "Test basic", fields: fields{capacity: 1, rate: 1}, want: true},
{
name: "Test After 1 attempt",
fields: fields{capacity: 1, rate: 1},
previousAttempts: 1,
want: false,
},
{
name: "Test after many attempts",
fields: fields{capacity: 5, rate: 2},
previousAttempts: 4,
want: true,
},
{
name: "Test after many attempts and 2 sec",
fields: fields{capacity: 5, rate: 2},
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no test coverage for concurrent access to the Limiter. Given that rate limiters are commonly used in concurrent scenarios, tests should verify thread-safe behavior by calling Allow() from multiple goroutines simultaneously.

Copilot uses AI. Check for mistakes.
@serroba serroba merged commit 04543a6 into main Dec 31, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants