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.
This commit is contained in:
@@ -171,7 +171,8 @@ func awaitCredential(file string, until time.Time, say func(string, ...any)) (bu
|
|||||||
case changed <- struct{}{}:
|
case changed <- struct{}{}:
|
||||||
default:
|
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 {
|
if werr != nil {
|
||||||
say("cannot watch %s (%v): it is read again every %s instead", file, werr, rereadEvery)
|
say("cannot watch %s (%v): it is read again every %s instead", file, werr, rereadEvery)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
"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
|
// 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) {
|
func TestAwaitCredential(t *testing.T) {
|
||||||
const good = `{"url":"tls://bus.example:4222","module":"m"}`
|
const good = `{"url":"tls://bus.example:4222","module":"m"}`
|
||||||
rereadEvery = time.Hour // only the watch can wake the read in time
|
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) {
|
place := func(path, body string) {
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -34,7 +37,7 @@ func TestAwaitCredential(t *testing.T) {
|
|||||||
for _, c := range []struct {
|
for _, c := range []struct {
|
||||||
name string
|
name string
|
||||||
before string // written before the wait, "" none
|
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
|
deeper bool // the file's directory is not there at the start
|
||||||
bound time.Duration
|
bound time.Duration
|
||||||
want string // "" a credential; else a word of the error
|
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: "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: "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: "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},
|
{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) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
@@ -57,7 +60,7 @@ func TestAwaitCredential(t *testing.T) {
|
|||||||
place(file, c.before)
|
place(file, c.before)
|
||||||
}
|
}
|
||||||
if c.later != "" {
|
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
|
var said []string
|
||||||
began := time.Now()
|
began := time.Now()
|
||||||
|
|||||||
@@ -35,24 +35,31 @@ type bundleWatch struct {
|
|||||||
changed func()
|
changed func()
|
||||||
logf func(string, ...any)
|
logf func(string, ...any)
|
||||||
done chan struct{}
|
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
|
// 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
|
// 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
|
// for its broker credential reads it again when the node-engine writes it, never on a clock (issue 1496). It answers its
|
||||||
// stop.
|
// stop.
|
||||||
func WatchFile(path string, changed func(), logf func(string, ...any)) (func(), error) {
|
func WatchFile(path string, changed func(), logf func(string, ...any), lost string) (func(), error) {
|
||||||
return watchBundles([]string{path}, changed, logf)
|
return watch([]string{path}, changed, logf, path, lost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// watchBundles starts the watch of the entrypoints at paths. It answers its stop.
|
// 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) {
|
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)
|
fd, err := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("inotify: %w", err)
|
return nil, fmt.Errorf("inotify: %w", err)
|
||||||
}
|
}
|
||||||
w := &bundleWatch{fd: fd, paths: paths, byWD: map[int]string{}, settle: Settle, changed: changed, logf: logf,
|
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 {
|
if err := unix.Pipe2(w.wake[:], unix.O_CLOEXEC|unix.O_NONBLOCK); err != nil {
|
||||||
unix.Close(fd)
|
unix.Close(fd)
|
||||||
return nil, fmt.Errorf("pipe: %w", err)
|
return nil, fmt.Errorf("pipe: %w", err)
|
||||||
@@ -83,7 +90,7 @@ func (w *bundleWatch) arm() {
|
|||||||
}
|
}
|
||||||
wd, err := unix.InotifyAddWatch(w.fd, dir, watchMask)
|
wd, err := unix.InotifyAddWatch(w.fd, dir, watchMask)
|
||||||
if err != nil {
|
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
|
break
|
||||||
}
|
}
|
||||||
w.byWD[wd] = dir
|
w.byWD[wd] = dir
|
||||||
@@ -126,7 +133,7 @@ func (w *bundleWatch) loop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
if fds[1].Revents != 0 {
|
if fds[1].Revents != 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user