From c2ec8b6b2fdece8dd76a9d2d1f29d3ae4579f174 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 11:09:35 +0100 Subject: [PATCH] Say nothing more on a step's log after one publish fails, so a bus that does not take its lines costs a step at most one timeout and never fails it --- go/buildstep/step_test.go | 12 ++++++++++++ go/buildstep/steplog.go | 12 ++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/go/buildstep/step_test.go b/go/buildstep/step_test.go index c0b4588..153836e 100644 --- a/go/buildstep/step_test.go +++ b/go/buildstep/step_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "sync" "testing" "time" @@ -90,6 +91,17 @@ func TestStepEndsPassedFailedOrRefusedOnItsKey(t *testing.T) { {"no whole output", func(context.Context) (any, error) { return map[string]any{"failed": []map[string]string{{"package": "p", "full_output": "all of it"}}}, nil }, delivery.StepPassed, "", `{"failed":[{"package":"p"}]}`}, + // A log the bus does not take is tried once in a step, then left: the step passes, its failures name no step. + {"a dead log", func(context.Context) (any, error) { + tries := 0 + l := &stepLog{say: func(string, any) error { tries++; return errors.New("timeout") }, id: "s1", run: "r1"} + raw := l.narrate(json.RawMessage(`{"failed":[{"package":"p","full_output":"a"},{"package":"q","full_output":"b"}]}`), nil) + raw = l.narrate(raw, nil) + if tries != 1 { + return nil, fmt.Errorf("%d publishes, want one", tries) + } + return raw, nil + }, delivery.StepPassed, "", `{"failed":[{"package":"p"},{"package":"q"}]}`}, {"error", func(context.Context) (any, error) { return nil, errors.New("exit 2") }, delivery.StepFailed, "exit 2", ""}, {"refusal", func(context.Context) (any, error) { return nil, &Refusal{Text: "go 1.27.1 installed, the recipe builds with go 1.27.0", Want: "1.27.0", Have: "1.27.1"} diff --git a/go/buildstep/steplog.go b/go/buildstep/steplog.go index 8daa0d3..bfc1726 100644 --- a/go/buildstep/steplog.go +++ b/go/buildstep/steplog.go @@ -31,6 +31,9 @@ type stepLog struct { module, node string id, run string seq int + // dead says a publish failed: the step says nothing more on its log, so a bus that does not take its lines costs the + // step at most one publish's timeout, and never fails it (the 2026-10-30 review). + dead bool } // stepLog is the log of the step whose ask runs under ctx, or nil when no ask runs (a tool called by hand) or the module @@ -49,8 +52,8 @@ func (s AskSteps) stepLog(ctx context.Context) *stepLog { } // narrate says every failure's Full of a step answer's "failed" on the step's log and empties it, setting the failure's -// Step, Module, Machine and Run; a failure whose lines could not all be said keeps no address, and that is said through -// logf. With l nil, Full is only emptied: the step's result never holds it. +// Step, Module, Machine and Run; a failure whose lines could not all be said keeps no address, that is said once through +// logf, and the step says nothing more on its log (dead). With l nil, Full is only emptied: the step's result never holds it. func (l *stepLog) narrate(raw json.RawMessage, logf func(string, ...any)) json.RawMessage { if !strings.Contains(string(raw), `"full_output"`) { return raw @@ -67,7 +70,7 @@ func (l *stepLog) narrate(raw json.RawMessage, logf func(string, ...any)) json.R var full string _ = json.Unmarshal(f["full_output"], &full) delete(failed[i], "full_output") - if full == "" || l == nil { + if full == "" || l == nil || l.dead { continue } var name struct { @@ -77,8 +80,9 @@ func (l *stepLog) narrate(raw json.RawMessage, logf func(string, ...any)) json.R b, _ := json.Marshal(f) _ = json.Unmarshal(b, &name) if err := l.say1(name.Package, name.Test, strings.Split(full, "\n")); err != nil { + l.dead = true if logf != nil { - logf("the whole output of %s %s in the step %s is not on its log: %v", name.Package, name.Test, l.id, err) + logf("the step %s says no more on its log, from the whole output of %s %s on: %v", l.id, name.Package, name.Test, err) } continue }