aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-02-24 14:48:50 +0530
committerBobby <[email protected]>2026-02-24 14:48:50 +0530
commit17b77153a862ad1eb3babe1e34e748363ac9916c (patch)
tree33871abe60da895112ebfffd5802f396005f7c79
parentefb4f68869a9e712c4016ef286d3f16b0bba3110 (diff)
downloadmetachan-17b77153a862ad1eb3babe1e34e748363ac9916c.tar.xz
metachan-17b77153a862ad1eb3babe1e34e748363ac9916c.zip
Refactor rate limiter: simplify implementation and improve token management
-rw-r--r--repositories/anime.go3
-rw-r--r--services/anime.go1
-rw-r--r--utils/api/jikan/jikan.go4
-rw-r--r--utils/ratelimit/limiter.go96
-rw-r--r--utils/ratelimit/types.go10
5 files changed, 37 insertions, 77 deletions
diff --git a/repositories/anime.go b/repositories/anime.go
index ac10189..9a17542 100644
--- a/repositories/anime.go
+++ b/repositories/anime.go
@@ -63,6 +63,9 @@ func GetAnime[T idType](maptype enums.MappingType, id T) (entities.Anime, error)
First(&anime)
if result.Error != nil {
+ if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+ return entities.Anime{}, result.Error
+ }
logger.Errorf("Anime", "Failed to get anime details: %v", result.Error)
return entities.Anime{}, errors.New("anime not found")
}
diff --git a/services/anime.go b/services/anime.go
index 6efd81e..de19aa5 100644
--- a/services/anime.go
+++ b/services/anime.go
@@ -86,7 +86,6 @@ func fetchAnime(mapping *entities.Mapping, existing *entities.Anime) (*entities.
logger.Warnf("AnimeService", "Failed to fetch characters from Jikan: %v", err)
}
- // Reset all slice fields so re-fetching doesn't double-append onto existing DB data
anime.Episodes = nil
anime.Characters = nil
anime.Genres = nil
diff --git a/utils/api/jikan/jikan.go b/utils/api/jikan/jikan.go
index 244253d..8812c5a 100644
--- a/utils/api/jikan/jikan.go
+++ b/utils/api/jikan/jikan.go
@@ -63,7 +63,7 @@ func (c *client) handleRetry(retries *int, url string, reason string, retryAfter
backoffDuration = retryAfter
}
- logger.Warnf("JikanClient", "%s for %s (attempt %d/%d)", reason, url, *retries, c.maxRetries)
+ logger.Debugf("JikanClient", "%s for %s (attempt %d/%d)", reason, url, *retries, c.maxRetries)
time.Sleep(backoffDuration)
return true
}
@@ -288,7 +288,7 @@ func GetAnimeProducers() (*types.JikanProducersResponse, error) {
response.Pagination = pageResponse.Pagination
}
- logger.Debugf("JikanClient", "Fetched page (%d/%d) - %d producers", page, response.Pagination.LastVisiblePage, len(pageResponse.Data))
+ logger.Infof("JikanClient", "Fetched page (%d/%d) - %d producers", page, response.Pagination.LastVisiblePage, len(pageResponse.Data))
response.Data = append(response.Data, pageResponse.Data...)
hasNextPage = pageResponse.Pagination.HasNextPage
diff --git a/utils/ratelimit/limiter.go b/utils/ratelimit/limiter.go
index 6450c8b..c99a935 100644
--- a/utils/ratelimit/limiter.go
+++ b/utils/ratelimit/limiter.go
@@ -5,80 +5,40 @@ import (
)
func NewRateLimiter(maxRequests int, window time.Duration) *RateLimiter {
- minDelay := window / time.Duration(maxRequests)
- return &RateLimiter{
- lastRequests: make([]time.Time, 0, maxRequests),
- maxRequests: maxRequests,
- window: window,
- minDelay: minDelay,
- }
+ interval := window / time.Duration(maxRequests)
+ rl := &RateLimiter{
+ tokens: make(chan struct{}, maxRequests),
+ done: make(chan struct{}),
+ }
+ rl.tokens <- struct{}{}
+ go func() {
+ ticker := time.NewTicker(interval)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ select {
+ case rl.tokens <- struct{}{}:
+ default:
+ }
+ case <-rl.done:
+ return
+ }
+ }
+ }()
+ return rl
}
func (r *RateLimiter) Wait() {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- now := time.Now()
-
- if !r.lastRequest.IsZero() {
- elapsed := now.Sub(r.lastRequest)
- if elapsed < r.minDelay {
- waitTime := r.minDelay - elapsed
- r.mu.Unlock()
- time.Sleep(waitTime)
- r.mu.Lock()
- now = time.Now()
- }
- }
-
- cutoff := now.Add(-r.window)
- i := 0
- for i < len(r.lastRequests) && r.lastRequests[i].Before(cutoff) {
- i++
- }
- if i > 0 {
- r.lastRequests = r.lastRequests[i:]
- }
-
- if len(r.lastRequests) >= r.maxRequests {
- oldestInWindow := r.lastRequests[0]
- waitDuration := r.window - now.Sub(oldestInWindow)
-
- r.mu.Unlock()
- time.Sleep(waitDuration + time.Millisecond)
- r.mu.Lock()
-
- now = time.Now()
- cutoff = now.Add(-r.window)
- i = 0
- for i < len(r.lastRequests) && r.lastRequests[i].Before(cutoff) {
- i++
- }
- if i > 0 {
- r.lastRequests = r.lastRequests[i:]
- }
- }
+ <-r.tokens
+}
- r.lastRequest = now
- r.lastRequests = append(r.lastRequests, now)
+func (r *RateLimiter) Stop() {
+ close(r.done)
}
func (r *RateLimiter) RemainingRequests() int {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- now := time.Now()
- cutoff := now.Add(-r.window)
-
- i := 0
- for i < len(r.lastRequests) && r.lastRequests[i].Before(cutoff) {
- i++
- }
- if i > 0 {
- r.lastRequests = r.lastRequests[i:]
- }
-
- return r.maxRequests - len(r.lastRequests)
+ return len(r.tokens)
}
func NewMultiLimiter(limiters ...*RateLimiter) *MultiLimiter {
@@ -88,6 +48,8 @@ func NewMultiLimiter(limiters ...*RateLimiter) *MultiLimiter {
}
func (m *MultiLimiter) Wait() {
+ m.mu.Lock()
+ defer m.mu.Unlock()
for _, limiter := range m.limiters {
limiter.Wait()
}
diff --git a/utils/ratelimit/types.go b/utils/ratelimit/types.go
index f962f22..17b7900 100644
--- a/utils/ratelimit/types.go
+++ b/utils/ratelimit/types.go
@@ -2,18 +2,14 @@ package ratelimit
import (
"sync"
- "time"
)
type RateLimiter struct {
- mu sync.Mutex
- lastRequest time.Time
- lastRequests []time.Time
- maxRequests int
- window time.Duration
- minDelay time.Duration
+ tokens chan struct{}
+ done chan struct{}
}
type MultiLimiter struct {
+ mu sync.Mutex
limiters []*RateLimiter
}