-
Notifications
You must be signed in to change notification settings - Fork 38
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 a method to refresh only if lookup is a success, not an error. #159
Add a method to refresh only if lookup is a success, not an error. #159
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.
There are a lot of concurrency problems here. Setting the value and then trying to set it back introduces significant complexity and issues. In particular, if we replace a MapValue.Pending
then we must ensure that the promise
is completed under all circumstances.
Thanks for your reply!
Can you tell me some specific examples that are problematic? |
We didn't change the promise completion logic, it will be same as before: when moving away from
|
If I concurrently get a value immediately after the refresh has started and the computation fails I will observe the error, but if I get the value a moment later after the computation has failed I'll get the old value or potentially nothing at all. |
Calling
Only the part in bold is new, the rest is how |
Sorry if I'm missing something here but currently if I'm waiting on the promise I either get the old value or I get the new value. But under this logic if I'm waiting on the promise the promise is being completed with the exit value of the workflow, even if it is an error. So now I have a case where this error value can be observed even though it is never being put in the cache. So before I either get old or new versus now I get old, new, or this ephemeral failure value. |
@@ -256,7 +268,8 @@ object Cache { | |||
value = map.putIfAbsent(k, MapValue.Pending(new MapKey(k), promise)) | |||
} | |||
val result = if (value eq null) { | |||
lookupValueOf(in, promise) | |||
val rollbackResultIfError = if (rollbackIfError) Right(None) else Left(()) |
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.
Not sure this really makes sense. If the value isn't in the cache at all how is this any different from a get
which will put the value in the cache even if it is an error? I think one of the issues here is we are treating refresh
inconsistently with get
.
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 see your point. To be consistent with get
we can say that we put the error into the cache then and remove that special case.
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.
Yeah I think our principle should be that the idea, as I understand it, is that if there is a valid "good" value in the cache we don't want to overwrite it with a "bad" value. One question that raises is what we should do when the original value is itself an error.
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 do wonder whether this is the best path to address this. Like do you want to cache error values at all?
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.
- Empty cache
- Lookup Success -> replace with lookup exit
- Lookup Error -> replace with lookup exit
- Existing cache value
- Lookup Success -> replace with lookup exit
- Lookup Error
- if the old one was a success, we keep it
- if the old one was an error, do we keep it or replace with the new error? Replacing will reset the error ttl, so I think keeping the old one would be better since the main goal is to refresh the success value?
rollbackResultIfError match { | ||
case Left(()) => | ||
map.put(key, MapValue.Complete(new MapKey(key), exit, entryStats, now.plus(timeToLive(exit)))) | ||
case Right(None) => |
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.
Similarly here this is basically a get
when there is no previous value.
case Right(None) => | ||
map.remove(key) | ||
case Right(Some(rollbackResult)) => | ||
map.put(key, rollbackResult) |
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.
Here if the time to live has already expired this is really another get
.
Actually thinking more about this, there is another problem: if you call Any ideas for a different approach? What really matters for us is only the case where we had a good value that we want to refresh it only with another good one. We don't really care of cases where the value didn't exist or was an error. A simple |
I made PR with a different approach. Can you take a look at the above PR? |
Added
def refreshValue(key: Key): IO[Error, Unit]
to keep old value in case of an error.