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
Add remove reason signal to OnRemove callback #80
Conversation
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.
Overall it looks good, I like there is a reason. I would ask you fix the iota
comments to make it more idiomatic and then either add a test to cover Expired
or implement it in the same test.
bigcache.go
Outdated
type RemoveReason int32 | ||
const ( | ||
// Expired means the key is past its LifeWindow. | ||
Expired = 1 << iota |
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 don't really see a reason to left shift an iota
, tbh. You can just call iota
like this:
const (
Expired = iota
NoSpace
Deleted
)
It will do the same thing. :)
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.
from my understanding, what you're suggesting will assign the sequence of values as 0, 1, 2
However, for bitmasks, the correct sequence is 1,2,4,8,16 etc
since the binary representation of 4 is, for example, 0100, you can combine these masks with the bitwise | to store multiple bools on the same integer. Thus, Expired | Deleted actually equals 0101, which is what lets me store just one integer instead of several bools
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.
Mmmm while I do appreciate the optimisation, I am going to disagree and say while it is an integer optimization, the benefit of the optimisation underweighs against the idiotmatic use of iota
. When you filed the PR, you made this statement:
I'm still pretty new to golang, so this PR probably has a C-like odor to it
Bit shifting an iota
is something you shouldn't really be doing in idiomatic Go as a lot of the reason for iota
was to provide an enumerator that you don't have to manage or think about - the runtime manages it.
I'm going to defer to @janisz, but my stance is that bit shifting an iota
is over-engineering and not idiomatic.
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'm fine changing the approach to be a list of bools that say basically blockOnRemoveForSpace, blockOnRemoveForExpiration, blockOnRemoveForDeletion- the downside is that in the Set path, where the keys are evicted, we have to do a cascading "if reason == eviction && cache.blockOnRemoveForEviction == false { cache.OnRemove(reason) } else if ...."
In C we always avoid doing long, sequential boolean checks using bitmaps. I guess in Go it's just about choosing where the mess goes- either you bitshift an iota (or, if you'd prefer, we can assign constant values 1 2 4 8 16 etc)- or you check a series of booleans sequentially and have a bunch of branches in a single function.
I'll wait to hear @janisz opinion before making any changes, though.
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.
In C we always avoid doing long, sequential boolean checks using bitmaps.
I agree, but this is Go, you can always do:
switch reason {
case Expired: //
case NoSpace: //
case Deleted: //
}
Which is both cleaner logic as well as being idiomatic. More lines of code for idiomacy is better.
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.
The switch is definitely cleaner- but it still requires 3x variables on the config instead of one, for the filter
bigcache.go
Outdated
Expired = 1 << iota | ||
// NoSpace means the key is the oldest and the cache size was at its maximum when Set was called, or the | ||
// entry exceeded the maximum shard size. | ||
NoSpace = 1 << iota |
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.
Ditto
bigcache.go
Outdated
// entry exceeded the maximum shard size. | ||
NoSpace = 1 << iota | ||
// Deleted means Delete was called and this key was removed as a result. | ||
Deleted = 1 << iota |
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.
Ditto
bigcache_test.go
Outdated
MaxEntriesInWindow: 1, | ||
MaxEntrySize: 256, | ||
OnRemove: onRemove, | ||
OnRemoveFilter: Deleted | NoSpace, |
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.
Should this be ||
once you fix the bit shifting?
@@ -203,6 +204,39 @@ func TestOnRemoveCallback(t *testing.T) { | |||
assert.True(t, onRemoveInvoked) | |||
} | |||
|
|||
func TestOnRemoveFilter(t *testing.T) { |
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.
Need to add another test that would cover Expired
as well. Or implement Expired
in this test. :)
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.
TestOnRemoveCallback asserts the reason is Expired- I can add another test that makes sure it works in the Filter, but we're covered by the expisting one which fails if we get a callback on expiration
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 have some doubts regarding breaking change around OnRemove
, maybe we should add OnRemoveExt
(Ext from Extended) ? So we will save backcompatibility, but add a new feature. WDYT?
Also, Travis is failing due to formatting, so make sure to run |
I really like this change, I would posit there is a path forward via versioning: Current: Go's recommended deprecation path:
I do agree we shouldn't break backwards compatibility but this is a nice change. |
I've made the recommended changes with regard to the API breakage. It should be easy to deprecate OnRemove when the time comes. The only thing worth noting is that OnRemoveExt will be ignored as long as OnRemove is provided, as users shouldn't use both. I've updated the comments/docs to reflect this |
@jshufro can you please fix the bit shifting iotas? The rest looks good. :) |
@mxplusb I replied inline earlier - #80 (comment) |
README.md
Outdated
// for the new entry, or because delete was called. A bitmask representing the reason will be passed through. | ||
// Default value is nil which means no callback and it prevents from unwrapping the oldest entry. | ||
// Ignored if OnRemove is specified. | ||
OnRemoveExt func(key string, entry []byte, reason RemoveReason) |
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'd prefer different name OnRemoveWithReason
sorry for the radio silence- i'll get back to this shortly. I actually have a decent idea of how to keep the single bitmask for the filter without shifting the constants |
@jshufro excited to see what you come up with! |
I pushed up a change that does the following:
|
bigcache.go
Outdated
|
||
const ( | ||
// Expired means the key is past its LifeWindow. | ||
Expired = iota |
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.
Expired RemoveReason = iota
, without an explicit type it will be int
.
See example https://play.golang.org/p/mZZdMaI92cI
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.
Neat. I'll update.
assert.False(t, onRemoveInvoked) | ||
|
||
// and when | ||
cache.Delete("key2") |
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.
onRemoveInvoked
should be set to false
before this line, isn't it?
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.
It's initialized to false on L246 and asserted to still be false on L266
I can explicitly set it to false here if it makes things clearer?
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.
Ah, indeed. So just ignore my comment, sorry :)
@jshufro lgtm, thanks for the change! |
Had to amend the commit to add a gpg signature |
I signed this with a key for the wrong email. I'll resign it. |
Fixed the gpg signature. I promise this is the last update |
Merged, thanks for the contribution! |
I'm still pretty new to golang, so this PR probably has a C-like odor to it (especially with regard to the bitmasks). The general purpose is to indicate in the OnRemove callback why a key is being removed. There are 3 reasons, represented as bitmasks by leftshifting iota in a const expression. I'm not sure if there's a more go-like approach, but I'd like to see support for this feature so I thought I'd open a PR and get a discussion going.
As for the API- I've made a small breaking change. Users of the library will have to add a parameter to their OnRemove callbacks, which can be ignored. If it's preferable to add new functions instead of changing the signature if existing ones, I'd be happy to adapt the code.