package proxy import ( "context" "time" "apps/backend/internal/svc" "github.com/zeromicro/go-zero/core/logx" ) // proxyCharge tracks one reserved quota unit for a proxied upstream call. // // The key mode is resolved once by the caller and reused for both the reservation and the audit // event, because resolving separately reads member settings twice and a member editing their // keys in between would reserve against one mode and bill the other. type proxyCharge struct { svcCtx *svc.ServiceContext uid int64 meter string mode string label string source string settled bool } // reserveQuota holds quota for an upcoming call. Pair it with `defer charge.Settle(ctx, &err)` // on a named error return so that failures refund instead of charging for nothing. func reserveQuota(ctx context.Context, svcCtx *svc.ServiceContext, uid int64, meter, mode, label, source string) (*proxyCharge, error) { if err := svcCtx.Usage.PrepareCallWithMode(ctx, uid, meter, mode); err != nil { return nil, err } return &proxyCharge{ svcCtx: svcCtx, uid: uid, meter: meter, mode: mode, label: label, source: source, }, nil } // Settle commits the charge on success and refunds it on failure. func (c *proxyCharge) Settle(ctx context.Context, errp *error) { if errp != nil && *errp != nil { c.release(ctx) return } c.commit(ctx) } // commit writes the audit event. The upstream call already succeeded, so a failure here is // logged rather than returned: the member has their result and charging them is correct, but // losing the event must not be silent either. func (c *proxyCharge) commit(ctx context.Context) { if c == nil || c.settled { return } c.settled = true if _, err := c.svcCtx.Usage.RecordCall(ctx, c.uid, c.meter, c.mode, c.label, c.source); err != nil { logx.Errorf("usage record uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err) } } func (c *proxyCharge) release(ctx context.Context) { if c == nil || c.settled { return } c.settled = true // The request context is often already cancelled on the path being compensated for. rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second) defer cancel() if err := c.svcCtx.Usage.ReleaseCall(rctx, c.uid, c.meter, c.mode); err != nil { logx.Errorf("usage release uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err) } }