From 9e18de1dc2fb195872b8d0d39e2924b110c43054 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 18 Apr 2026 02:59:44 +0900 Subject: [PATCH] internal/pkg: flush cached errors on abort This avoids disabling the artifact until cache is reopened. The same has to be implemented for Cancel in a future change. Signed-off-by: Ophestra --- internal/pkg/pkg.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index d7191907..36aa7a91 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -219,7 +219,7 @@ func (c *common) Open(a Artifact) (r io.ReadCloser, err error) { } var pathname *check.Absolute - if pathname, _, err = c.cache.Cure(a); err != nil { + if pathname, _, err = c.cache.cure(a, true); err != nil { return } @@ -601,6 +601,8 @@ type Cache struct { identPending map[unique.Handle[ID]]*pendingCure // Synchronises access to ident and corresponding filesystem entries. identMu sync.RWMutex + // Synchronises entry into Abort and Cure. + abortMu sync.RWMutex // Synchronises entry into exclusive artifacts for the cure method. exclMu sync.Mutex @@ -1036,6 +1038,9 @@ func (c *Cache) Scrub(checks int) error { // loadOrStoreIdent attempts to load a cached [Artifact] by its identifier or // wait for a pending [Artifact] to cure. If neither is possible, the current // identifier is stored in identPending and a non-nil channel is returned. +// +// Since identErr is treated as grow-only, loadOrStoreIdent must not be entered +// without holding a read lock on abortMu. func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) ( ctx context.Context, done chan<- struct{}, @@ -1069,6 +1074,7 @@ func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) ( d := make(chan struct{}) pending = &pendingCure{done: d} ctx, pending.cancel = context.WithCancel(c.toplevel.Load().ctx) + c.wg.Add(1) c.identPending[id] = pending c.identMu.Unlock() done = d @@ -1091,6 +1097,7 @@ func (c *Cache) finaliseIdent( } delete(c.identPending, id) c.identMu.Unlock() + c.wg.Done() close(done) } @@ -1310,6 +1317,9 @@ func (c *Cache) Cure(a Artifact) ( checksum unique.Handle[Checksum], err error, ) { + c.abortMu.RLock() + defer c.abortMu.RUnlock() + if err = c.toplevel.Load().ctx.Err(); err != nil { return } @@ -1504,7 +1514,8 @@ func (r *RContext) NewMeasuredReader( return r.cache.newMeasuredReader(rc, checksum) } -// cure implements Cure without checking the full dependency graph. +// cure implements Cure without acquiring a read lock on abortMu. cure must not +// be entered during Abort. func (c *Cache) cure(a Artifact, curesExempt bool) ( pathname *check.Absolute, checksum unique.Handle[Checksum], @@ -1871,8 +1882,8 @@ func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) { return } -// Abort cancels all pending cures but does not close the store. Abort does not -// wait for cures to complete. +// Abort cancels all pending cures and waits for them to clean up, but does not +// close the cache. func (c *Cache) Abort() { c.closeMu.Lock() defer c.closeMu.Unlock() @@ -1881,7 +1892,16 @@ func (c *Cache) Abort() { return } - c.toplevel.Swap(newToplevel(c.parent)).cancel() + c.toplevel.Load().cancel() + c.abortMu.Lock() + defer c.abortMu.Unlock() + + // holding abortMu, identPending stays empty + c.wg.Wait() + c.identMu.Lock() + c.toplevel.Store(newToplevel(c.parent)) + clear(c.identErr) + c.identMu.Unlock() } // Close cancels all pending cures and waits for them to clean up.