Take a container left set aside as the next apply's, and bound an apply to 30 minutes (issue 1945)
mesh/delivery merged: source.main moved to novox/mesh-host@3e3e43de5214
mesh/check pass 175fb45c19e3 on 3e3e43de5214 | unit 299 run, 1042 kept, 5 skipped | integration 302 run, 1043 kept, 4 skipped | over bound: unit (slow…

This commit is contained in:
2026-10-25 12:18:50 +01:00
parent 219169dbd9
commit 175fb45c19
5 changed files with 99 additions and 8 deletions
+17 -1
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"testing"
"time"
)
// An apply runs in a context the bus's loss cannot end: the link's context ends when the bus goes, and the bus may go
@@ -12,7 +13,8 @@ import (
func TestAnApplyOutlivesTheLinksContext(t *testing.T) {
type key struct{}
link, cancel := context.WithCancel(context.WithValue(context.Background(), key{}, "v"))
ctx := applyContext(link)
ctx, done := applyContext(link)
defer done()
cancel()
if ctx.Err() != nil {
t.Fatalf("the apply's context ended with the link's: %v", ctx.Err())
@@ -21,3 +23,17 @@ func TestAnApplyOutlivesTheLinksContext(t *testing.T) {
t.Fatal("the apply's context lost the link's values")
}
}
// An apply is bounded all the same: a step that hangs (a docker call that never returns) cannot hold the node agent for
// ever (the review of issue 1945).
func TestAnApplyIsBounded(t *testing.T) {
ctx, done := applyContext(context.Background())
defer done()
at, ok := ctx.Deadline()
if !ok {
t.Fatal("an apply has no bound")
}
if left := time.Until(at); left > applyBound || left < applyBound-time.Minute {
t.Fatalf("the bound is %s away, want about %s", left, applyBound)
}
}
+17 -7
View File
@@ -298,13 +298,17 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, sess
Backup: backupNode(session),
Say: say,
Apply: func(ctx context.Context, c delivery.Composed, others []delivery.Composed) agent.Result {
res := applyComposition(applyContext(session.WithLink(ctx)), opts, c, others, sched, say)
actx, done := applyContext(session.WithLink(ctx))
res := applyComposition(actx, opts, c, others, sched, say)
done()
afterApply()
sayFacts(ctx, ag)
return res
},
Remove: func(ctx context.Context, module string, others []delivery.Composed) agent.Result {
res := removeComposition(applyContext(session.WithLink(ctx)), opts, module, others, sched, say)
actx, done := applyContext(session.WithLink(ctx))
res := removeComposition(actx, opts, module, others, sched, say)
done()
afterApply()
sayFacts(ctx, ag)
return res
@@ -568,8 +572,14 @@ func declaresNothingToBackUp(said json.RawMessage, module string) (nothing bool,
return true, true
}
// applyContext is the context an apply or a removal runs in: the link's values, never its end. The link's context ends
// when the bus goes, and the bus may go because of the apply itself (it replaces the bus's own container), which must
// then finish: a container removed and never made again took the mesh down (issue 1945, live on 2026-10-25 at 11:23).
// An apply is bounded by its own steps' bounds, not by the link.
func applyContext(ctx context.Context) context.Context { return context.WithoutCancel(ctx) }
// applyBound is how long one apply or removal may take: long enough for the slowest image pull and backup step, short
// enough that a step that hangs cannot hold the node agent for ever.
const applyBound = 30 * time.Minute
// applyContext is the context an apply or a removal runs in: the link's values, never its end, and a bound of its own.
// The link's context ends when the bus goes, and the bus may go because of the apply itself (it replaces the bus's own
// container), which must then finish: a container removed and never made again took the mesh down (issue 1945, live on
// 2026-10-25 at 11:23). applyBound keeps a hung step from blocking the agent for ever.
func applyContext(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(context.WithoutCancel(ctx), applyBound)
}