node-tools: keep the stopping-runtime test under a second, and start no first bundle once stopping
mesh/delivery merged: source.main moved to novox/mesh-tools@27266f99a3c3
mesh/delivery-group group fix/shutdown-bound merged: every member is merged
mesh/check pass a9b02bc67604 on 6cc33988801f | unit 2192 run, 3794 kept, 57 skipped | integration 2208 run, 3829 kept, 57 skipped | over bound: unit, …

The test watched two seconds per row; a 10 ms restart pause and a 250 ms window
prove the same. launch.Start itself now refuses while the runtime stops, so a
reload's first start is held too (issue 1126).
This commit is contained in:
2026-10-28 15:40:39 +01:00
parent f016c67a72
commit a9b02bc676
2 changed files with 16 additions and 3 deletions
+13 -3
View File
@@ -53,7 +53,7 @@ done
// itself is stopping, none is, by a restart or by a call. // itself is stopping, none is, by a restart or by a call.
func TestABundleIsStartedAgainUnlessTheRuntimeIsStopping(t *testing.T) { func TestABundleIsStartedAgainUnlessTheRuntimeIsStopping(t *testing.T) {
saved := RestartFirst saved := RestartFirst
RestartFirst = 50 * time.Millisecond RestartFirst = 10 * time.Millisecond
t.Cleanup(func() { RestartFirst = saved; shuttingDown.Store(false) }) t.Cleanup(func() { RestartFirst = saved; shuttingDown.Store(false) })
for _, row := range []struct { for _, row := range []struct {
name string name string
@@ -86,20 +86,30 @@ func TestABundleIsStartedAgainUnlessTheRuntimeIsStopping(t *testing.T) {
if err := syscall.Kill(pid, syscall.SIGTERM); err != nil { if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
t.Fatal(err) t.Fatal(err)
} }
deadline := time.Now().Add(2 * time.Second) // The restart row ends at its second start; the stopping row watches a window many restart pauses long.
deadline := time.Now().Add(250 * time.Millisecond)
for { for {
said, _ = os.ReadFile(out) said, _ = os.ReadFile(out)
n := strings.Count(string(said), "started") n := strings.Count(string(said), "started")
if n > row.starts { if n > row.starts {
t.Fatalf("started %d times, want %d", n, row.starts) t.Fatalf("started %d times, want %d", n, row.starts)
} }
if n == row.starts && !row.stopping {
break
}
if time.Now().After(deadline) { if time.Now().After(deadline) {
if n != row.starts { if n != row.starts {
t.Fatalf("started %d times, want %d", n, row.starts) t.Fatalf("started %d times, want %d", n, row.starts)
} }
break break
} }
time.Sleep(20 * time.Millisecond) time.Sleep(5 * time.Millisecond)
}
if row.stopping {
// And a bundle first loaded now (a reload) is not started either.
if _, err := Start("late", entry, append(os.Environ(), "OUT="+out), &subscribing{}, t.Logf); err == nil {
t.Error("a bundle was started after the runtime began stopping")
}
} }
}) })
} }
+3
View File
@@ -194,6 +194,9 @@ var colour = regexp.MustCompile("\x1b\\[[0-9;]*m")
// Start launches one bundle and learns its tools. It fails when the child cannot be started or does // Start launches one bundle and learns its tools. It fails when the child cannot be started or does
// not complete the handshake. A child that exits later is started again on its next call. // not complete the handshake. A child that exits later is started again on its next call.
func Start(module, entry string, env []string, mesh Bus, logf func(string, ...any)) (*Launched, error) { func Start(module, entry string, env []string, mesh Bus, logf func(string, ...any)) (*Launched, error) {
if shuttingDown.Load() {
return nil, fmt.Errorf("%s's bundle is not started: the runtime is stopping", module)
}
var mu sync.Mutex var mu sync.Mutex
var current *child var current *child
// The child that last subscribed is the one events are handed to: a restarted child subscribes // The child that last subscribed is the one events are handed to: a restarted child subscribes