diff options
Diffstat (limited to 'internal/pkg/pkg.go')
| -rw-r--r-- | internal/pkg/pkg.go | 51 |
1 files changed, 41 insertions, 10 deletions
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index b3b08510..15114fd7 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -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 } |
