From 8cdb7c303210cf29df850f403a39d00d56e07238 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 22:18:12 +0100 Subject: [PATCH] The credential wait's test stays under a second, and a failed watch says the re-read, not a restart (issue 1496) Review of 3baeaec: TestAwaitCredential took 1.9 s, over the unit limit; it now sets the watch's settle to 10 ms, writes at 20 ms and gives up at 100 ms (0.2 s). The watch's journal line for a failure named a bundle and a restart; the credential wait now says it is read again every 30 s instead. --- node-tools/cmd/node-tools/main.go | 3 ++- node-tools/cmd/node-tools/main_test.go | 11 +++++++---- node-tools/internal/runtime/watch.go | 17 ++++++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/node-tools/cmd/node-tools/main.go b/node-tools/cmd/node-tools/main.go index ab68a25..816b270 100644 --- a/node-tools/cmd/node-tools/main.go +++ b/node-tools/cmd/node-tools/main.go @@ -171,7 +171,8 @@ func awaitCredential(file string, until time.Time, say func(string, ...any)) (bu case changed <- struct{}{}: default: } - }, func(format string, args ...any) { say(format, args...) }) + }, func(format string, args ...any) { say(format, args...) }, + fmt.Sprintf("the credential is read again every %s instead", rereadEvery)) if werr != nil { say("cannot watch %s (%v): it is read again every %s instead", file, werr, rereadEvery) } else { diff --git a/node-tools/cmd/node-tools/main_test.go b/node-tools/cmd/node-tools/main_test.go index 63aa778..a9d093a 100644 --- a/node-tools/cmd/node-tools/main_test.go +++ b/node-tools/cmd/node-tools/main_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/novox/mesh-tools/node-tools/internal/bus" + "github.com/novox/mesh-tools/node-tools/internal/runtime" ) // A runtime started before its credential is on the machine waits for it, woken by the watch of its directory, and gives @@ -18,7 +19,9 @@ import ( func TestAwaitCredential(t *testing.T) { const good = `{"url":"tls://bus.example:4222","module":"m"}` rereadEvery = time.Hour // only the watch can wake the read in time - t.Cleanup(func() { rereadEvery = 30 * time.Second }) + settle := runtime.Settle + runtime.Settle = 10 * time.Millisecond // a unit test stays well under a second + t.Cleanup(func() { rereadEvery, runtime.Settle = 30*time.Second, settle }) place := func(path, body string) { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) @@ -34,7 +37,7 @@ func TestAwaitCredential(t *testing.T) { for _, c := range []struct { name string before string // written before the wait, "" none - later string // written 200ms into the wait, "" none + later string // written 20ms into the wait, "" none deeper bool // the file's directory is not there at the start bound time.Duration want string // "" a credential; else a word of the error @@ -44,7 +47,7 @@ func TestAwaitCredential(t *testing.T) { {name: "written during the wait", later: good, bound: 5 * time.Second, said: 2}, {name: "empty, then filled", before: " ", later: good, bound: 5 * time.Second, said: 2}, {name: "its directory made during the wait", later: good, deeper: true, bound: 5 * time.Second, said: 2}, - {name: "never written", bound: 400 * time.Millisecond, want: "waited", said: 1}, + {name: "never written", bound: 100 * time.Millisecond, want: "waited", said: 1}, {name: "no url", before: `{"module":"m"}`, bound: 5 * time.Second, want: "not a broker credential", said: 0}, } { t.Run(c.name, func(t *testing.T) { @@ -57,7 +60,7 @@ func TestAwaitCredential(t *testing.T) { place(file, c.before) } if c.later != "" { - go func() { time.Sleep(200 * time.Millisecond); place(file, c.later) }() + go func() { time.Sleep(20 * time.Millisecond); place(file, c.later) }() } var said []string began := time.Now() diff --git a/node-tools/internal/runtime/watch.go b/node-tools/internal/runtime/watch.go index 6486011..f215962 100644 --- a/node-tools/internal/runtime/watch.go +++ b/node-tools/internal/runtime/watch.go @@ -35,24 +35,31 @@ type bundleWatch struct { changed func() logf func(string, ...any) done chan struct{} + // what names what is watched, and lost what follows when the watch fails, in the words a journal line carries. + what, lost string } // WatchFile hears, as the bundles are heard, the directory of the file at path, or the nearest directory above it that // exists, and calls changed once at its start and once after each burst of changes there has settled: a runtime waiting // for its broker credential reads it again when the node-engine writes it, never on a clock (issue 1496). It answers its // stop. -func WatchFile(path string, changed func(), logf func(string, ...any)) (func(), error) { - return watchBundles([]string{path}, changed, logf) +func WatchFile(path string, changed func(), logf func(string, ...any), lost string) (func(), error) { + return watch([]string{path}, changed, logf, path, lost) } // watchBundles starts the watch of the entrypoints at paths. It answers its stop. func watchBundles(paths []string, changed func(), logf func(string, ...any)) (func(), error) { + return watch(paths, changed, logf, "the bundles", "a bundle placed or changed there is served only after a restart") +} + +// watch starts the watch of the files at paths, named what in its words; lost says what follows when it fails. +func watch(paths []string, changed func(), logf func(string, ...any), what, lost string) (func(), error) { fd, err := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK) if err != nil { return nil, fmt.Errorf("inotify: %w", err) } w := &bundleWatch{fd: fd, paths: paths, byWD: map[int]string{}, settle: Settle, changed: changed, logf: logf, - done: make(chan struct{})} + done: make(chan struct{}), what: what, lost: lost} if err := unix.Pipe2(w.wake[:], unix.O_CLOEXEC|unix.O_NONBLOCK); err != nil { unix.Close(fd) return nil, fmt.Errorf("pipe: %w", err) @@ -83,7 +90,7 @@ func (w *bundleWatch) arm() { } wd, err := unix.InotifyAddWatch(w.fd, dir, watchMask) if err != nil { - w.logf("[mesh-tools] cannot watch %s for %s's bundle: %v; a bundle placed or changed there is served only after a restart", dir, p, err) + w.logf("[mesh-tools] cannot watch %s for %s: %v; %s", dir, p, err, w.lost) break } w.byWD[wd] = dir @@ -126,7 +133,7 @@ func (w *bundleWatch) loop() { continue } if err != nil { - w.logf("[mesh-tools] the watch of the bundles stopped: %v; a bundle placed or changed later is served only after a restart", err) + w.logf("[mesh-tools] the watch of %s stopped: %v; %s", w.what, err, w.lost) return } if fds[1].Revents != 0 {