-
Notifications
You must be signed in to change notification settings - Fork 455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[x/cache] Fix LRU cache mem leak (when used with no loader) #3806
Changes from 1 commit
f294aef
c5f6237
712fd03
9f2f705
6845b33
790e89c
3aeab78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -229,7 +229,7 @@ func (c *LRU) PutWithTTL(key string, value interface{}, ttl time.Duration) { | |||||||||||||||||||||||||||||||||||||||||||||||
c.mut.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||
defer c.mut.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
_, _ = c.updateCacheEntryWithLock(key, expiresAt, value, nil) | ||||||||||||||||||||||||||||||||||||||||||||||||
_, _ = c.updateCacheEntryWithLock(key, expiresAt, value, nil, true) | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
// Get returns the value associated with the key, optionally | ||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -390,7 +390,7 @@ func (c *LRU) cacheLoadComplete( | |||||||||||||||||||||||||||||||||||||||||||||||
return c.handleCacheLoadErrorWithLock(key, expiresAt, err) | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
return c.updateCacheEntryWithLock(key, expiresAt, value, err) | ||||||||||||||||||||||||||||||||||||||||||||||||
return c.updateCacheEntryWithLock(key, expiresAt, value, err, false) | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
// handleCacheLoadErrorWithLock handles the results of an error from a cache load. If | ||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -402,15 +402,15 @@ func (c *LRU) handleCacheLoadErrorWithLock( | |||||||||||||||||||||||||||||||||||||||||||||||
// If the loader is telling us to cache this error, do so unconditionally | ||||||||||||||||||||||||||||||||||||||||||||||||
var cachedErr CachedError | ||||||||||||||||||||||||||||||||||||||||||||||||
if errors.As(err, &cachedErr) { | ||||||||||||||||||||||||||||||||||||||||||||||||
return c.updateCacheEntryWithLock(key, expiresAt, nil, cachedErr.Err) | ||||||||||||||||||||||||||||||||||||||||||||||||
return c.updateCacheEntryWithLock(key, expiresAt, nil, cachedErr.Err, false) | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
// If the cache is configured to cache errors by default, do so unless | ||||||||||||||||||||||||||||||||||||||||||||||||
// the loader is telling us not to cache this one (e.g. it's transient) | ||||||||||||||||||||||||||||||||||||||||||||||||
var uncachedErr UncachedError | ||||||||||||||||||||||||||||||||||||||||||||||||
isUncachedError := errors.As(err, &uncachedErr) | ||||||||||||||||||||||||||||||||||||||||||||||||
if c.cacheErrors && !isUncachedError { | ||||||||||||||||||||||||||||||||||||||||||||||||
return c.updateCacheEntryWithLock(key, expiresAt, nil, err) | ||||||||||||||||||||||||||||||||||||||||||||||||
return c.updateCacheEntryWithLock(key, expiresAt, nil, err, false) | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
// Something happened during load, but we don't want to cache this - remove the entry, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -430,10 +430,15 @@ func (c *LRU) handleCacheLoadErrorWithLock( | |||||||||||||||||||||||||||||||||||||||||||||||
// updateCacheEntryWithLock updates a cache entry with a new value or cached error, | ||||||||||||||||||||||||||||||||||||||||||||||||
// and marks it as the most recently accessed and most recently loaded entry | ||||||||||||||||||||||||||||||||||||||||||||||||
func (c *LRU) updateCacheEntryWithLock( | ||||||||||||||||||||||||||||||||||||||||||||||||
key string, expiresAt time.Time, value interface{}, err error, | ||||||||||||||||||||||||||||||||||||||||||||||||
key string, expiresAt time.Time, value interface{}, err error, enforceLimit bool, | ||||||||||||||||||||||||||||||||||||||||||||||||
) (interface{}, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
entry := c.entries[key] | ||||||||||||||||||||||||||||||||||||||||||||||||
if entry == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||
if enforceLimit && c.reserveCapacity(1) != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe check the returned error type is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, replacing return value |
||||||||||||||||||||||||||||||||||||||||||||||||
// Silently skip adding the new entry if we fail to free up space for it | ||||||||||||||||||||||||||||||||||||||||||||||||
// (which should never be happening). | ||||||||||||||||||||||||||||||||||||||||||||||||
return value, err | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should return the error from the reserve capacity call yeah? Otherwise the err would be nil yeah?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, this aspect is a bit vague. The contract of Lines 223 to 233 in c5f6237
So I'm not really sure if I should change this (but don't have a strong opinion either, even after jumping around this code for quire a long time). I guess that's the support of two modes (with/without loader) in a single data structure that makes it hard to come up with an elegant implementation. Perhaps @ryanhall07 will chime in with his insights as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see yeah, that makes sense. Feel free to ignore my suggestion then 👍 The other question is, this only happens when there is a code bug yeah? If so.. does that mean we have a remaining code bug we're not aware of? Which is fine if so, we can chase this up after the fact. Just wanted to understand current state of the world (post-merging this change). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, what I meant, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok, that makes sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. left another comment. maybe it's just best to change |
||||||||||||||||||||||||||||||||||||||||||||||||
entry = &lruCacheEntry{} | ||||||||||||||||||||||||||||||||||||||||||||||||
c.entries[key] = entry | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i hate to over optimize, but I do wonder if scanning the entire cache for evictions on every put is going to be too much. I guess we can try this and if it causes performance issues, we can add some kind of eviction every N puts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should not be a problem -
reserveCapacity
removes entries while scanning them, so we either free up one slot for the current Put value, or free up more and then some subsequent Puts will be handled for free.