diff --git a/modules/mesh-delivery-builder/internal/build/buildagain.go b/modules/mesh-delivery-builder/internal/build/buildagain.go index 16e9a1c..8aa6287 100644 --- a/modules/mesh-delivery-builder/internal/build/buildagain.go +++ b/modules/mesh-delivery-builder/internal/build/buildagain.go @@ -159,8 +159,14 @@ func (b *Builder) BuildAgain(ctx context.Context, module, fp, why, by string, bo case ended && len(res.Asks) < max(lanes, 0)+boolInt(lanes < 0): // The look ended before every ask was put: say how the key stands, not that it was asked. if v, _, found, err := b.Claimer.Store.Get(ctx, key); err == nil && found { - if cur, err := delivery.DecodeBuilderBuild(v); err == nil && cur.State == delivery.BuildFailed { - res.Failed = cur.Error + if cur, err := delivery.DecodeBuilderBuild(v); err == nil { + switch cur.State { + case delivery.BuildFailed: + res.Failed = cur.Error + case delivery.BuildDone: + default: + res.Pending = fmt.Sprintf("the look ended with the key %s (%s) before every ask was put: its log says why", cur.State, cur.Step) + } } } case !shut: diff --git a/modules/mesh-delivery-builder/internal/build/run.go b/modules/mesh-delivery-builder/internal/build/run.go index 0bd1051..935860b 100644 --- a/modules/mesh-delivery-builder/internal/build/run.go +++ b/modules/mesh-delivery-builder/internal/build/run.go @@ -375,21 +375,27 @@ func (b *Builder) consider(ctx context.Context, module string, m delivery.Module }() } -// startOnce looks at the module once, for a key the caller has set inflight already (BuildAgain): it never falls into -// again, so the key is not built a second time, and clears inflight when the look ends. +// startOnce looks at the module for a key the caller has set inflight already (BuildAgain): the by-hand look runs once, and +// a look the builder queued for the key meanwhile (again) still runs after it, as consider's loop does. func (b *Builder) startOnce(ctx context.Context, key, module string, m delivery.Module) { b.wg.Add(1) go func() { defer b.wg.Done() - defer func() { - b.mu.Lock() - delete(b.again, key) - delete(b.inflight, key) - b.mu.Unlock() - }() b.sem <- struct{}{} defer func() { <-b.sem }() - b.try(ctx, module, m) + for { + b.try(ctx, module, m) + b.mu.Lock() + next, more := b.again[key] + delete(b.again, key) + if !more || ctx.Err() != nil { + delete(b.inflight, key) + b.mu.Unlock() + return + } + b.mu.Unlock() + m = next // a look the builder queued meanwhile is not lost (consider's rule); it is not a second by-hand build + } }() } diff --git a/modules/mesh-delivery-builder/internal/build/run_test.go b/modules/mesh-delivery-builder/internal/build/run_test.go index 884f892..481d925 100644 --- a/modules/mesh-delivery-builder/internal/build/run_test.go +++ b/modules/mesh-delivery-builder/internal/build/run_test.go @@ -706,7 +706,11 @@ func TestABuildAskWaitingInItsStreamIsNotBounded(t *testing.T) { edit func(m *delivery.Module) busy int failed string + // during is a look of the builder's own at the module while the by-hand build runs: it still runs afterwards. + during bool }{ + {name: "build again: a look the builder queued during the by-hand build still runs afterwards", again: true, during: true, + key: &delivery.BuilderBuild{State: delivery.BuildFailed, Error: "ace: no tun"}}, {name: "build again: a key looked at by the builder now is refused, not built twice", again: true, refuse: "is being looked at", busy: 1, key: &delivery.BuilderBuild{State: delivery.BuildFailed, Error: "ace: no tun"}}, {name: "build again: a key already asked by hand is refused, its watch kept", again: true, refuse: "is being looked at", busy: 2, @@ -757,6 +761,11 @@ func TestABuildAskWaitingInItsStreamIsNotBounded(t *testing.T) { c.edit(&m) } r.b.init() + var stored atomic.Int64 + if c.during { + r.asks.Answer = nil + r.b.Stored = func(context.Context, []string) ([]string, error) { stored.Add(1); return nil, nil } + } switch c.busy { case 1: r.b.inflight[r.key] = true @@ -767,6 +776,16 @@ func TestABuildAskWaitingInItsStreamIsNotBounded(t *testing.T) { log := &logged{} r.b.Logf = log.logf res := r.b.BuildAgain(context.Background(), "m", fp1, "no tun on ace", "shanks.lead", 2*time.Second) + if c.during { + // The trunk's modules value arrives while the by-hand build waits for its ask: it is queued, and run after it. + r.b.Module(context.Background(), "m", moduleValue(t, "modules/m")) + r.asks.End(r.answer(waitForAsks(t, r, 1)[0])) + r.b.Wait() + if v := valueOf(t, r.st, r.key); v.State != delivery.BuildDone || res.Revision <= rev || stored.Load() != 1 { + t.Fatalf("key %+v, answer %+v, %d asks of the store; want built from the new revision and the queued look run once after it", v, res, stored.Load()) + } + return + } r.b.Wait() after := r.st.revs[r.key] if c.refuse != "" {