Skip to content

refactor: use cache store from tinyauth#29

Open
steveiliop56 wants to merge 1 commit into
mainfrom
refactor/cache-service
Open

refactor: use cache store from tinyauth#29
steveiliop56 wants to merge 1 commit into
mainfrom
refactor/cache-service

Conversation

@steveiliop56

@steveiliop56 steveiliop56 commented Jun 13, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Refactor
    • Redesigned internal caching system with generic type support and time-based expiration
    • Updated rate limiter to use new cache implementation with periodic cleanup
    • Modified rate limiter response headers to include x-ratelimit-limit, x-ratelimit-used, and x-ratelimit-remaining counts
    • Enhanced IP address parsing for rate limit client identification

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 13, 2026
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR replaces a non-generic Cache type with a new generic CacheStore[T] that supports TTL-based expiration, bounded-size eviction, and atomic multi-operation access. The rate limiter is updated to use an internal cache store and periodic background sweeping instead of a mutex-guarded external cache. Cache instantiation is removed from main.go.

Changes

Cache subsystem and rate limiter migration

Layer / File(s) Summary
Generic cache data model and core operations
cache_store.go
Defines CacheStore[T] struct with TTL-aware entry storage and insertion-order tracking. Implements NewCacheStore constructor, WithLock for atomic multi-step operations, Set with capacity enforcement via evictOne, Get with expiration checking, Update for conditional modification, and Delete with order slice cleanup.
Cache maintenance and eviction
cache_store.go
Adds Sweep to remove expired entries, evictOne to enforce maxSize by preferring expired entries for deletion or falling back to oldest insertion order, and utility methods Size and Clear.
Rate limiter redesign using generic cache
rate_limiter.go
Removes *Cache parameter from NewRateLimiter, replaces it with internal CacheStore[int] and a background ticker that calls Sweep() every minute. Rewrites the limit handler to compute usage count within atomic WithLock block, sets rate-limit headers (x-ratelimit-limit, x-ratelimit-used, x-ratelimit-remaining), and returns HTTP 429 on limit exceeded. Simplifies getClientIP to parse comma-separated IPs from the x-forwarded-for header.
Application integration wiring
main.go
Moves router initialization before rate limiter creation and removes the NewCache() call, constructing rate limiter with only RateLimitCount and TrustedProxies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring: replacing the custom Cache implementation with a new CacheStore generic type.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
rate_limiter.go (1)

32-39: 💤 Low value

Background goroutine has no shutdown mechanism.

The ticker goroutine runs indefinitely with no way to stop it. In the current application (single RateLimiter living for the process lifetime), this is acceptable. However, for testability and potential reuse, consider adding a shutdown path via a context or stop channel.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rate_limiter.go` around lines 32 - 39, The background ticker goroutine in
rate_limiter.go that calls rl.caches.ratelimit.Sweep() needs a shutdown path;
add a stop mechanism on the RateLimiter (e.g., a stopCh chan struct{} field or
accept a context) and initialize it when creating the RateLimiter (e.g., in
NewRateLimiter), change the anonymous goroutine to select between ticker.C and
the stop channel (or ctx.Done()) so it breaks and defers ticker.Stop(), and
expose a Stop() (or Close(ctx)) method on RateLimiter that signals the channel
(or cancels the context) to terminate the goroutine cleanly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rate_limiter.go`:
- Around line 96-103: The parsed X-Forwarded-For value can include spaces, so
trim whitespace from the extracted IP to avoid inconsistent keys: after
computing firstIp := strings.SplitN(xForwardedFor, ",", 2)[0] call firstIp =
strings.TrimSpace(firstIp) (or equivalent) before checking if firstIp != "" and
returning it; update references to xForwardedFor/firstIp in that block
accordingly.
- Around line 52-66: The rate counter is not persisted when a request pushes
current past rl.config.RateLimitCount so x-ratelimit-used becomes stale; inside
the rl.caches.ratelimit.WithLock block (the code that reads/sets current for
clientIP and assigns used), always call actions.Update(clientIP, current, 0)
after incrementing current — even when current > rl.config.RateLimitCount —
before returning, so the cache reflects the actual attempted count; keep the
same TTL behavior used for the allowed path.

---

Nitpick comments:
In `@rate_limiter.go`:
- Around line 32-39: The background ticker goroutine in rate_limiter.go that
calls rl.caches.ratelimit.Sweep() needs a shutdown path; add a stop mechanism on
the RateLimiter (e.g., a stopCh chan struct{} field or accept a context) and
initialize it when creating the RateLimiter (e.g., in NewRateLimiter), change
the anonymous goroutine to select between ticker.C and the stop channel (or
ctx.Done()) so it breaks and defers ticker.Stop(), and expose a Stop() (or
Close(ctx)) method on RateLimiter that signals the channel (or cancels the
context) to terminate the goroutine cleanly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dbccdee6-4c76-4d7b-8eda-77e381700982

