Skip to content
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

Merged
merged 1 commit into from Apr 23, 2018

Conversation

jshufro
Copy link
Contributor

@jshufro jshufro commented Apr 5, 2018

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.

@coveralls
Copy link

coveralls commented Apr 5, 2018

Coverage Status

Coverage increased (+0.2%) to 90.223% when pulling 7385726 on jshufro:tracking into e3e2429 on allegro:master.

Copy link
Collaborator

@siennathesane siennathesane left a 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
Copy link
Collaborator

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. :)

Copy link
Contributor Author

@jshufro jshufro Apr 5, 2018

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

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

@jshufro jshufro Apr 9, 2018

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
Copy link
Collaborator

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
Copy link
Collaborator

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,
Copy link
Collaborator

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) {
Copy link
Collaborator

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. :)

Copy link
Contributor Author

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

Copy link
Collaborator

@cristaloleg cristaloleg left a 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?

@siennathesane
Copy link
Collaborator

Also, Travis is failing due to formatting, so make sure to run go fmt on files you change so it doesn't error out on linting. 😄

@siennathesane
Copy link
Collaborator

siennathesane commented Apr 5, 2018

I really like this change, I would posit there is a path forward via versioning:

Current: OnRemove + OnRemoveExt
Current+1: OnRemove with deprecation notice + OnRemoveExt
Current+2: OnRemoveExt

Go's recommended deprecation path:

Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples in the standard library.

I do agree we shouldn't break backwards compatibility but this is a nice change.

@jshufro
Copy link
Contributor Author

jshufro commented Apr 6, 2018

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

@siennathesane
Copy link
Collaborator

@jshufro can you please fix the bit shifting iotas? The rest looks good. :)

@jshufro
Copy link
Contributor Author

jshufro commented Apr 7, 2018

@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)
Copy link
Collaborator

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

@jshufro
Copy link
Contributor Author

jshufro commented Apr 16, 2018

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

@siennathesane
Copy link
Collaborator

@jshufro excited to see what you come up with!

@jshufro
Copy link
Contributor Author

jshufro commented Apr 20, 2018

I pushed up a change that does the following:

  1. no longer bitshift by iota, but instead bitshift when reading/writing the filter mask
  2. add a variadric function to set the filter mask
    edit: the function introduces the fluent pattern for config.go- there are no other 'setters', so I'm curious to hear if this approach is acceptable.
  3. rename OnRemoveExt to OnRemoveWithReason
    and obviously update docs/comments

bigcache.go Outdated

const (
// Expired means the key is past its LifeWindow.
Expired = iota
Copy link
Collaborator

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

Copy link
Contributor Author

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")
Copy link
Collaborator

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?

Copy link
Contributor Author

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?

Copy link
Collaborator

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 :)

@siennathesane
Copy link
Collaborator

@jshufro lgtm, thanks for the change!

siennathesane
siennathesane previously approved these changes Apr 22, 2018
@jshufro
Copy link
Contributor Author

jshufro commented Apr 22, 2018

Had to amend the commit to add a gpg signature

janisz
janisz previously approved these changes Apr 23, 2018
druminski
druminski previously approved these changes Apr 23, 2018
@jshufro
Copy link
Contributor Author

jshufro commented Apr 23, 2018

I signed this with a key for the wrong email. I'll resign it.

@jshufro jshufro dismissed stale reviews from druminski and janisz via 7385726 April 23, 2018 15:02
@jshufro
Copy link
Contributor Author

jshufro commented Apr 23, 2018

Fixed the gpg signature. I promise this is the last update

@siennathesane siennathesane merged commit 7385726 into allegro:master Apr 23, 2018
@siennathesane
Copy link
Collaborator

Merged, thanks for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants