internal/pkg: in-flight error resolution
Test / Create distribution (push) Successful in 55s
Test / Sandbox (push) Successful in 2m58s
Test / Hakurei (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 7m35s
Test / ShareFS (push) Successful in 8m31s
Test / Flake checks (push) Successful in 1m32s
Test / Create distribution (push) Successful in 55s
Test / Sandbox (push) Successful in 2m58s
Test / Hakurei (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m55s
Test / Hakurei (race detector) (push) Successful in 7m35s
Test / ShareFS (push) Successful in 8m31s
Test / Flake checks (push) Successful in 1m32s
The DCE is a slow and overcomplicated solution to a simple problem. This change replaces the DCE by resolving errors in-flight. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
+31
-94
@@ -18,7 +18,6 @@ import (
|
||||
"maps"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
@@ -654,10 +653,10 @@ type pendingArtifactDep struct {
|
||||
// if curing succeeds.
|
||||
resP *cureRes
|
||||
|
||||
// Address of result error slice populated during [Cache.Cure], dereferenced
|
||||
// Address of result error map populated during [Cache.Cure], dereferenced
|
||||
// after acquiring errsMu if curing fails. No additional action is taken,
|
||||
// [Cache] and its caller are responsible for further error handling.
|
||||
errs *DependencyCureError
|
||||
errs InputError
|
||||
// Address of mutex synchronising access to errs.
|
||||
errsMu *sync.Mutex
|
||||
|
||||
@@ -1721,109 +1720,43 @@ retry:
|
||||
goto retry
|
||||
}
|
||||
|
||||
// CureError wraps a non-nil error returned attempting to cure an [Artifact].
|
||||
type CureError struct {
|
||||
A Artifact
|
||||
Err error
|
||||
}
|
||||
// An InputError describes inputs of a [FloodArtifact] which had failed to cure.
|
||||
type InputError map[Artifact]error
|
||||
|
||||
// Unwrap returns the underlying error.
|
||||
func (e *CureError) Unwrap() error { return e.Err }
|
||||
// Error returns a user-facing, deterministic text representation of e.
|
||||
func (e InputError) Error() string {
|
||||
ir := NewIR()
|
||||
|
||||
// Error returns the error message from the underlying Err.
|
||||
func (e *CureError) Error() string { return e.Err.Error() }
|
||||
|
||||
// A DependencyCureError wraps errors returned while curing dependencies.
|
||||
type DependencyCureError []*CureError
|
||||
|
||||
// unwrapM recursively expands underlying errors into a caller-supplied map.
|
||||
func (e *DependencyCureError) unwrapM(
|
||||
ctx context.Context,
|
||||
ir *IRCache,
|
||||
me map[unique.Handle[ID]]*CureError,
|
||||
) {
|
||||
for _, err := range *e {
|
||||
if ctx.Err() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
id := ir.Ident(err.A)
|
||||
if _, ok := me[id]; ok {
|
||||
continue
|
||||
}
|
||||
if _e, ok := err.Err.(*DependencyCureError); ok {
|
||||
_e.unwrapM(ctx, ir, me)
|
||||
continue
|
||||
}
|
||||
me[id] = err
|
||||
type input struct {
|
||||
a Artifact
|
||||
id unique.Handle[ID]
|
||||
}
|
||||
}
|
||||
|
||||
// unwrap recursively expands and deduplicates underlying errors.
|
||||
func (e *DependencyCureError) unwrap(
|
||||
ctx context.Context,
|
||||
ir *IRCache,
|
||||
) DependencyCureError {
|
||||
me := make(map[unique.Handle[ID]]*CureError)
|
||||
e.unwrapM(ctx, ir, me)
|
||||
type ent struct {
|
||||
id unique.Handle[ID]
|
||||
err *CureError
|
||||
}
|
||||
errs := make([]*ent, 0, len(me))
|
||||
for id, err := range me {
|
||||
errs = append(errs, &ent{id, err})
|
||||
p := make([]input, 0, len(e))
|
||||
for a := range e {
|
||||
p = append(p, input{a, ir.Ident(a)})
|
||||
}
|
||||
|
||||
var identBuf [2]ID
|
||||
slices.SortFunc(errs, func(a, b *ent) int {
|
||||
slices.SortFunc(p, func(a, b input) int {
|
||||
identBuf[0], identBuf[1] = a.id.Value(), b.id.Value()
|
||||
return slices.Compare(identBuf[0][:], identBuf[1][:])
|
||||
})
|
||||
|
||||
_errs := make(DependencyCureError, len(errs))
|
||||
for i, v := range errs {
|
||||
_errs[i] = v.err
|
||||
}
|
||||
return _errs
|
||||
}
|
||||
|
||||
// Unwrap returns a deduplicated slice of underlying errors.
|
||||
func (e *DependencyCureError) Unwrap() []error {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
errs := e.unwrap(ctx, NewIR())
|
||||
_errs := make([]error, len(errs))
|
||||
for i, err := range errs {
|
||||
_errs[i] = err
|
||||
}
|
||||
return _errs
|
||||
}
|
||||
|
||||
// Error returns a user-facing multiline error message.
|
||||
func (e *DependencyCureError) Error() string {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
ir := NewIR()
|
||||
errs := e.unwrap(ctx, ir)
|
||||
if len(errs) == 0 {
|
||||
return "invalid dependency cure outcome"
|
||||
}
|
||||
var buf strings.Builder
|
||||
buf.WriteString("errors curing dependencies:")
|
||||
for _, err := range errs {
|
||||
buf.WriteString("errors curing inputs:")
|
||||
for _, i := range p {
|
||||
buf.WriteString("\n\t" +
|
||||
reportName(err.A, ir.Ident(err.A)) + ": " +
|
||||
err.Error())
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
buf.WriteString("\nerror resolution cancelled")
|
||||
reportName(i.a, i.id) + ": " +
|
||||
e[i.a].Error())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Unwrap returns a slice of underlying errors in unspecified order.
|
||||
func (e InputError) Unwrap() []error {
|
||||
return slices.AppendSeq(make([]error, 0, len(e)), maps.Values(e))
|
||||
}
|
||||
|
||||
// enterCure must be called before entering an [Artifact] implementation.
|
||||
func (c *Cache) enterCure(a Artifact, curesExempt bool) error {
|
||||
if c.attr.Notify != nil {
|
||||
@@ -2023,7 +1956,7 @@ func (c *Cache) cureMany(
|
||||
wg.Add(len(inputs))
|
||||
var mask []bool
|
||||
res := make([]cureRes, len(inputs))
|
||||
errs := make(DependencyCureError, 0, len(inputs))
|
||||
errs := make(InputError)
|
||||
var errsMu sync.Mutex
|
||||
if shallow {
|
||||
mask = make([]bool, len(inputs))
|
||||
@@ -2042,13 +1975,13 @@ func (c *Cache) cureMany(
|
||||
continue
|
||||
}
|
||||
}
|
||||
pending := pendingArtifactDep{d, &res[i], &errs, &errsMu, &wg}
|
||||
pending := pendingArtifactDep{d, &res[i], errs, &errsMu, &wg}
|
||||
go pending.cure(c)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) > 0 {
|
||||
return mask, &errs
|
||||
return mask, errs
|
||||
}
|
||||
for i, p := range res {
|
||||
if shallow && mask[i] {
|
||||
@@ -2651,7 +2584,11 @@ func (pending *pendingArtifactDep) cure(c *Cache) {
|
||||
}
|
||||
|
||||
pending.errsMu.Lock()
|
||||
*pending.errs = append(*pending.errs, &CureError{pending.a, err})
|
||||
if errs, ok := err.(InputError); ok {
|
||||
maps.Copy(pending.errs, errs)
|
||||
} else {
|
||||
pending.errs[pending.a] = err
|
||||
}
|
||||
pending.errsMu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user