diff --git a/go.mod b/go.mod index 960acb74..4b78b40f 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/jedisct1/go-hpke-compact v0.0.0-20241212093903-5caa4621366f github.com/jedisct1/go-ipcrypt v0.1.1 github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 - github.com/jedisct1/go-sieve-cache v0.1.7 + github.com/jedisct1/go-sieve-cache v0.1.8 github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41 github.com/k-sone/critbitgo v1.4.0 github.com/kardianos/service v1.2.4 diff --git a/go.sum b/go.sum index 3365c319..b5ebbb58 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 h1:FWpSWRD8Fb github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7/go.mod h1:BMxO138bOokdgt4UaxZiEfypcSHX0t6SIFimVP1oRfk= github.com/jedisct1/go-sieve-cache v0.1.7 h1:mdc4gTMZI6pUvMTze5K5CHxLxkk5iJcug85g2hERBVk= github.com/jedisct1/go-sieve-cache v0.1.7/go.mod h1:EN88bnjKpiyS9TfZZNbUkCwgpWueZSaUi4vgVtw2988= +github.com/jedisct1/go-sieve-cache v0.1.8 h1:0/yr4dj2rI+u5XWSB4dyxCNeM8AX+vJdnH1mi/bOljc= +github.com/jedisct1/go-sieve-cache v0.1.8/go.mod h1:LV8jfNmswZpdMxHc2uu4R+gTe3kH8CGn/sKnKJVnank= github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41 h1:TPF+VETyhqUOY51j3KF0uk5cgHQ2Bzi6XCorcGNGfTs= github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41/go.mod h1:eh2PYNEklsNDqUxnbnN9Duvpw1b+ZectZAtDUDRj2tA= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= diff --git a/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sharded.go b/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sharded.go index dc2ef890..a2c60fa8 100644 --- a/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sharded.go +++ b/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sharded.go @@ -1,6 +1,7 @@ package sievecache import ( + "encoding/binary" "errors" "fmt" "hash/maphash" @@ -31,6 +32,12 @@ func NewShardedWithShards[K comparable, V any](capacity int, numShards int) (*Sh return nil, errors.New("ShardedSieveCache: number of shards must be greater than 0") } + // Avoid inflating total capacity: never create more shards than the + // total capacity can support (each shard needs at least 1 slot). + if numShards > capacity { + numShards = capacity + } + // Calculate per-shard capacity baseCapacityPerShard := capacity / numShards remaining := capacity % numShards @@ -105,21 +112,11 @@ func (c *ShardedSieveCache[K, V]) getShardIndex(key K) int { h.Write(k) case int: var buf [8]byte - buf[0] = byte(k) - buf[1] = byte(k >> 8) - buf[2] = byte(k >> 16) - buf[3] = byte(k >> 24) - h.Write(buf[:4]) + binary.LittleEndian.PutUint64(buf[:], uint64(k)) + h.Write(buf[:]) case int64: var buf [8]byte - buf[0] = byte(k) - buf[1] = byte(k >> 8) - buf[2] = byte(k >> 16) - buf[3] = byte(k >> 24) - buf[4] = byte(k >> 32) - buf[5] = byte(k >> 40) - buf[6] = byte(k >> 48) - buf[7] = byte(k >> 56) + binary.LittleEndian.PutUint64(buf[:], uint64(k)) h.Write(buf[:]) default: // For other types, convert to string diff --git a/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sievecache.go b/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sievecache.go index 198fa6b9..a169b42f 100644 --- a/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sievecache.go +++ b/vendor/github.com/jedisct1/go-sieve-cache/pkg/sievecache/sievecache.go @@ -104,7 +104,15 @@ func (c *SieveCache[K, V]) Insert(key K, value V) bool { // Evict if at capacity if len(c.nodes) >= c.capacity { - c.Evict() + _, evicted := c.Evict() + // When the cache is full and *all* entries are marked `visited`, our Evict() performs + // a first pass that clears the `visited` bits but may return false without removing + // anything. We still must free a slot before inserting, so we call Evict() a second + // time. This mirrors the original SIEVE miss path, which keeps scanning (wrapping once) + // until it finds an item to evict after clearing bits. + if !evicted { + c.Evict() + } } // Add new node to the end @@ -136,16 +144,18 @@ func (c *SieveCache[K, V]) Remove(key K) (V, bool) { return node.Value, true } - // Update hand if needed + // Update hand if needed (scan direction is from low to high indices) if c.handInitialized { + lastIdx := len(c.nodes) - 1 if c.hand == idx { - // Move hand to the previous node or wrap to end - if idx > 0 { - c.hand = idx - 1 - } else { - c.hand = len(c.nodes) - 2 + newLen := len(c.nodes) - 1 + if newLen == 0 { + c.handInitialized = false + } else if idx >= newLen { + c.hand = 0 } - } else if c.hand == len(c.nodes)-1 { + // Otherwise keep hand at same position (swapped element will be there) + } else if c.hand == lastIdx { // If hand points to the last element (which will be moved to idx) c.hand = idx } @@ -182,12 +192,12 @@ func (c *SieveCache[K, V]) Evict() (V, bool) { return zero, false } - // Start from the hand pointer or the end if hand is not initialized + // Start from the hand pointer or index 0 (tail/oldest) if hand is not initialized var currentIdx int if c.handInitialized { currentIdx = c.hand } else { - currentIdx = len(c.nodes) - 1 + currentIdx = 0 } startIdx := currentIdx @@ -195,7 +205,7 @@ func (c *SieveCache[K, V]) Evict() (V, bool) { wrapped := false foundIdx := -1 - // Scan for a non-visited entry + // Scan for a non-visited entry (from tail toward head) for { // If current node is not visited, mark it for eviction if !c.visited.Get(currentIdx) { @@ -206,17 +216,17 @@ func (c *SieveCache[K, V]) Evict() (V, bool) { // Mark as non-visited for next scan c.visited.Set(currentIdx, false) - // Move to previous node or wrap to end - if currentIdx > 0 { - currentIdx-- + // Move to next node (toward head) or wrap to beginning (tail) + if currentIdx+1 < len(c.nodes) { + currentIdx++ } else { - // Wrap around to end of slice + // Wrap around to beginning of slice if wrapped { // If we've already wrapped, break to avoid infinite loop break } wrapped = true - currentIdx = len(c.nodes) - 1 + currentIdx = 0 } // If we've looped back to start, we've checked all nodes @@ -228,17 +238,29 @@ func (c *SieveCache[K, V]) Evict() (V, bool) { // If we found a node to evict if foundIdx >= 0 { evictIdx := foundIdx + lastIdx := len(c.nodes) - 1 - // Update the hand pointer to the previous node or wrap to end - if evictIdx > 0 { - c.hand = evictIdx - 1 - } else if len(c.nodes) > 1 { - c.hand = len(c.nodes) - 2 + // Update the hand pointer to the next position in scan order + if evictIdx == lastIdx { + // Evicting the last element, hand wraps to 0 + if len(c.nodes) > 1 { + c.hand = 0 + c.handInitialized = true + } else { + c.handInitialized = false + } } else { - // Keep hand at 0 but mark as not initialized - c.handInitialized = false + // swap_remove will move the last element to evictIdx + // Hand should point to evictIdx (where the moved element now is) to continue scanning + // But if hand would be >= new length, wrap to 0 + newLen := len(c.nodes) - 1 + if evictIdx < newLen { + c.hand = evictIdx + } else { + c.hand = 0 + } + c.handInitialized = true } - c.handInitialized = true // Remove the key from the map delete(c.indices, c.nodes[evictIdx].Key) @@ -246,7 +268,7 @@ func (c *SieveCache[K, V]) Evict() (V, bool) { // Remove the node and return its value nodeToEvict := c.nodes[evictIdx] - if evictIdx == len(c.nodes)-1 { + if evictIdx == lastIdx { // If last node, just remove it c.nodes = c.nodes[:len(c.nodes)-1] c.visited.Truncate(len(c.nodes)) @@ -254,7 +276,6 @@ func (c *SieveCache[K, V]) Evict() (V, bool) { } // Otherwise swap with the last node - lastIdx := len(c.nodes) - 1 lastNode := c.nodes[lastIdx] c.nodes[evictIdx] = lastNode c.visited.Set(evictIdx, c.visited.Get(lastIdx)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 7d3ddf13..04e38c2f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -53,7 +53,7 @@ github.com/jedisct1/go-ipcrypt # github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 ## explicit; go 1.23.4 github.com/jedisct1/go-minisign -# github.com/jedisct1/go-sieve-cache v0.1.7 +# github.com/jedisct1/go-sieve-cache v0.1.8 ## explicit; go 1.21 github.com/jedisct1/go-sieve-cache/pkg/sievecache # github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41