cmd/mbf: optionally open cache
All checks were successful
Test / Sandbox (push) Successful in 3m13s
Test / Create distribution (push) Successful in 1m1s
Test / ShareFS (push) Successful in 2m42s
Test / Hakurei (push) Successful in 2m49s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m35s
Test / Flake checks (push) Successful in 1m27s

Some commands do not require the cache. This change also makes acquisition of locked cache cancelable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-04-16 15:43:06 +09:00
parent d6b082dd0b
commit 136bc0917b
3 changed files with 219 additions and 76 deletions

90
cmd/mbf/cache.go Normal file
View File

@@ -0,0 +1,90 @@
package main
import (
"context"
"os"
"path/filepath"
"hakurei.app/check"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
// cache refers to an instance of [pkg.Cache] that might be open.
type cache struct {
ctx context.Context
msg message.Msg
// Should generally not be used directly.
c *pkg.Cache
cures, jobs int
hostAbstract, idle bool
base string
}
// open opens the underlying [pkg.Cache].
func (cache *cache) open() (err error) {
if cache.c != nil {
return os.ErrInvalid
}
if cache.base == "" {
cache.base = "cache"
}
var base *check.Absolute
if cache.base, err = filepath.Abs(cache.base); err != nil {
return
} else if base, err = check.NewAbs(cache.base); err != nil {
return
}
var flags int
if cache.idle {
flags |= pkg.CSchedIdle
}
if cache.hostAbstract {
flags |= pkg.CHostAbstract
}
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-cache.ctx.Done():
os.Exit(2)
case <-done:
return
}
}()
cache.msg.Verbosef("opening cache at %s", base)
cache.c, err = pkg.Open(
cache.ctx,
cache.msg,
flags,
cache.cures,
cache.jobs,
base,
)
return
}
// Close closes the underlying [pkg.Cache] if it is open.
func (cache *cache) Close() {
if cache.c != nil {
cache.c.Close()
}
}
// Do calls f on the underlying cache and returns its error value.
func (cache *cache) Do(f func(cache *pkg.Cache) error) error {
if cache.c == nil {
if err := cache.open(); err != nil {
return err
}
}
return f(cache.c)
}

38
cmd/mbf/cache_test.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"context"
"log"
"os"
"testing"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
func TestCache(t *testing.T) {
t.Parallel()
cm := cache{
ctx: context.Background(),
msg: message.New(log.New(os.Stderr, "check: ", 0)),
base: t.TempDir(),
hostAbstract: true, idle: true,
}
defer cm.Close()
cm.Close()
if err := cm.open(); err != nil {
t.Fatalf("open: error = %v", err)
}
if err := cm.open(); err != os.ErrInvalid {
t.Errorf("(duplicate) open: error = %v", err)
}
if err := cm.Do(func(cache *pkg.Cache) error {
return cache.Scrub(0)
}); err != nil {
t.Errorf("Scrub: error = %v", err)
}
}

View File