📥 Commits

Reviewing files that changed from the base of the PR and between f6a5db5 and f5ced3b.

📒 Files selected for processing (4)
  • cache.go
  • cache_store.go
  • main.go
  • rate_limiter.go
💤 Files with no reviewable changes (1)
  • cache.go

Comment thread rate_limiter.go
Comment on lines +52 to +66
var used int
rl.caches.ratelimit.WithLock(func(actions CacheStoreActions[int]) {
current, exists := actions.Get(clientIP)
if !exists {
actions.Set(clientIP, 1, 12*time.Hour)
used = 1
return
}
current++
used = current
if current > rl.config.RateLimitCount {
return
}
actions.Update(clientIP, current, 0)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Counter not updated when rate limit exceeded, causing inaccurate x-ratelimit-used header.

When current > RateLimitCount (line 62-64), the function returns without calling Update, so the cache remains at RateLimitCount. All subsequent blocked requests will show used = RateLimitCount + 1 instead of the actual attempt count.

Example with RateLimitCount=3:

  • Requests 1-3: cache updated to 1, 2, 3 (allowed)
  • Request 4: cache=3, increments to 4, blocked, no update → cache stays at 3
  • Request 5+: cache still 3, increments to 4, blocked → used header always shows 4

If accurate usage tracking matters, update the counter even when blocked:

🔧 Suggested fix
 		current++
 		used = current
+		actions.Update(clientIP, current, 0)
 		if current > rl.config.RateLimitCount {
 			return
 		}
-		actions.Update(clientIP, current, 0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var used int
rl.caches.ratelimit.WithLock(func(actions CacheStoreActions[int]) {
current, exists := actions.Get(clientIP)
if !exists {
actions.Set(clientIP, 1, 12*time.Hour)
used = 1
return
}
current++
used = current
if current > rl.config.RateLimitCount {
return
}
actions.Update(clientIP, current, 0)
})
var used int
rl.caches.ratelimit.WithLock(func(actions CacheStoreActions[int]) {
current, exists := actions.Get(clientIP)
if !exists {
actions.Set(clientIP, 1, 12*time.Hour)
used = 1
return
}
current++
used = current
actions.Update(clientIP, current, 0)
if current > rl.config.RateLimitCount {
return
}
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rate_limiter.go` around lines 52 - 66, The rate counter is not persisted when
a request pushes current past rl.config.RateLimitCount so x-ratelimit-used
becomes stale; inside the rl.caches.ratelimit.WithLock block (the code that
reads/sets current for clientIP and assigns used), always call
actions.Update(clientIP, current, 0) after incrementing current — even when
current > rl.config.RateLimitCount — before returning, so the cache reflects the
actual attempted count; keep the same TTL behavior used for the allowed path.

Comment thread rate_limiter.go
Comment on lines +96 to 103
xForwardedFor := r.Header.Get("x-forwarded-for")

if len(xForwardedFor) > 0 {
return xForwardedFor[0]
if xForwardedFor != "" {
firstIp := strings.SplitN(xForwardedFor, ",", 2)[0]
if firstIp != "" {
return firstIp
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Trim whitespace from parsed x-forwarded-for IP.

The X-Forwarded-For header value may contain spaces around commas (e.g., "1.2.3.4, 5.6.7.8"). The SplitN result could have trailing whitespace, which would create inconsistent cache keys for the same client IP.

🔧 Suggested fix
 		if xForwardedFor != "" {
 			firstIp := strings.SplitN(xForwardedFor, ",", 2)[0]
+			firstIp = strings.TrimSpace(firstIp)
 			if firstIp != "" {
 				return firstIp
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
xForwardedFor := r.Header.Get("x-forwarded-for")
if len(xForwardedFor) > 0 {
return xForwardedFor[0]
if xForwardedFor != "" {
firstIp := strings.SplitN(xForwardedFor, ",", 2)[0]
if firstIp != "" {
return firstIp
}
}
xForwardedFor := r.Header.Get("x-forwarded-for")
if xForwardedFor != "" {
firstIp := strings.SplitN(xForwardedFor, ",", 2)[0]
firstIp = strings.TrimSpace(firstIp)
if firstIp != "" {
return firstIp
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rate_limiter.go` around lines 96 - 103, The parsed X-Forwarded-For value can
include spaces, so trim whitespace from the extracted IP to avoid inconsistent
keys: after computing firstIp := strings.SplitN(xForwardedFor, ",", 2)[0] call
firstIp = strings.TrimSpace(firstIp) (or equivalent) before checking if firstIp
!= "" and returning it; update references to xForwardedFor/firstIp in that block
accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant