package schedule import ( "context" "errors" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/registry" "net/http" "sync/atomic" "testing" "time" ) type cancelDelivery struct { started chan struct{} legacy atomic.Bool } func (d *cancelDelivery) Deliver(context.Context, registry.Route, *http.Request) (int, error) { d.legacy.Store(true) return 200, nil } func (d *cancelDelivery) DeliverClaim(ctx context.Context, rt registry.Route, req *http.Request, admit func(context.Context) (context.Context, func(), error)) (int, error) { child, finish, err := admit(ctx) if err != nil { return 0, err } defer finish() close(d.started) <-child.Done() return 0, child.Err() } func TestClaimTimerDeliveryDrainsBeforeBookkeeping(t *testing.T) { reg, r := runnerFixture(t, &fakeDeliverer{status: 204}) now := time.Unix(20000, 0) r.Now = func() time.Time { return now } valid := &atomic.Bool{} valid.Store(true) r.HostWrites = hostwrite.Config{Enabled: true, Selector: lifecycleSelector{valid}} d := &cancelDelivery{started: make(chan struct{})} r.Deliver = d finished := make(chan struct{}) var stop context.CancelFunc r.StartDelivery = func(ctx context.Context, w hostwrite.Work, _ registry.Route, _ registry.Timer) (context.Context, func(), error) { var child context.Context err := w.With(ctx, hostwrite.Identity{}, func() error { child, stop = context.WithCancel(ctx); return nil }) return child, func() { close(finished) }, err } host := "a.bab.oncarlos.com" if err := reg.UpsertTimer(registry.Timer{Host: host, Name: "tick", Kind: KindSchedule, Spec: "every 1h0m0s", Path: "/tick", NextAt: now.Unix()}); err != nil { t.Fatal(err) } r.Tick(t.Context()) <-d.started valid.Store(false) stop() r.wait() select { case <-finished: default: t.Fatal("delivery not joined") } row, err := reg.GetTimer(host, "tick") if err != nil || row.LastAt != 0 || row.NextAt != now.Unix() || d.legacy.Load() { t.Fatalf("late write or legacy delivery: %+v %v", row, err) } r.StartDelivery = nil valid.Store(true) ctx := context.WithValue(t.Context(), timerWorkKey{}, timerWork{t.Context(), lifecycleWork{valid}}) if _, err := r.deliverClaim(ctx, registry.Route{Host: host}, *row, &http.Request{}); !errors.Is(err, hostwrite.ErrUnavailable) { t.Fatal(err) } }