A stopped tool drops its membership handler and a stopped bundle starts no copy on a call, so a replaced bundle's old tools stay gone (issue 1945, review round 2)
mesh/delivery merged: source.main moved to novox/mesh-tools@5d6cee9d844e
mesh/check pass 2cd5550dc70f on 77b05089c8df | unit 0 run 126 kept | int 0 run 128 kept | clone 9.5s | composition passed on 4 machines

Its events are refused before the old bundle stops, the bundle watch follows a directory made between
its stat and its watch, and a bundle's reason is read without node's colour codes.
This commit is contained in:
2026-10-25 15:44:10 +01:00
parent d8ac69904d
commit 2cd5550dc7
5 changed files with 78 additions and 16 deletions
+19 -3
View File
@@ -174,6 +174,11 @@ func (c *child) write(m any) error {
var stackLine = regexp.MustCompile(`^\s+at\s`)
// colour is a terminal's colour or style sequence, which node writes into a stack trace when its
// environment asks for colour (FORCE_COLOR): taken out before a line is judged or kept as a bundle's
// reason, so the reason reads the same in every environment.
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
// 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) {
@@ -245,9 +250,10 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
continue
}
logf("[%s] %s", module, line)
if !stackLine.MatchString(line) && !strings.HasPrefix(line, "Node.js v") {
plain := colour.ReplaceAllString(line, "")
if !stackLine.MatchString(plain) && !strings.HasPrefix(plain, "Node.js v") {
saidMu.Lock()
lastSaid = strings.TrimSpace(line)
lastSaid = strings.TrimSpace(plain)
saidMu.Unlock()
}
}
@@ -456,14 +462,24 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
asking := func(method string, params any, timeout time.Duration) (json.RawMessage, error) {
mu.Lock()
c := current
c, gone := current, stopped
mu.Unlock()
if gone {
// A stopped bundle is never started again by a call: a process started now would be one
// nothing stops (issue 1945).
return nil, fmt.Errorf("%s's bundle is stopped; it answers no call", module)
}
if c == nil {
fresh, err := start()
if err != nil {
return nil, err
}
mu.Lock()
if stopped {
mu.Unlock()
_ = fresh.cmd.Process.Signal(syscall.SIGTERM)
return nil, fmt.Errorf("%s's bundle is stopped; it answers no call", module)
}
if current == nil {
current = fresh
startedAt = time.Now()