1
0
forked from rosa/hakurei

cmd/mbf: optionally open cache

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