aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/pkg.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-05-13 17:58:18 +0900
committerOphestra <cat@gensokyo.uk>2026-05-13 17:58:18 +0900
commitf2f17261905e6ea6c686bb490188f86710fd9720 (patch)
tree427aaa81253e469f3fceb0289a35aa97b0e86776 /internal/pkg/pkg.go
parentf63203cb0a1ed9c1aa74d3608836e3899d8a12d5 (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.go60
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(