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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"iter"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -217,6 +218,23 @@ type FloodArtifact interface {
|
|||||||
Artifact
|
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
|
// TrivialArtifact refers to an [Artifact] that cures without requiring that
|
||||||
// any other [Artifact] is cured before it. Its dependency tree is ignored after
|
// any other [Artifact] is cured before it. Its dependency tree is ignored after
|
||||||
// computing its identifier.
|
// computing its identifier.
|
||||||
@@ -405,9 +423,11 @@ type Cache struct {
|
|||||||
// Directory where all [Cache] related files are placed.
|
// Directory where all [Cache] related files are placed.
|
||||||
base *check.Absolute
|
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.
|
// significantly reduces performance.
|
||||||
strict bool
|
strict bool
|
||||||
|
// Maximum size of a dependency graph.
|
||||||
|
threshold uintptr
|
||||||
|
|
||||||
// Synchronises access to dirChecksum.
|
// Synchronises access to dirChecksum.
|
||||||
checksumMu sync.RWMutex
|
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.
|
// This method is not safe for concurrent use with any other method.
|
||||||
func (c *Cache) SetStrict(strict bool) { c.strict = strict }
|
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.
|
// A ChecksumMismatchError describes an [Artifact] with unexpected content.
|
||||||
type ChecksumMismatchError struct {
|
type ChecksumMismatchError struct {
|
||||||
// Actual and expected checksums.
|
// Actual and expected checksums.
|
||||||
@@ -894,12 +920,40 @@ func (e InvalidArtifactError) Error() string {
|
|||||||
return "artifact " + Encode(e) + " cannot be cured"
|
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
|
// Cure cures the [Artifact] and returns its pathname and [Checksum]. Direct
|
||||||
// calls to Cure are not subject to the cures limit.
|
// calls to Cure are not subject to the cures limit.
|
||||||
func (c *Cache) Cure(a Artifact) (
|
func (c *Cache) Cure(a Artifact) (
|
||||||
pathname *check.Absolute,
|
pathname *check.Absolute,
|
||||||
checksum Checksum,
|
checksum Checksum,
|
||||||
err error,
|
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)
|
id := Ident(a)
|
||||||
ids := Encode(id)
|
ids := Encode(id)
|
||||||
@@ -1190,7 +1244,7 @@ func (c *Cache) Cure(a Artifact) (
|
|||||||
func (pending *pendingArtifactDep) cure(c *Cache) {
|
func (pending *pendingArtifactDep) cure(c *Cache) {
|
||||||
defer pending.Done()
|
defer pending.Done()
|
||||||
|
|
||||||
pathname, _, err := c.Cure(pending.a)
|
pathname, _, err := c.cure(pending.a)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
*pending.resP = pathname
|
*pending.resP = pathname
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user