All of mesh-build-go's buildstep Go module leaves mesh-catalog for modules/mesh-delivery-sdk: the step run, fetch and release and the regenerate-and-compare step as the package steprun, the CPU share as steprun/buildcpu, the conformance kit as steprun/conformance, the seat generator as seatgen with cmd/seatgen, and cmd/mesh-pack. The three build modules and the builder's and checker's tests import the SDK and keep no copy; the SDK requires nothing of mesh-catalog. Code the build modules share is the delivery domain's SDK, not one module's package. It carries the build-module round's two new files of that package (dropdetected.go 247c422f, storedbuilds.go 904b8aed). Builds: design 47 G §464, §680, §327, §420, §495 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
113 lines
3.5 KiB
Go
113 lines
3.5 KiB
Go
//go:build linux
|
|
|
|
package steprun
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
const settingsMask = syscall.IN_CLOSE_WRITE | syscall.IN_MOVED_TO | syscall.IN_CREATE
|
|
|
|
// watchSettings wakes the puller each time its settings file is written or replaced (inotify on the file's directory, so
|
|
// a rename over it counts): the event the node-engine's refill of the file is, which a parked puller at capacity 0
|
|
// otherwise never sees. No timer: it blocks on the kernel's event. When the kernel drops the watch (IN_IGNORED: the
|
|
// directory was replaced or removed) it is added again, said once; while the directory is missing the nearest existing
|
|
// ancestor is watched for the directory's return. A watch that cannot start is said once.
|
|
func (p *Puller) watchSettings(ctx context.Context) {
|
|
if p.SettingsFile == "" {
|
|
return
|
|
}
|
|
fd, err := syscall.InotifyInit1(syscall.IN_CLOEXEC | syscall.IN_NONBLOCK)
|
|
if err != nil {
|
|
p.logf("the settings file %s cannot be watched, so a refill of it wakes the puller only at another event: %v", p.SettingsFile, err)
|
|
return
|
|
}
|
|
f := os.NewFile(uintptr(fd), "inotify")
|
|
defer f.Close()
|
|
dir, base := filepath.Split(p.SettingsFile)
|
|
dir = filepath.Clean(dir)
|
|
// arm watches dir, or when it is missing its nearest existing ancestor; it returns the watch descriptor and whether it
|
|
// is the directory's own.
|
|
arm := func() (int, bool, error) {
|
|
wd, err := syscall.InotifyAddWatch(fd, dir, settingsMask)
|
|
if err == nil {
|
|
return wd, true, nil
|
|
}
|
|
for up := filepath.Dir(dir); ; up = filepath.Dir(up) {
|
|
wd, aerr := syscall.InotifyAddWatch(fd, up, settingsMask)
|
|
if aerr == nil {
|
|
// the directory may have come back between the two calls: look once more, now that the ancestor is watched
|
|
if own, oerr := syscall.InotifyAddWatch(fd, dir, settingsMask); oerr == nil {
|
|
return own, true, nil
|
|
}
|
|
return wd, false, nil
|
|
}
|
|
if up == filepath.Dir(up) {
|
|
return 0, false, err
|
|
}
|
|
}
|
|
}
|
|
wd, own, err := arm()
|
|
if err != nil {
|
|
p.logf("the settings file %s cannot be watched, so a refill of it wakes the puller only at another event: %v", p.SettingsFile, err)
|
|
return
|
|
}
|
|
if p.watching != nil {
|
|
p.watching()
|
|
}
|
|
go func() { <-ctx.Done(); f.Close() }()
|
|
buf := make([]byte, 4096)
|
|
for {
|
|
n, err := f.Read(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
hit, rearm := false, false
|
|
for off := 0; off+16 <= n; {
|
|
ewd := int(int32(binary.LittleEndian.Uint32(buf[off:])))
|
|
mask := binary.LittleEndian.Uint32(buf[off+4:])
|
|
l := int(binary.LittleEndian.Uint32(buf[off+12:]))
|
|
name := buf[off+16 : off+16+l]
|
|
for i, c := range name {
|
|
if c == 0 {
|
|
name = name[:i]
|
|
break
|
|
}
|
|
}
|
|
switch {
|
|
case mask&syscall.IN_IGNORED != 0 && ewd == wd:
|
|
rearm = true
|
|
case ewd == wd && own && string(name) == base:
|
|
hit = true
|
|
case ewd == wd && !own:
|
|
rearm = true // something appeared where the directory was: look again
|
|
}
|
|
off += 16 + l
|
|
}
|
|
if rearm {
|
|
nwd, nown, err := arm()
|
|
if err != nil {
|
|
p.logf("the settings file %s is no longer watched, so a refill of it wakes the puller only at another event: %v", p.SettingsFile, err)
|
|
return
|
|
}
|
|
if own || nown { // said once per loss of the watch on the directory itself
|
|
if own {
|
|
p.logf("the watch of the settings file %s was dropped (its directory was replaced); watching again", p.SettingsFile)
|
|
}
|
|
}
|
|
wd, own = nwd, nown
|
|
if p.watching != nil {
|
|
p.watching()
|
|
}
|
|
hit = true // the file may have been refilled meanwhile
|
|
}
|
|
if hit {
|
|
p.Wake()
|
|
}
|
|
}
|
|
}
|