diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index e52af70615..74956299aa 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.110.28 +0.1.110.29 diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 32c96de22d..a4571db28d 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -7529,14 +7529,21 @@ func finalizePostUsageBilling(p *postUsageBillingParams, deps *billingDeps, resu deps.deferredService.ScheduleLastUsedUpdate(p.Account.ID) - notifyBalanceLow(p, deps, result) - notifyAccountQuota(p, deps, result) + // Notification checks run async — all parameters are already captured, + // no dependency on the request context or upstream connection. + go notifyBalanceLow(p, deps, result) + go notifyAccountQuota(p, deps, result) } // notifyBalanceLow sends balance low notification after deduction. // When result.NewBalance is available (from DB transaction RETURNING), it is used directly // to reconstruct oldBalance, avoiding stale Redis reads and concurrent-deduction races. func notifyBalanceLow(p *postUsageBillingParams, deps *billingDeps, result *UsageBillingApplyResult) { + defer func() { + if r := recover(); r != nil { + slog.Error("panic in notifyBalanceLow", "recover", r) + } + }() if p.IsSubscriptionBill || p.Cost.ActualCost <= 0 || p.User == nil || deps.balanceNotifyService == nil { slog.Debug("notifyBalanceLow: skipped", "is_subscription", p.IsSubscriptionBill, @@ -7573,6 +7580,11 @@ func resolveOldBalance(p *postUsageBillingParams, result *UsageBillingApplyResul // When result.QuotaState is available (from DB transaction RETURNING), it is passed directly // to avoid a separate DB read that may see stale or concurrently-modified data. func notifyAccountQuota(p *postUsageBillingParams, deps *billingDeps, result *UsageBillingApplyResult) { + defer func() { + if r := recover(); r != nil { + slog.Error("panic in notifyAccountQuota", "recover", r) + } + }() if p.Cost.TotalCost <= 0 || p.Account == nil || !p.Account.IsAPIKeyOrBedrock() || deps.balanceNotifyService == nil { slog.Debug("notifyAccountQuota: skipped", "total_cost", p.Cost.TotalCost,