Both pass runners do nothing once the loop's context is cancelled, so a stop no longer starts an eviction pass after a cancelled reconciliation or on a pending write-pressure wakeup or tick. In evictBatch, a candidate that fails after cancellation ends the batch with the context's error instead of logging its own warning; this replaces the check before each candidate, since every candidate started after cancellation fails at its first database call. A stop now logs at most one warning. Model: opus-5-5
This commit is contained in:
@@ -32,7 +32,8 @@ P2: security: referer blacklist
|
|||||||
- 2026-10-04 shutdown stops cache eviction in progress (closes #102):
|
- 2026-10-04 shutdown stops cache eviction in progress (closes #102):
|
||||||
`StartEviction` runs the eviction goroutine with its own context, which
|
`StartEviction` runs the eviction goroutine with its own context, which
|
||||||
`StopEviction` cancels, so a pass in progress stops at its next database
|
`StopEviction` cancels, so a pass in progress stops at its next database
|
||||||
call, file, row or eviction candidate instead of running to completion;
|
call, file, row or eviction candidate instead of running to completion, and
|
||||||
|
no pass starts after it, so a stop logs at most one warning;
|
||||||
`StopEviction` takes a context and, when that context ends before the
|
`StopEviction` takes a context and, when that context ends before the
|
||||||
goroutine exits, stops waiting and returns its error; the handlers' stop hook
|
goroutine exits, stops waiting and returns its error; the handlers' stop hook
|
||||||
passes fx's stop context, so an eviction still running when fx's stop
|
passes fx's stop context, so an eviction still running when fx's stop
|
||||||
|
|||||||
@@ -117,8 +117,10 @@ func (c *Cache) EvictToLimit(ctx context.Context) error {
|
|||||||
|
|
||||||
// evictBatch fetches one batch of LRU candidates across variants and
|
// evictBatch fetches one batch of LRU candidates across variants and
|
||||||
// source blobs and evicts them oldest-first until excessBytes are
|
// source blobs and evicts them oldest-first until excessBytes are
|
||||||
// freed or the batch is exhausted. It returns the bytes freed. Once ctx
|
// freed or the batch is exhausted. It returns the bytes freed. A
|
||||||
// is cancelled, it stops at the next candidate and returns ctx's error.
|
// candidate that fails once ctx is cancelled (every one started after
|
||||||
|
// that fails at its first database call) ends the batch with ctx's
|
||||||
|
// error, without a warning.
|
||||||
func (c *Cache) evictBatch(ctx context.Context, excessBytes int64) (int64, error) {
|
func (c *Cache) evictBatch(ctx context.Context, excessBytes int64) (int64, error) {
|
||||||
candidates, err := c.evictionCandidates(ctx)
|
candidates, err := c.evictionCandidates(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -132,12 +134,12 @@ func (c *Cache) evictBatch(ctx context.Context, excessBytes int64) (int64, error
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return freed, ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.evictCandidate(ctx, candidate)
|
err := c.evictCandidate(ctx, candidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return freed, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
c.log.Warn("failed to evict cache entry",
|
c.log.Warn("failed to evict cache entry",
|
||||||
"cache_key", candidate.cacheKey,
|
"cache_key", candidate.cacheKey,
|
||||||
"content_hash", candidate.contentHash,
|
"content_hash", candidate.contentHash,
|
||||||
@@ -463,7 +465,7 @@ func (c *Cache) StopEviction(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// evictionLoop is the body of the background eviction goroutine. It
|
// evictionLoop is the body of the background eviction goroutine. It
|
||||||
// returns when ctx is cancelled.
|
// returns when ctx is cancelled, and starts no pass after that.
|
||||||
func (c *Cache) evictionLoop(ctx context.Context, interval time.Duration) {
|
func (c *Cache) evictionLoop(ctx context.Context, interval time.Duration) {
|
||||||
defer close(c.evictionDone)
|
defer close(c.evictionDone)
|
||||||
|
|
||||||
@@ -496,8 +498,13 @@ func (c *Cache) evictionLoop(ctx context.Context, interval time.Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runEvictionPass runs one eviction pass, logging failures instead of
|
// runEvictionPass runs one eviction pass, logging failures instead of
|
||||||
// propagating them (the loop must keep running).
|
// propagating them (the loop must keep running). It does nothing once
|
||||||
|
// ctx is cancelled.
|
||||||
func (c *Cache) runEvictionPass(ctx context.Context) {
|
func (c *Cache) runEvictionPass(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := c.EvictToLimit(ctx)
|
err := c.EvictToLimit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Warn("cache eviction pass failed", "error", err)
|
c.log.Warn("cache eviction pass failed", "error", err)
|
||||||
@@ -505,8 +512,13 @@ func (c *Cache) runEvictionPass(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runReconciliationPass runs one reconciliation pass, logging failures
|
// runReconciliationPass runs one reconciliation pass, logging failures
|
||||||
// instead of propagating them (the loop must keep running).
|
// instead of propagating them (the loop must keep running). It does
|
||||||
|
// nothing once ctx is cancelled.
|
||||||
func (c *Cache) runReconciliationPass(ctx context.Context) {
|
func (c *Cache) runReconciliationPass(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := c.reconcileAccounting(ctx)
|
err := c.reconcileAccounting(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Warn("cache accounting reconciliation failed", "error", err)
|
c.log.Warn("cache accounting reconciliation failed", "error", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user