Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ sequenceDiagram
Upstream-->>SF: new value
SF->>NFCache: Del(key)
SF->>Cache: Set(key, value)
else Cache Hit + Stale (serveStale=false) or TooStale
Cache-->>Client: value (stale/too stale)
else Cache Hit + Stale (serveStale=false) or Rotten
Cache-->>Client: value (stale/rotten)
Note over Client: Skip NotFoundCache, fetch directly<br/>(backend has data)
Client->>SF: Fetch(key)
SF->>Upstream: Fetch(key)
Expand Down Expand Up @@ -143,8 +143,8 @@ sequenceDiagram
SF->>NFCache: Del(key)
SF->>Cache: Set(key, value)
end
else NotFound Hit + Stale (serveStale=false) or TooStale or Miss
NFCache-->>Client: stale/too stale/miss
else NotFound Hit + Stale (serveStale=false) or Rotten or Miss
NFCache-->>Client: stale/rotten/miss
Client->>SF: Fetch(key)
SF->>Upstream: Fetch(key)
alt Key Exists
Expand Down Expand Up @@ -343,7 +343,7 @@ client := cachex.NewClient(
if age < 5*time.Second + 25*time.Second {
return cachex.StateStale
}
return cachex.StateTooStale
return cachex.StateRotten
}),
cachex.WithServeStale[*Product](true),
)
Expand Down
10 changes: 5 additions & 5 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ sequenceDiagram
Upstream-->>SF: new value
SF->>NFCache: Del(key)
SF->>Cache: Set(key, value)
else Cache Hit + Stale (serveStale=false) or TooStale
Cache-->>Client: value (stale/too stale)
else Cache Hit + Stale (serveStale=false) or Rotten
Cache-->>Client: value (stale/rotten)
Note over Client: Skip NotFoundCache, fetch directly<br/>(backend has data)
Client->>SF: Fetch(key)
SF->>Upstream: Fetch(key)
Expand Down Expand Up @@ -143,8 +143,8 @@ sequenceDiagram
SF->>NFCache: Del(key)
SF->>Cache: Set(key, value)
end
else NotFound Hit + Stale (serveStale=false) or TooStale or Miss
NFCache-->>Client: stale/too stale/miss
else NotFound Hit + Stale (serveStale=false) or Rotten or Miss
NFCache-->>Client: stale/rotten/miss
Client->>SF: Fetch(key)
SF->>Upstream: Fetch(key)
alt Key Exists
Expand Down Expand Up @@ -343,7 +343,7 @@ client := cachex.NewClient(
if age < 5*time.Second + 25*time.Second {
return cachex.StateStale
}
return cachex.StateTooStale
return cachex.StateRotten
}),
cachex.WithServeStale[*Product](true),
)
Expand Down
10 changes: 5 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ func (c *Client[T]) get(ctx context.Context, key string, doubleCheck bool) (T, e
return value, nil
}

case StateTooStale:
// Too stale, must refresh
case StateRotten:
// Rotten, must refresh
}
} else if !IsErrKeyNotFound(err) {
return zero, errors.Wrapf(err, "get from backend failed for key: %s", key)
Expand Down Expand Up @@ -147,8 +147,8 @@ func (c *Client[T]) get(ctx context.Context, key string, doubleCheck bool) (T, e
}, "key not found in cache for key: %s", key)
}

case StateTooStale:
// Too stale, must refresh
case StateRotten:
// Rotten, must refresh
}
} else if !IsErrKeyNotFound(err) {
return zero, errors.Wrapf(err, "get from notFoundCache failed for key: %s", key)
Expand Down Expand Up @@ -415,7 +415,7 @@ func NotFoundWithTTL[T any](cache Cache[time.Time], freshTTL time.Duration, stal
if staleTTL > 0 && age < freshTTL+staleTTL {
return StateStale
}
return StateTooStale
return StateRotten
})
}

Expand Down
18 changes: 9 additions & 9 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestClientStaleHandling(t *testing.T) {
if age < 150*time.Millisecond {
return StateStale
}
return StateTooStale
return StateRotten
}

t.Run("without serve stale", func(t *testing.T) {
Expand Down Expand Up @@ -142,7 +142,7 @@ func TestClientStaleHandling(t *testing.T) {

value, err = cli.Get(ctx, "key2")
require.NoError(t, err)
assert.Equal(t, "fetch-4", value.Data, "should refetch when too stale")
assert.Equal(t, "fetch-4", value.Data, "should refetch when rotten")
assert.Equal(t, 4, fetchCount)
})
}
Expand Down Expand Up @@ -331,12 +331,12 @@ func TestStaleDataCleanupWhenUpstreamDeletes(t *testing.T) {
return nil, &ErrKeyNotFound{}
})

