diff --git a/cmd/mbf/cache.go b/cmd/mbf/cache.go index aea54546..7134e5d2 100644 --- a/cmd/mbf/cache.go +++ b/cmd/mbf/cache.go @@ -21,7 +21,7 @@ type cache struct { // Should generally not be used directly. c *pkg.Cache - cures, jobs int + attr pkg.CacheAttr // Primarily to work around missing landlock LSM. hostAbstract bool // Set SCHED_IDLE. @@ -49,18 +49,17 @@ func (cache *cache) open() (err error) { return } - var flags int if cache.idle { - flags |= pkg.CSchedIdle + cache.attr.Flags |= pkg.CSchedIdle } if cache.hostAbstract { - flags |= pkg.CHostAbstract + cache.attr.Flags |= pkg.CHostAbstract } if !cache.verboseInit { - flags |= pkg.CSuppressInit + cache.attr.Flags |= pkg.CSuppressInit } if !cache.deep { - flags |= pkg.CExternShallow + cache.attr.Flags |= pkg.CExternShallow } done := make(chan struct{}) @@ -82,10 +81,8 @@ func (cache *cache) open() (err error) { cache.c, err = pkg.Open( cache.ctx, cache.msg, - flags, - cache.cures, - cache.jobs, base, + &cache.attr, ) if err != nil { return diff --git a/cmd/mbf/daemon_test.go b/cmd/mbf/daemon_test.go index 7b825d0b..5d4bbdfc 100644 --- a/cmd/mbf/daemon_test.go +++ b/cmd/mbf/daemon_test.go @@ -28,8 +28,8 @@ func TestNoReply(t *testing.T) { c, err := pkg.Open( t.Context(), message.New(log.New(os.Stderr, "cir: ", 0)), - 0, 0, 0, check.MustAbs(t.TempDir()), + nil, ) if err != nil { t.Fatalf("Open: error = %v", err) diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index b0f5495a..f3ca4eba 100644 --- a/cmd/mbf/main.go +++ b/cmd/mbf/main.go @@ -203,11 +203,11 @@ func main() { "v", command.BoolFlag(false), "Do not suppress verbose output from init", ).Flag( - &cm.cures, + &cm.attr.Cures, "cures", command.IntFlag(0), "Maximum number of dependencies to cure at any given time", ).Flag( - &cm.jobs, + &cm.attr.Jobs, "jobs", command.IntFlag(0), "Preferred number of jobs to run, when applicable", ).Flag( @@ -934,8 +934,8 @@ func main() { return fmt.Errorf("unknown artifact %q", args[1]) } - if !a.IsExclusive() && cm.jobs < 1 { - cm.jobs = runtime.NumCPU()/n + 1 + if !a.IsExclusive() && cm.attr.Jobs < 1 { + cm.attr.Jobs = runtime.NumCPU()/n + 1 } res := make([]unique.Handle[pkg.Checksum], n) diff --git a/internal/pkg/clean_test.go b/internal/pkg/clean_test.go index e9124ab4..3e21a683 100644 --- a/internal/pkg/clean_test.go +++ b/internal/pkg/clean_test.go @@ -238,7 +238,7 @@ func TestClean(t *testing.T) { base := makeBase(t) msg := message.New(log.New(os.Stderr, "clean: ", 0)) msg.SwapVerbose(testing.Verbose()) - c, err := pkg.Open(t.Context(), msg, 0, 0, 0, base) + c, err := pkg.Open(t.Context(), msg, base, nil) if err != nil { t.Fatal(err) } diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go index 3623c927..d9063c74 100644 --- a/internal/pkg/exec.go +++ b/internal/pkg/exec.go @@ -649,8 +649,8 @@ func (c *Cache) EnterExec( var z *container.Container z, err = e.makeContainer( ctx, c.msg, - c.flags, - c.jobs, + c.attr.Flags, + c.attr.Jobs, hostNet, temp, work, func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) { @@ -693,7 +693,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { msg := f.GetMessage() var z *container.Container if z, err = a.makeContainer( - ctx, msg, f.cache.flags, f.GetJobs(), hostNet, + ctx, msg, f.cache.attr.Flags, f.GetJobs(), hostNet, f.GetTempDir(), f.GetWorkDir(), f.GetArtifact, f.cache.Ident, diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index 4d1d3f72..ea493d39 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -279,7 +279,7 @@ func (c *common) GetMessage() message.Msg { return c.cache.msg } // GetJobs returns the preferred number of jobs to run, when applicable. Its // value must not affect cure outcome. -func (c *common) GetJobs() int { return c.cache.jobs } +func (c *common) GetJobs() int { return c.cache.attr.Jobs } // GetWorkDir returns a pathname to a directory which [Artifact] is expected to // write its output to. This is not the final resting place of the [Artifact] @@ -741,10 +741,8 @@ type Cache struct { // Directory where all [Cache] related files are placed. base *check.Absolute - // Immutable cure options set by [Open]. - flags int - // Immutable job count, when applicable. - jobs int + // Immutable [CacheAttr] populated by [Open]. + attr CacheAttr // Must not be exposed directly. irCache @@ -1449,7 +1447,7 @@ func (c *Cache) openFile( ctx context.Context, f FileArtifact, ) (r io.ReadCloser, err error) { - if kc, ok := f.(KnownChecksum); c.flags&CAssumeChecksum != 0 && ok { + if kc, ok := f.(KnownChecksum); c.attr.Flags&CAssumeChecksum != 0 && ok { c.checksumMu.RLock() r, err = os.Open(c.base.Append( dirChecksum, @@ -2141,7 +2139,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( checksums, ) - if c.flags&CAssumeChecksum != 0 { + if c.attr.Flags&CAssumeChecksum != 0 { c.checksumMu.RLock() checksumFi, err = os.Stat(checksumPathname.String()) c.checksumMu.RUnlock() @@ -2210,7 +2208,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( } r, err = f.Cure(&RContext{common{ctx, c}}) if err == nil { - if checksumPathname == nil || c.flags&CValidateKnown != 0 { + if checksumPathname == nil || c.attr.Flags&CValidateKnown != 0 { h := sha512.New384() hbw := c.getWriter(h) _, err = io.Copy(w, io.TeeReader(r, hbw)) @@ -2227,7 +2225,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( if checksumPathname == nil { checksum = unique.Make(Checksum(buf[:])) checksums = Encode(Checksum(buf[:])) - } else if c.flags&CValidateKnown != 0 { + } else if c.attr.Flags&CValidateKnown != 0 { if got := Checksum(buf[:]); got != checksum.Value() { err = &ChecksumMismatchError{ Got: got, @@ -2319,7 +2317,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( return } extern := externChecksum != zeroChecksum - shallow := extern && c.flags&CExternShallow != 0 + shallow := extern && c.attr.Flags&CExternShallow != 0 inputs := a.Inputs() f := FContext{t, make(map[Artifact]cureRes, len(inputs))} @@ -2383,7 +2381,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( substitutes, ) - if !rebuild && c.flags&CIgnoreSubstitutes == 0 { + if !rebuild && c.attr.Flags&CIgnoreSubstitutes == 0 { var substituteChecksum unique.Handle[Checksum] substituteChecksum, err = c.loadSubstitute(substitute) if err != nil { @@ -2677,6 +2675,20 @@ var ( ErrWouldPromote = errors.New("operation would promote unextended cache") ) +// CacheAttr holds the attributes that will be applied to a new [Cache] opened +// by [Open]. +type CacheAttr struct { + // Concurrent cures of a [FloodArtifact] dependency graph. + Cures int + // Options affecting [Cache] behaviour. + Flags int + // Preferred job count, when applicable. + Jobs int + + // Omit the [lockedfile] lock. + skipLock bool +} + // Open returns the address of a newly opened instance of [Cache]. // // Concurrent cures of a [FloodArtifact] dependency graph is limited to the @@ -2693,20 +2705,8 @@ var ( func Open( ctx context.Context, msg message.Msg, - flags, cures, jobs int, base *check.Absolute, -) (*Cache, error) { - return open(ctx, msg, flags, cures, jobs, base, true) -} - -// open implements Open but allows omitting the [lockedfile] lock when called -// from a test. This is used to simulate invalid states in the test suite. -func open( - ctx context.Context, - msg message.Msg, - flags, cures, jobs int, - base *check.Absolute, - lock bool, + attr *CacheAttr, ) (*Cache, error) { openMu.Lock() defer openMu.Unlock() @@ -2716,11 +2716,15 @@ func open( panic("attempting to open cache with incomplete variant setup") } - if cures < 1 { - cures = runtime.NumCPU() + if attr == nil { + attr = new(CacheAttr) } - if jobs < 1 { - jobs = runtime.NumCPU() + + if attr.Cures < 1 { + attr.Cures = runtime.NumCPU() + } + if attr.Jobs < 1 { + attr.Jobs = runtime.NumCPU() } for _, name := range []string{ @@ -2742,9 +2746,8 @@ func open( c := Cache{ parent: ctx, - cures: make(chan struct{}, cures), - flags: flags, - jobs: jobs, + cures: make(chan struct{}, attr.Cures), + attr: *attr, msg: msg, base: base, @@ -2761,7 +2764,7 @@ func open( } c.toplevel.Store(newToplevel(ctx)) - if lock || !testing.Testing() { + if !attr.skipLock || !testing.Testing() { if unlock, err := lockedfile.MutexAt( base.Append(fileLock).String(), ).Lock(); err != nil { @@ -2822,7 +2825,7 @@ func open( } } else if s := string(p); s == "" { if extension != "" { - if flags&CPromoteVariant == 0 { + if attr.Flags&CPromoteVariant == 0 { c.unlock() return nil, ErrWouldPromote } diff --git a/internal/pkg/pkg_test.go b/internal/pkg/pkg_test.go index ff51d583..a28578c3 100644 --- a/internal/pkg/pkg_test.go +++ b/internal/pkg/pkg_test.go @@ -35,14 +35,14 @@ import ( "hakurei.app/message" ) -//go:linkname unsafeOpen hakurei.app/internal/pkg.open -func unsafeOpen( - ctx context.Context, - msg message.Msg, - flags, cures, jobs int, - base *check.Absolute, - lock bool, -) (*pkg.Cache, error) +var skipLock = func() pkg.CacheAttr { + var attr pkg.CacheAttr + *(*bool)(unsafe.Pointer(reflect.ValueOf(&attr). + Elem(). + FieldByName("skipLock"). + UnsafeAddr())) = true + return attr +}() var ( // extension is a string uniquely identifying a set of custom [Artifact] @@ -339,7 +339,7 @@ func TestIdent(t *testing.T) { var cache *pkg.Cache if a, err := check.NewAbs(t.TempDir()); err != nil { t.Fatal(err) - } else if cache, err = pkg.Open(t.Context(), msg, 0, 0, 0, a); err != nil { + } else if cache, err = pkg.Open(t.Context(), msg, a, nil); err != nil { t.Fatal(err) } t.Cleanup(cache.Close) @@ -519,7 +519,10 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) { } var scrubFunc func() error // scrub after hashing - if c, err := pkg.Open(t.Context(), msg, flags, 1<<4, 0, base); err != nil { + if c, err := pkg.Open(t.Context(), msg, base, &pkg.CacheAttr{ + Cures: 1 << 4, + Flags: flags, + }); err != nil { t.Fatalf("Open: error = %v", err) } else { t.Cleanup(c.Close) @@ -867,10 +870,10 @@ func TestCache(t *testing.T) { }}, }) - if c0, err := unsafeOpen( + if c0, err := pkg.Open( t.Context(), message.New(nil), - 0, 0, 0, base, false, + base, &skipLock, ); err != nil { t.Fatalf("open: error = %v", err) } else { @@ -1141,10 +1144,10 @@ func TestCache(t *testing.T) { ), want, pkg.WSubstitute, nil}, }) - if c0, err := unsafeOpen( + if c0, err := pkg.Open( t.Context(), message.New(nil), - 0, 0, 0, base, false, + base, &skipLock, ); err != nil { t.Fatalf("open: error = %v", err) } else { @@ -2050,7 +2053,7 @@ func (a earlyFailureF) Cure(*pkg.FContext) error { func BenchmarkEarlyDCE(b *testing.B) { msg := message.New(log.New(os.Stderr, "dce: ", 0)) msg.SwapVerbose(testing.Verbose()) - c, err := pkg.Open(b.Context(), msg, 0, 0, 0, check.MustAbs(b.TempDir())) + c, err := pkg.Open(b.Context(), msg, check.MustAbs(b.TempDir()), nil) if err != nil { b.Fatal(err) } @@ -2099,7 +2102,8 @@ func TestOpen(t *testing.T) { if _, err := pkg.Open( t.Context(), message.New(nil), - 0, 0, 0, check.MustAbs(container.Nonexistent), + check.MustAbs(container.Nonexistent), + nil, ); !reflect.DeepEqual(err, wantErr) { t.Errorf("Open: error = %#v, want %#v", err, wantErr) } @@ -2127,7 +2131,8 @@ func TestOpen(t *testing.T) { if _, err := pkg.Open( t.Context(), message.New(nil), - 0, 0, 0, tempDir.Append("cache"), + tempDir.Append("cache"), + nil, ); !reflect.DeepEqual(err, wantErr) { t.Errorf("Open: error = %#v, want %#v", err, wantErr) } @@ -2149,7 +2154,8 @@ func TestOpen(t *testing.T) { if _, err := pkg.Open( t.Context(), message.New(nil), - 0, 0, 0, tempDir.Append("cache"), + tempDir.Append("cache"), + nil, ); !reflect.DeepEqual(err, wantErr) { t.Errorf("Open: error = %#v, want %#v", err, wantErr) } @@ -2170,7 +2176,7 @@ func TestOpen(t *testing.T) { if _, err := pkg.Open( t.Context(), message.New(nil), - 0, 0, 0, tempDir.Append("cache"), + tempDir.Append("cache"), nil, ); !reflect.DeepEqual(err, wantErr) { t.Errorf("Open: error = %#v, want %#v", err, wantErr) } @@ -2223,8 +2229,8 @@ func TestExtensionRegister(t *testing.T) { if _, err := pkg.Open( t.Context(), message.New(log.Default()), - 0, 0, 0, check.MustAbs(container.Nonexistent), + nil, ); !errors.Is(err, os.ErrNotExist) { t.Fatalf("Open: error = %v", err) } @@ -2262,7 +2268,7 @@ func TestExtensionRegister(t *testing.T) { pkg.Register(pkg.KindCustomOffset, nil) t.Cleanup(func() { opened = false }) - _, _ = pkg.Open(nil, nil, 0, 0, 0, nil) + _, _ = pkg.Open(nil, nil, nil, nil) panic("unreachable") }) @@ -2273,11 +2279,7 @@ func TestExtensionRegister(t *testing.T) { base := check.MustAbs(t.TempDir()) t.Cleanup(func() { opened = false }) - if c, err := pkg.Open( - t.Context(), nil, - 0, 0, 0, - base, - ); err != nil { + if c, err := pkg.Open(t.Context(), nil, base, nil); err != nil { t.Fatal(err) } else { c.Close() @@ -2305,8 +2307,7 @@ func TestExtensionRegister(t *testing.T) { } if _, err := pkg.Open( t.Context(), nil, - 0, 0, 0, - base, + base, nil, ); !reflect.DeepEqual(err, wantErr) { t.Fatalf("Open: error = %v, want %v", err, wantErr) } @@ -2327,8 +2328,8 @@ func TestExtensionRegister(t *testing.T) { if _, err := pkg.Open( t.Context(), nil, - 0, 0, 0, base, + nil, ); !reflect.DeepEqual(err, pkg.ErrWouldPromote) { t.Fatalf("Open: error = %v", err) } @@ -2341,8 +2342,7 @@ func TestExtensionRegister(t *testing.T) { if c, err := pkg.Open( t.Context(), nil, - pkg.CPromoteVariant, 0, 0, - base, + base, &pkg.CacheAttr{Flags: pkg.CPromoteVariant}, ); err != nil { t.Fatalf("Open: error = %v", err) } else { @@ -2367,8 +2367,7 @@ func TestExtensionRegister(t *testing.T) { if _, err := pkg.Open( t.Context(), nil, - 0, 0, 0, - base, + base, nil, ); !reflect.DeepEqual(err, pkg.ErrInvalidExtension) { t.Fatalf("Open: error = %v", err) } @@ -2385,8 +2384,7 @@ func TestExtensionRegister(t *testing.T) { if _, err := pkg.Open( t.Context(), nil, - 0, 0, 0, - base, + base, nil, ); !reflect.DeepEqual(err, pkg.UnsupportedVariantError("rosa")) { t.Fatalf("Open: error = %v", err) } diff --git a/internal/rosa/mirror_test.go b/internal/rosa/mirror_test.go index acd45c39..6c3ec92e 100644 --- a/internal/rosa/mirror_test.go +++ b/internal/rosa/mirror_test.go @@ -31,7 +31,7 @@ func TestMirror(t *testing.T) { msg.SwapVerbose(testing.Verbose()) var c *pkg.Cache - c, err = pkg.Open(t.Context(), msg, 0, 0, 0, base) + c, err = pkg.Open(t.Context(), msg, base, nil) if err != nil { t.Fatal(err) } diff --git a/internal/rosa/rosa_test.go b/internal/rosa/rosa_test.go index e612c11d..c2a6451c 100644 --- a/internal/rosa/rosa_test.go +++ b/internal/rosa/rosa_test.go @@ -61,7 +61,9 @@ func getCache(t *testing.T) *pkg.Cache { msg := message.New(log.New(os.Stderr, "rosa: ", 0)) msg.SwapVerbose(true) - if buildTestCache, err = pkg.Open(ctx, msg, pkg.CSuppressInit, 0, 0, a); err != nil { + if buildTestCache, err = pkg.Open(ctx, msg, a, &pkg.CacheAttr{ + Flags: pkg.CSuppressInit, + }); err != nil { t.Fatal(err) } }