72 lines
2.3 KiB
Go
72 lines
2.3 KiB
Go
|
|
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)
|
||
|
|
}
|
||
|
|
}
|