// Stale check: fresh for 100ms, then TooStale (force refetch)
// Stale check: fresh for 100ms, then Rotten (force refetch)
checkStale := func(v *timestampedValue) State {
if clock.Now().Before(v.ExpiresAt) {
return StateFresh
}
return StateTooStale
return StateRotten
}

notFoundCache := newRistrettoCache[time.Time](t)
Expand All @@ -361,7 +361,7 @@ func TestStaleDataCleanupWhenUpstreamDeletes(t *testing.T) {
clock.Advance(150 * time.Millisecond)

// Verify cached data is now stale
assert.Equal(t, StateTooStale, checkStale(cachedValue), "cached data should be stale")
assert.Equal(t, StateRotten, checkStale(cachedValue), "cached data should be rotten")

// Step 3: Meanwhile, data was deleted from upstream
realDataExists = false
Expand Down Expand Up @@ -537,7 +537,7 @@ func TestDoFetchDoesNotTouchUpstream(t *testing.T) {
if clock.Now().Before(v.ExpiresAt) {
return StateFresh
}
return StateTooStale
return StateRotten
}

client := NewClient(backend, trackedUpstream,
Expand Down Expand Up @@ -641,15 +641,15 @@ func TestNotFoundCacheStale(t *testing.T) {
assert.Equal(t, 2, fetchCount, "async refresh should have happened")
})

t.Run("too stale triggers immediate fetch", func(t *testing.T) {
t.Run("rotten triggers immediate fetch", func(t *testing.T) {
clock.Advance(600 * time.Millisecond) // Beyond stale TTL

_, err := cli.Get(ctx, "not-exist")
var e *ErrKeyNotFound
assert.True(t, errors.As(err, &e))
// After refetch, error comes from upstream (not cached)
assert.False(t, e.Cached, "too stale refetch returns fresh upstream error")
assert.Equal(t, 3, fetchCount, "should refetch immediately when too stale")
assert.False(t, e.Cached, "rotten refetch returns fresh upstream error")
assert.Equal(t, 3, fetchCount, "should refetch immediately when rotten")
})
}

Expand Down
2 changes: 1 addition & 1 deletion entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ func EntryWithTTL[T any](freshTTL, staleTTL time.Duration) ClientOption[*Entry[T
if age < freshTTL+staleTTL {
return StateStale
}
return StateTooStale
return StateRotten
})
}
10 changes: 5 additions & 5 deletions entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ func TestEntryWithTTL(t *testing.T) {
assert.Equal(t, StateStale, state)
})

t.Run("too stale entry", func(t *testing.T) {
t.Run("rotten entry", func(t *testing.T) {
now := time.Now()
oldNowFunc := NowFunc
NowFunc = func() time.Time { return now }
defer func() { NowFunc = oldNowFunc }()

entry := &Entry[string]{
Data: "too stale data",
Data: "rotten data",
CachedAt: now.Add(-20 * time.Second), // 20 seconds ago
}

Expand All @@ -124,7 +124,7 @@ func TestEntryWithTTL(t *testing.T) {
option(client)

state := client.checkDataStale(entry)
assert.Equal(t, StateTooStale, state)
assert.Equal(t, StateRotten, state)
})

t.Run("exact boundary - fresh to stale", func(t *testing.T) {
Expand All @@ -146,7 +146,7 @@ func TestEntryWithTTL(t *testing.T) {
assert.Equal(t, StateStale, state)
})

t.Run("exact boundary - stale to too stale", func(t *testing.T) {
t.Run("exact boundary - stale to rotten", func(t *testing.T) {
now := time.Now()
oldNowFunc := NowFunc
NowFunc = func() time.Time { return now }
Expand All @@ -162,7 +162,7 @@ func TestEntryWithTTL(t *testing.T) {
option(client)

state := client.checkDataStale(entry)
assert.Equal(t, StateTooStale, state)
assert.Equal(t, StateRotten, state)
})
}

Expand Down
4 changes: 2 additions & 2 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func (e *ErrKeyNotFound) Error() string {
return "key not found (cached, fresh)"
case StateStale:
return "key not found (cached, stale)"
case StateTooStale:
return "key not found (cached, too stale)"
case StateRotten:
return "key not found (cached, rotten)"
default:
return fmt.Sprintf("key not found (cached, state=%d)", e.CacheState)
}
Expand Down
6 changes: 3 additions & 3 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
type State int8

const (
StateFresh State = iota // Data is fresh and valid
StateStale // Data is stale but usable
StateTooStale // Data is too stale and must be refreshed
StateFresh State = iota // Data is fresh and valid
StateStale // Data is stale but usable
StateRotten // Data is rotten and must be refreshed
)

// Upstream defines the interface for a data source that can retrieve values
Expand Down
Loading