| index 8bf9344ff..744a55211 100644 |
| --- a/internal/console/selfserve_job.go |
| +++ b/internal/console/selfserve_job.go |
| @@ -230,6 +230,12 @@ func (s *Server) selfServeStep(ctx context.Context, t *leaseToken, row *SelfServ |
| if err != nil && !gone { |
| return err |
| } |
| + case errors.Is(rerr, errReplayNeeded) && row.Request.Phase != row.Phase: |
| + // A request whose issuing phase the row has left: no phase |
| + // function replays it, so it is resolved as an abandoned |
| + // write is, by the fence (its receipt confirms it; absent, it |
| + // is voided) and the row decides again next tick. |
| + return s.selfServeResolveFenced(ctx, t, row) |
| case errors.Is(rerr, errReplayNeeded): |
| // The write has not landed and the record is unchanged: the |
| // phase function replays it (below), unless the reserving |
| @@ -455,6 +461,23 @@ func (s *Server) selfServeFence(ctx context.Context, t *leaseToken, row *SelfSer |
| return obs.Rec, nil |
| } |
| |
| +// selfServeResolveFenced resolves the row's unconfirmed request without |
| +// replaying it: the record fenced (so the write can never land later), |
| +// the fenced observation's receipt confirming it, and otherwise a void. |
| +func (s *Server) selfServeResolveFenced(ctx context.Context, t *leaseToken, row *SelfServeMove) error { |
| + TestHooks.point(row.Request.Kind + ":fence") |
| + frec, err := s.selfServeFence(ctx, t, row) |
| + if err != nil { |
| + log.Printf("console: self-serve move %s: fencing the %s request: %v (pending)", row.ID, row.Request.Kind, err) |
| + return nil |
| + } |
| + if frec != nil && frec.Incarnation == row.Incarnation && selfServeHasReceipt(frec, row.Request.Nonce) { |
| + return s.selfServeConfirmFenced(ctx, row, frec) |
| + } |
| + TestHooks.point(row.Request.Kind + ":void") |
| + return s.store.VoidSelfServeRequest(row.ID, row.Request.Nonce) |
| +} |
| + |
| // selfServeConfirmFenced confirms the row's request from a fenced |
| // observation that carries its receipt, and announces it. |
| func (s *Server) selfServeConfirmFenced(ctx context.Context, row *SelfServeMove, rec *provision.Record) error { |
| @@ -501,7 +524,8 @@ func (s *Server) selfServeExpired(ctx context.Context, t *leaseToken, row *SelfS |
| return s.selfServeConfirmFenced(ctx, row, frec) |
| } |
| TestHooks.point(pr.Kind + ":void-expired") |
| - if _, err := s.store.VoidExpiredSelfServeRequest(row.ID, pr.Nonce, s.selfServeNow(), msgSelfServeCouldNotStart); err != nil { |
| + reason := s.selfServeExpiryReason(ctx, row, frec, s.selfServeNow()) |
| + if _, err := s.store.VoidExpiredSelfServeRequest(row.ID, pr.Nonce, s.selfServeNow(), reason); err != nil { |
| return err |
| } |
| return nil |
| @@ -587,20 +611,8 @@ func (s *Server) selfServeAbortStep(ctx context.Context, t *leaseToken, row *Sel |
| out, conf := selfServeOutstanding(row) |
| switch { |
| case out && !conf: |
| - TestHooks.point(row.Request.Kind + ":fence") |
| - frec, err := s.selfServeFence(ctx, t, row) |
| - if err != nil { |
| - log.Printf("console: self-serve move %s: fencing the %s request before the abort: %v (pending)", row.ID, row.Request.Kind, err) |
| - return true, nil |
| - } |
| - if frec != nil && frec.Incarnation == row.Incarnation && selfServeHasReceipt(frec, row.Request.Nonce) { |
| - return true, s.selfServeConfirmFenced(ctx, row, frec) |
| - } |
| - TestHooks.point(row.Request.Kind + ":void") |
| - if err := s.store.VoidSelfServeRequest(row.ID, row.Request.Nonce); err != nil { |
| - return true, err |
| - } |
| - return true, nil // the next tick decides from the record |
| + // The next tick decides from the record. |
| + return true, s.selfServeResolveFenced(ctx, t, row) |
| case out && conf && row.Request.Kind != "abort" && row.Request.Kind != "finish": |
| TestHooks.point(row.Request.Kind + ":abandon") |
| if _, err := s.store.AbandonSelfServeRequest(row.ID, row.Request.Nonce, row.StepDeadline); err != nil { |