diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-05-13 17:58:18 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-05-13 17:58:18 +0900 |
| commit | f2f17261905e6ea6c686bb490188f86710fd9720 (patch) | |
| tree | 427aaa81253e469f3fceb0289a35aa97b0e86776 /internal/pkg/pkg.go | |
| parent | f63203cb0a1ed9c1aa74d3608836e3899d8a12d5 (diff) | |
internal/pkg: record cure faults
These are useful for troubleshooting. This change records them in a separate directory.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/pkg.go')
| -rw-r--r-- | internal/pkg/pkg.go | 60 |
1 files changed, 59 insertions, 1 deletions
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index b81abe08..e1d354e0 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -4,6 +4,7 @@ package pkg import ( "bufio" "bytes" + "cmp" "context" "crypto/sha512" "encoding/base64" @@ -25,6 +26,7 @@ import ( "sync/atomic" "syscall" "testing" + "time" "unique" "unsafe" @@ -248,7 +250,14 @@ func (t *TContext) destroy(errP *error) { *errP = errors.Join(*errP, err) } if *errP != nil { - *errP = errors.Join(*errP, os.Remove(t.statusPath.String())) + *errP = errors.Join(*errP, os.Rename( + t.statusPath.String(), t.cache.base.Append( + dirFault, + t.ids+"."+strconv.FormatUint(uint64( + time.Now().UnixNano(), + ), 10), + ).String(), + )) } t.status = nil } @@ -527,6 +536,8 @@ const ( // identifier. For [FloodArtifact], the same file is also available under // its substitute identifier. dirStatus = "status" + // dirFault holds status files of faulted cures. + dirFault = "fault" // dirWork holds working pathnames set up during [Cache.Cure]. dirWork = "work" @@ -2148,6 +2159,52 @@ func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) { return } +// Fault holds the pathname and termination time of an [Artifact] fault entry. +type Fault struct { + *check.Absolute + t uint64 +} + +// Time returns the instant in time where the fault occurred. +func (f Fault) Time() time.Time { return time.Unix(0, int64(f.t)) } + +// Open opens the underlying entry for reading. +func (f Fault) Open() (io.ReadCloser, error) { return os.Open(f.Absolute.String()) } + +// Destroy removes the underlying fault entry. +func (f Fault) Destroy() error { return os.Remove(f.Absolute.String()) } + +// ReadFaults returns fault entries for an [Artifact]. +func (c *Cache) ReadFaults(a Artifact) (faults []Fault, err error) { + prefix := Encode(c.Ident(a).Value()) + "." + var dents []os.DirEntry + if dents, err = os.ReadDir(c.base.Append(dirFault).String()); err != nil { + return + } + + for _, dent := range dents { + name := dent.Name() + if !strings.HasPrefix(name, prefix) { + continue + } + var t uint64 + t, err = strconv.ParseUint(name[len(prefix):], 10, 64) + if err != nil { + return + } + + faults = append(faults, Fault{c.base.Append( + dirFault, + name, + ), t}) + } + + slices.SortFunc(faults, func(a, b Fault) int { + return cmp.Compare(a.t, b.t) + }) + return +} + // Abort cancels all pending cures and waits for them to clean up, but does not // close the cache. func (c *Cache) Abort() { @@ -2251,6 +2308,7 @@ func open( dirIdentifier, dirChecksum, dirStatus, + dirFault, dirWork, } { if err := os.MkdirAll( |
