package hostbox import ( "context" "errors" "sync" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/park" ) type Renewable interface { Err() error Lost() <-chan struct{} Release(context.Context) error } type RenewingLeases struct { Region string Open func(context.Context, string, string, string) (Renewable, error) } func (l RenewingLeases) Acquire(ctx context.Context, bucket, scope string) (park.HeldLease, error) { if ctx == nil || l.Region == "" || bucket == "" || scope == "" { return nil, ErrUnsupported } open := l.Open if open == nil { open = func(ctx context.Context, b, p, r string) (Renewable, error) { return lease.Acquire(ctx, b, p, r) } } bounded, cancel := context.WithTimeout(ctx, RequestLimit) h, err := open(bounded, bucket, scope, l.Region) cancel() if err != nil { return nil, err } if h == nil { return nil, ErrUnsupported } cctx, stop := context.WithCancel(ctx) held := &renewingHeld{bucket: bucket, scope: scope, h: h, ctx: cctx, cancel: stop, done: make(chan struct{})} go func() { defer close(held.done) select { case <-h.Lost(): stop() case <-cctx.Done(): } }() if err := held.Check(ctx); err != nil { held.Close() return nil, err } return held, nil } type renewingHeld struct { bucket, scope string h Renewable ctx context.Context cancel context.CancelFunc done chan struct{} once sync.Once releaseErr error } func (h *renewingHeld) Check(ctx context.Context) error { if err := ctx.Err(); err != nil { return err } if err := h.ctx.Err(); err != nil { return err } return h.h.Err() } func (h *renewingHeld) Context(ctx context.Context) context.Context { c, cancel := context.WithCancel(ctx) stop := context.AfterFunc(h.ctx, cancel) context.AfterFunc(c, func() { stop() }) return c } func (h *renewingHeld) Release(ctx context.Context) error { h.once.Do(func() { h.cancel() <-h.done bounded, cancel := context.WithTimeout(context.WithoutCancel(ctx), RequestLimit) defer cancel() h.releaseErr = h.h.Release(bounded) }) return h.releaseErr } func (h *renewingHeld) Close() { _ = h.Release(context.Background()) } // Context carries every constituent's cancellation into the engine's requests. func (h *aggregateHeld) Context(ctx context.Context) context.Context { for _, l := range h.leases { if bound, ok := l.(interface { Context(context.Context) context.Context }); ok { ctx = bound.Context(ctx) } } return ctx } // LeasedCheck is useful before requests that are issued by a wake's producer. func LeasedCheck(ctx context.Context, health func() error, authority func(context.Context) error) error { if health == nil || authority == nil { return ErrUnsupported } return errors.Join(ctx.Err(), health(), authority(ctx)) } func (h *renewingHeld) LeaseScope() (string, string) { return h.bucket, h.scope }