From 8cf23ae65da37527402d1c021885a27f26c39e7c Mon Sep 17 00:00:00 2001 From: mesutoezdil Date: Tue, 21 Jul 2026 11:07:06 +0200 Subject: [PATCH] fix(ratelimiter): return error when olric setup fails NewRateLimiter logged the olric.New error but continued with a nil db, so the following db.Start call panicked in a goroutine and crashed the process. Return the wrapped error instead of logging it, per the log-or-return rule, and stop the two ttlcache expiration goroutines on every constructor error path so failed setup does not leak them. Also replace three em dashes in nearby comments to satisfy the ASCII-only style rule. Closes #291 Signed-off-by: mesutoezdil --- .../ratelimiter/rate_limiter.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/invocation-plane-services/ratelimiter/rate_limiter.go b/src/invocation-plane-services/ratelimiter/rate_limiter.go index bb932cd96..304788bdb 100644 --- a/src/invocation-plane-services/ratelimiter/rate_limiter.go +++ b/src/invocation-plane-services/ratelimiter/rate_limiter.go @@ -292,6 +292,11 @@ func NewRateLimiter(config Config, olricConfig *olricConfig.Config) (*RateLimite // Wrap the NVCF client with caching to reduce redundant gRPC calls cachedNVCFClient := NewCachedNVCFClient(nvcfApiClient, cacheTTL) + stopCaches := func() { + cache.Stop() + indexedPoliciesCache.Stop() + } + c := make(chan struct{}) olricConfig.Started = func() { close(c) @@ -300,7 +305,8 @@ func NewRateLimiter(config Config, olricConfig *olricConfig.Config) (*RateLimite // Create a new Olric instance. db, err := olric.New(olricConfig) if err != nil { - zap.L().Error("Failed to create Olric instance", zap.Error(err)) + stopCaches() + return nil, fmt.Errorf("failed to create olric instance: %w", err) } errChannel := make(chan error, 1) @@ -318,12 +324,17 @@ func NewRateLimiter(config Config, olricConfig *olricConfig.Config) (*RateLimite case <-c: break case err := <-errChannel: + stopCaches() return nil, err } store, err := NewStore(db.NewEmbeddedClient()) if err != nil { zap.L().Error("Failed to create a Olric store", zap.Error(err)) + stopCaches() + if shutdownErr := db.Shutdown(context.Background()); shutdownErr != nil { + zap.L().Error("Failed to shut down olric instance", zap.Error(shutdownErr)) + } return nil, err } @@ -583,7 +594,7 @@ func (r *RateLimiter) loadPerNcaIdLimiters(ctx context.Context, cacheKey CacheKe // loadPerUserLimiters creates a per-user rate limiter for the requested // (clientAuthSubject, ncaId, functionVersionId). When perUserRate is configured // on the policy, the same single rate is enforced individually against every -// unique caller — counters are kept distinct via the clientAuthSubject in the +// unique caller; counters are kept distinct via the clientAuthSubject in the // cache key and Olric prefix. Returns nil when the policy has no perUserRate // configured, so downstream code falls through to NCA-tier-only behavior. func (r *RateLimiter) loadPerUserLimiters(ctx context.Context, cacheKey CacheKey, originalRequest *pb.RateLimitRequest) (*ttlcache.Item[CacheKey, LimiterEntry], error) { @@ -738,7 +749,7 @@ func (r *RateLimiter) RateLimit(ctx context.Context, request *pb.RateLimitReques func (r *RateLimiter) collectTiers(ctx context.Context, request *pb.RateLimitRequest) []tierCheck { tiers := make([]tierCheck, 0, 2) - // User tier — only when caller identity is present. + // User tier; only when caller identity is present. if request.ClientAuthSubject != "" { userKey := NewPerUserCacheKey(request.ClientAuthSubject, request.NcaId, request.FunctionVersionId) userLimiters := r.Limiters.Get(userKey, ttlcache.WithLoader(ttlcache.LoaderFunc[CacheKey, LimiterEntry](func(c *ttlcache.Cache[CacheKey, LimiterEntry], k CacheKey) *ttlcache.Item[CacheKey, LimiterEntry] { @@ -758,7 +769,7 @@ func (r *RateLimiter) collectTiers(ctx context.Context, request *pb.RateLimitReq } } - // NCA tier — per-NCA-ID config takes precedence over global. + // NCA tier; per-NCA-ID config takes precedence over global. perNcaIdKey := NewPerNcaIdCacheKey(request.NcaId, request.FunctionVersionId) perNcaIdLimiters := r.Limiters.Get(perNcaIdKey, ttlcache.WithLoader(ttlcache.LoaderFunc[CacheKey, LimiterEntry](func(c *ttlcache.Cache[CacheKey, LimiterEntry], k CacheKey) *ttlcache.Item[CacheKey, LimiterEntry] { item, err := r.loadPerNcaIdLimiters(ctx, k, request)