fix: serialize review list with trigger
This commit is contained in:
parent
bc5bce5459
commit
c88fdaa9ad
|
|
@ -366,6 +366,9 @@ func (e *Engine) List(ctx stdctx.Context, workerID domain.SessionID) (SessionRev
|
||||||
if workerID == "" {
|
if workerID == "" {
|
||||||
return SessionReviews{}, fmt.Errorf("%w: worker session id is required", ErrInvalid)
|
return SessionReviews{}, fmt.Errorf("%w: worker session id is required", ErrInvalid)
|
||||||
}
|
}
|
||||||
|
unlock := e.lockWorker(workerID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
runs, err := e.store.ListReviewRunsBySession(ctx, workerID)
|
runs, err := e.store.ListReviewRunsBySession(ctx, workerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SessionReviews{}, err
|
return SessionReviews{}, err
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,22 @@ func (f *fakeLauncher) Stop(_ context.Context, handleID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type blockingSpawnLauncher struct {
|
||||||
|
fakeLauncher
|
||||||
|
entered chan struct{}
|
||||||
|
release chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *blockingSpawnLauncher) Spawn(_ context.Context, spec LaunchSpec) (string, error) {
|
||||||
|
f.gotSpec = spec
|
||||||
|
f.specs = append(f.specs, spec)
|
||||||
|
close(f.entered)
|
||||||
|
<-f.release
|
||||||
|
f.spawned = true
|
||||||
|
f.spawnCount++
|
||||||
|
return f.handle, nil
|
||||||
|
}
|
||||||
|
|
||||||
func liveWorker() domain.SessionRecord {
|
func liveWorker() domain.SessionRecord {
|
||||||
return domain.SessionRecord{
|
return domain.SessionRecord{
|
||||||
ID: "mer-1",
|
ID: "mer-1",
|
||||||
|
|
@ -268,6 +284,61 @@ func TestTriggerConcurrentSameWorkerSpawnsOnce(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListWaitsForTriggerBeforeFailingDeadReviewer(t *testing.T) {
|
||||||
|
store := &fakeStore{
|
||||||
|
review: &domain.Review{ID: "rev-1", SessionID: "mer-1", ReviewerHandleID: "review-mer-old"},
|
||||||
|
}
|
||||||
|
launcher := &blockingSpawnLauncher{
|
||||||
|
fakeLauncher: fakeLauncher{handle: "review-mer-1", alive: false},
|
||||||
|
entered: make(chan struct{}),
|
||||||
|
release: make(chan struct{}),
|
||||||
|
}
|
||||||
|
eng := newEngineForTest(store, fakeSessions{rec: liveWorker(), ok: true}, prAt("sha1"), fakeProjects{}, launcher)
|
||||||
|
|
||||||
|
triggerDone := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, err := eng.Trigger(context.Background(), "mer-1")
|
||||||
|
triggerDone <- err
|
||||||
|
}()
|
||||||
|
<-launcher.entered
|
||||||
|
|
||||||
|
listDone := make(chan struct {
|
||||||
|
reviews SessionReviews
|
||||||
|
err error
|
||||||
|
}, 1)
|
||||||
|
go func() {
|
||||||
|
got, err := eng.List(context.Background(), "mer-1")
|
||||||
|
listDone <- struct {
|
||||||
|
reviews SessionReviews
|
||||||
|
err error
|
||||||
|
}{reviews: got, err: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case got := <-listDone:
|
||||||
|
t.Fatalf("List returned while Trigger was still spawning: got=%+v err=%v", got.reviews, got.err)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
close(launcher.release)
|
||||||
|
if err := <-triggerDone; err != nil {
|
||||||
|
t.Fatalf("Trigger: %v", err)
|
||||||
|
}
|
||||||
|
got := <-listDone
|
||||||
|
if got.err != nil {
|
||||||
|
t.Fatalf("List: %v", got.err)
|
||||||
|
}
|
||||||
|
if got.reviews.ReviewerHandleID != "review-mer-1" {
|
||||||
|
t.Fatalf("reviewer handle = %q, want fresh handle", got.reviews.ReviewerHandleID)
|
||||||
|
}
|
||||||
|
if len(got.reviews.Runs) != 1 || got.reviews.Runs[0].Status != domain.ReviewRunRunning {
|
||||||
|
t.Fatalf("runs = %+v, want still-running fresh run", got.reviews.Runs)
|
||||||
|
}
|
||||||
|
if store.runs[0].Status != domain.ReviewRunRunning {
|
||||||
|
t.Fatalf("stored run was failed during trigger/list race: %+v", store.runs[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTriggerFallsBackToExistingRunOnUniqueConflict(t *testing.T) {
|
func TestTriggerFallsBackToExistingRunOnUniqueConflict(t *testing.T) {
|
||||||
// The idempotency check passes (no run yet), but the insert loses to a
|
// The idempotency check passes (no run yet), but the insert loses to a
|
||||||
// concurrent writer the unique index already accepted.
|
// concurrent writer the unique index already accepted.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue