internal/pkg: optional dependency graph size limit
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 4m50s
Test / Hpkg (push) Successful in 5m10s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (push) Successful in 5m44s
Test / Hakurei (race detector) (push) Successful in 7m46s
Test / Flake checks (push) Successful in 1m45s
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 4m50s
Test / Hpkg (push) Successful in 5m10s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (push) Successful in 5m44s
Test / Hakurei (race detector) (push) Successful in 7m46s
Test / Flake checks (push) Successful in 1m45s
This provides a quick check against cyclic dependencies without hurting cure performance. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"iter"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -217,6 +218,23 @@ type FloodArtifact interface {
|
||||
Artifact
|
||||
}
|
||||
|
||||
// Flood returns an iterator over the dependency tree of an [Artifact].
|
||||
func Flood(a Artifact) iter.Seq[Artifact] {
|
||||
return func(yield func(Artifact) bool) {
|
||||
for _, d := range a.Dependencies() {
|
||||
if !yield(d) {
|
||||
return
|
||||
}
|
||||
|
||||
for d0 := range Flood(d) {
|
||||
if !yield(d0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TrivialArtifact refers to an [Artifact] that cures without requiring that
|
||||
// any other [Artifact] is cured before it. Its dependency tree is ignored after
|
||||
// computing its identifier.
|
||||
@@ -405,9 +423,11 @@ type Cache struct {
|
||||
// Directory where all [Cache] related files are placed.
|
||||
base *check.Absolute
|
||||
|
||||
// Whether to validate [File.Data] for a [KnownChecksum] file. This
|
||||
// Whether to validate [File.Cure] for a [KnownChecksum] file. This
|
||||
// significantly reduces performance.
|
||||
strict bool
|
||||
// Maximum size of a dependency graph.
|
||||
threshold uintptr
|
||||
|
||||
// Synchronises access to dirChecksum.
|
||||
checksumMu sync.RWMutex
|
||||
@@ -432,6 +452,12 @@ func (c *Cache) IsStrict() bool { return c.strict }
|
||||
// This method is not safe for concurrent use with any other method.
|
||||
func (c *Cache) SetStrict(strict bool) { c.strict = strict }
|
||||
|
||||
// SetThreshold imposes a maximum size on the dependency graph, checked on every
|
||||
// call to Cure. The zero value disables this check entirely.
|
||||
//
|
||||
// This method is not safe for concurrent use with any other method.
|
||||
func (c *Cache) SetThreshold(threshold uintptr) { c.threshold = threshold }
|
||||
|
||||
// A ChecksumMismatchError describes an [Artifact] with unexpected content.
|
||||
type ChecksumMismatchError struct {
|
||||
// Actual and expected checksums.
|
||||
@@ -894,12 +920,40 @@ func (e InvalidArtifactError) Error() string {
|
||||
return "artifact " + Encode(e) + " cannot be cured"
|
||||
}
|
||||
|
||||
// DependencyError refers to an artifact with a dependency tree larger than the
|
||||
// threshold specified by a previous call to [Cache.SetThreshold].
|
||||
type DependencyError struct{ A Artifact }
|
||||
|
||||
func (e DependencyError) Error() string {
|
||||
return "artifact has too many dependencies"
|
||||
}
|
||||
|
||||
// Cure cures the [Artifact] and returns its pathname and [Checksum]. Direct
|
||||
// calls to Cure are not subject to the cures limit.
|
||||
func (c *Cache) Cure(a Artifact) (
|
||||
pathname *check.Absolute,
|
||||
checksum Checksum,
|
||||
err error,
|
||||
) {
|
||||
if c.threshold > 0 {
|
||||
var n uintptr
|
||||
for range Flood(a) {
|
||||
if n == c.threshold {
|
||||
err = DependencyError{a}
|
||||
return
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return c.cure(a)
|
||||
}
|
||||
|
||||
// cure implements Cure without checking the full dependency graph.
|
||||
func (c *Cache) cure(a Artifact) (
|
||||
pathname *check.Absolute,
|
||||
checksum Checksum,
|
||||
err error,
|
||||
) {
|
||||
id := Ident(a)
|
||||
ids := Encode(id)
|
||||
@@ -1190,7 +1244,7 @@ func (c *Cache) Cure(a Artifact) (
|
||||
func (pending *pendingArtifactDep) cure(c *Cache) {
|
||||
defer pending.Done()
|
||||
|
||||
pathname, _, err := c.Cure(pending.a)
|
||||
pathname, _, err := c.cure(pending.a)
|
||||
if err == nil {
|
||||
*pending.resP = pathname
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user