@@ -54,14 +54,13 @@ func main() {
log.Fatal("this program must not run as root") log.Fatal("this program must not run as root")
} }
var cache *pkg.Cache
ctx, stop := signal.NotifyContext(context.Background(), ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer stop() defer stop()
var cm cache
defer func() { defer func() {
if cache != nil { cm.Close()
cache.Close()
}
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Println(r) fmt.Println(r)
@@ -71,60 +70,34 @@ func main() {
var ( var (
flagQuiet bool flagQuiet bool
flagCures int
flagJobs int
flagBase string
flagIdle bool
flagHostAbstract bool
) )
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) { c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
msg.SwapVerbose(!flagQuiet) msg.SwapVerbose(!flagQuiet)
cm.ctx, cm.msg = ctx, msg
flagBase = os.ExpandEnv(flagBase) cm.base = os.ExpandEnv(cm.base)
if flagBase == "" { return nil
flagBase = "cache"
}
var base *check.Absolute
if flagBase, err = filepath.Abs(flagBase); err != nil {
return
} else if base, err = check.NewAbs(flagBase); err != nil {
return
}
var flags int
if flagIdle {
flags |= pkg.CSchedIdle
}
if flagHostAbstract {
flags |= pkg.CHostAbstract
}
cache, err = pkg.Open(ctx, msg, flags, flagCures, flagJobs, base)
return
}).Flag( }).Flag(
&flagQuiet, &flagQuiet,
"q", command.BoolFlag(false), "q", command.BoolFlag(false),
"Do not print cure messages", "Do not print cure messages",
).Flag( ).Flag(
&flagCures, &cm.cures,
"cures", command.IntFlag(0), "cures", command.IntFlag(0),
"Maximum number of dependencies to cure at any given time", "Maximum number of dependencies to cure at any given time",
).Flag( ).Flag(
&flagJobs, &cm.jobs,
"jobs", command.IntFlag(0), "jobs", command.IntFlag(0),
"Preferred number of jobs to run, when applicable", "Preferred number of jobs to run, when applicable",
).Flag( ).Flag(
&flagBase, &cm.base,
"d", command.StringFlag("$MBF_CACHE_DIR"), "d", command.StringFlag("$MBF_CACHE_DIR"),
"Directory to store cured artifacts", "Directory to store cured artifacts",
).Flag( ).Flag(
&flagIdle, &cm.idle,
"sched-idle", command.BoolFlag(false), "sched-idle", command.BoolFlag(false),
"Set SCHED_IDLE scheduling policy", "Set SCHED_IDLE scheduling policy",
).Flag( ).Flag(
&flagHostAbstract, &cm.hostAbstract,
"host-abstract", command.BoolFlag( "host-abstract", command.BoolFlag(
os.Getenv("MBF_HOST_ABSTRACT") != "", os.Getenv("MBF_HOST_ABSTRACT") != "",
), ),
@@ -156,7 +129,9 @@ func main() {
if flagShifts < 0 || flagShifts > 31 { if flagShifts < 0 || flagShifts > 31 {
flagShifts = 12 flagShifts = 12
} }
return cache.Scrub(runtime.NumCPU() << flagShifts) return cm.Do(func(cache *pkg.Cache) error {
return cache.Scrub(runtime.NumCPU() << flagShifts)
})
}, },
).Flag( ).Flag(
&flagShifts, &flagShifts,
@@ -223,7 +198,10 @@ func main() {
if flagStatus { if flagStatus {
if r == nil { if r == nil {
var f io.ReadSeekCloser var f io.ReadSeekCloser
f, err = cache.OpenStatus(rosa.Std.Load(p)) err = cm.Do(func(cache *pkg.Cache) (err error) {
f, err = cache.OpenStatus(rosa.Std.Load(p))
return
})
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
fmt.Println( fmt.Println(
@@ -239,7 +217,7 @@ func main() {
return return
} }
} }
} else { } else if err = cm.Do(func(cache *pkg.Cache) (err error) {
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p))) status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
if status == nil { if status == nil {
fmt.Println( fmt.Println(
@@ -252,6 +230,9 @@ func main() {
return return
} }
} }
return
}); err != nil {
return
} }
} }
@@ -306,7 +287,9 @@ func main() {
if ext.Isatty(int(w.Fd())) { if ext.Isatty(int(w.Fd())) {
return errors.New("output appears to be a terminal") return errors.New("output appears to be a terminal")
} }
return rosa.WriteReport(msg, w, cache) return cm.Do(func(cache *pkg.Cache) error {
return rosa.WriteReport(msg, w, cache)
})
}, },
) )
@@ -406,23 +389,32 @@ func main() {
checksum [2]unique.Handle[pkg.Checksum] checksum [2]unique.Handle[pkg.Checksum]
) )
if pathname, _, err = cache.Cure( if err = cm.Do(func(cache *pkg.Cache) (err error) {
(t - 2).Load(rosa.Clang), pathname, _, err = cache.Cure(
); err != nil { (t - 2).Load(rosa.Clang),
return err )
return
}); err != nil {
return
} }
log.Println("stage1:", pathname) log.Println("stage1:", pathname)
if pathname, checksum[0], err = cache.Cure( if err = cm.Do(func(cache *pkg.Cache) (err error) {
(t - 1).Load(rosa.Clang), pathname, checksum[0], err = cache.Cure(
); err != nil { (t - 1).Load(rosa.Clang),
return err )
return
}); err != nil {
return
} }
log.Println("stage2:", pathname) log.Println("stage2:", pathname)
if pathname, checksum[1], err = cache.Cure( if err = cm.Do(func(cache *pkg.Cache) (err error) {
t.Load(rosa.Clang), pathname, checksum[1], err = cache.Cure(
); err != nil { t.Load(rosa.Clang),
return err )
return
}); err != nil {
return
} }
log.Println("stage3:", pathname) log.Println("stage3:", pathname)
@@ -439,10 +431,13 @@ func main() {
} }
if flagStage0 { if flagStage0 {
if pathname, _, err = cache.Cure( if err = cm.Do(func(cache *pkg.Cache) (err error) {
t.Load(rosa.Stage0), pathname, _, err = cache.Cure(
); err != nil { t.Load(rosa.Stage0),
return err )
return
}); err != nil {
return
} }
log.Println(pathname) log.Println(pathname)
} }
@@ -487,7 +482,11 @@ func main() {
switch { switch {
default: default:
pathname, _, err := cache.Cure(rosa.Std.Load(p)) var pathname *check.Absolute
err := cm.Do(func(cache *pkg.Cache) (err error) {
pathname, _, err = cache.Cure(rosa.Std.Load(p))
return
})
if err != nil { if err != nil {
return err return err
} }
@@ -527,7 +526,9 @@ func main() {
return err return err
} }
if err = cache.EncodeAll(f, rosa.Std.Load(p)); err != nil { if err = cm.Do(func(cache *pkg.Cache) error {
return cache.EncodeAll(f, rosa.Std.Load(p))
}); err != nil {
_ = f.Close() _ = f.Close()
return err return err
} }
@@ -535,13 +536,15 @@ func main() {
return f.Close() return f.Close()
case flagEnter: case flagEnter:
return cache.EnterExec( return cm.Do(func(cache *pkg.Cache) error {
ctx, return cache.EnterExec(
rosa.Std.Load(p), ctx,
true, os.Stdin, os.Stdout, os.Stderr, rosa.Std.Load(p),
rosa.AbsSystem.Append("bin", "mksh"), true, os.Stdin, os.Stdout, os.Stderr,
"sh", rosa.AbsSystem.Append("bin", "mksh"),
) "sh",
)
})
} }
}, },
). ).
@@ -595,7 +598,10 @@ func main() {
root := make(pkg.Collect, 0, 6+len(args)) root := make(pkg.Collect, 0, 6+len(args))
root = rosa.Std.AppendPresets(root, presets...) root = rosa.Std.AppendPresets(root, presets...)
if _, _, err := cache.Cure(&root); err == nil { if err := cm.Do(func(cache *pkg.Cache) error {
_, _, err := cache.Cure(&root)
return err
}); err == nil {
return errors.New("unreachable") return errors.New("unreachable")
} else if !pkg.IsCollected(err) { } else if !pkg.IsCollected(err) {
return err return err
@@ -607,11 +613,22 @@ func main() {
} }
cured := make(map[pkg.Artifact]cureRes) cured := make(map[pkg.Artifact]cureRes)
for _, a := range root { for _, a := range root {
pathname, checksum, err := cache.Cure(a) if err := cm.Do(func(cache *pkg.Cache) error {
if err != nil { pathname, checksum, err := cache.Cure(a)
if err == nil {
cured[a] = cureRes{pathname, checksum}
}
return err
}); err != nil {
return err
}
}
// explicitly open for direct error-free use from this point
if cm.c == nil {
if err := cm.open(); err != nil {
return err return err
} }
cured[a] = cureRes{pathname, checksum}
} }
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) ( layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
@@ -621,7 +638,7 @@ func main() {
res := cured[a] res := cured[a]
return res.pathname, res.checksum return res.pathname, res.checksum
}, func(i int, d pkg.Artifact) { }, func(i int, d pkg.Artifact) {
r := pkg.Encode(cache.Ident(d).Value()) r := pkg.Encode(cm.c.Ident(d).Value())
if s, ok := d.(fmt.Stringer); ok { if s, ok := d.(fmt.Stringer); ok {
if name := s.String(); name != "" { if name := s.String(); name != "" {
r += "-" + name r += "-" + name
@@ -711,9 +728,7 @@ func main() {
) )
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
if cache != nil { cm.Close()
cache.Close()
}
if w, ok := err.(interface{ Unwrap() []error }); !ok { if w, ok := err.(interface{ Unwrap() []error }); !ok {
log.Fatal(err) log.Fatal(err)
} else { } else {