internal/pkg: exclusive artifacts
All checks were successful
Test / Create distribution (push) Successful in 50s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m36s

This alleviates scheduler overhead when curing many artifacts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-01-27 01:16:21 +09:00
parent 948afe33e5
commit eb67e5e0a8
31 changed files with 132 additions and 68 deletions

View File

@@ -253,6 +253,24 @@ type Artifact interface {
//
// Result must remain identical across multiple invocations.
Dependencies() []Artifact
// IsExclusive returns whether the [Artifact] is exclusive. Exclusive
// artifacts might not run in parallel with each other, and are still
// subject to the cures limit.
//
// Some implementations may saturate the CPU for a nontrivial amount of
// time. Curing multiple such implementations simultaneously causes
// significant CPU scheduler overhead. An exclusive artifact will generally
// not be cured alongside another exclusive artifact, thus alleviating this
// overhead.
//
// Note that [Cache] reserves the right to still cure exclusive
// artifacts concurrently as this is not a synchronisation primitive but
// an optimisation one. Implementations are forbidden from accessing global
// state regardless of exclusivity.
//
// Result must remain identical across multiple invocations.
IsExclusive() bool
}
// FloodArtifact refers to an [Artifact] requiring its entire dependency graph
@@ -472,6 +490,8 @@ type Cache struct {
// Synchronises access to ident and corresponding filesystem entries.
identMu sync.RWMutex
// Synchronises entry into exclusive artifacts for the cure method.
exclMu sync.Mutex
// Buffered I/O free list, must not be accessed directly.
bufioPool sync.Pool
@@ -1215,7 +1235,10 @@ func (e *DependencyCureError) Error() string {
}
// enterCure must be called before entering an [Artifact] implementation.
func (c *Cache) enterCure(curesExempt bool) error {
func (c *Cache) enterCure(a Artifact, curesExempt bool) error {
if a.IsExclusive() {
c.exclMu.Lock()
}
if curesExempt {
return nil
}
@@ -1225,15 +1248,23 @@ func (c *Cache) enterCure(curesExempt bool) error {
return nil
case <-c.ctx.Done():
if a.IsExclusive() {
c.exclMu.Unlock()
}
return c.ctx.Err()
}
}
// exitCure must be called after exiting an [Artifact] implementation.
func (c *Cache) exitCure(curesExempt bool) {
if !curesExempt {
<-c.cures
func (c *Cache) exitCure(a Artifact, curesExempt bool) {
if a.IsExclusive() {
c.exclMu.Unlock()
}
if curesExempt {
return
}
<-c.cures
}
// getWriter is like [bufio.NewWriter] but for bufioPool.
@@ -1456,7 +1487,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
}()
var r io.ReadCloser
if err = c.enterCure(curesExempt); err != nil {
if err = c.enterCure(a, curesExempt); err != nil {
return
}
r, err = f.Cure(&RContext{c})
@@ -1505,7 +1536,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
err = closeErr
}
}
c.exitCure(curesExempt)
c.exitCure(a, curesExempt)
if err != nil {
return
}
@@ -1539,11 +1570,11 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
switch ca := a.(type) {
case TrivialArtifact:
defer t.destroy(&err)
if err = c.enterCure(curesExempt); err != nil {
if err = c.enterCure(a, curesExempt); err != nil {
return
}
err = ca.Cure(&t)
c.exitCure(curesExempt)
c.exitCure(a, curesExempt)
if err != nil {
return
}
@@ -1573,11 +1604,11 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
}
defer f.destroy(&err)
if err = c.enterCure(curesExempt); err != nil {
if err = c.enterCure(a, curesExempt); err != nil {
return
}
err = ca.Cure(&f)
c.exitCure(curesExempt)
c.exitCure(a, curesExempt)
if err != nil {
return
}