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
ISPN-9511 Expired event is not fired when modifying an expired entry #6260
ISPN-9511 Expired event is not fired when modifying an expired entry #6260
Conversation
Upon thinking about this further, the fix for ISPN-9530 is not correct. The following line https://github.com/infinispan/infinispan/blob/master/server/hotrod/src/main/java/org/infinispan/server/hotrod/ClientListenerRegistry.java#L206 should really use regular |
38c953a
to
aecbb29
Compare
I will need to rebase this after #6263 is integrated. However it should hopefully pass CI now. |
aecbb29
to
aceaecd
Compare
562ea5e
to
b438ea7
Compare
b438ea7
to
51e4078
Compare
Tests passing, rebased to latest master and added some more docs |
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.
Will, do we have a description somewhere of how clustered expiration is supposed to work? I barely remember anything I've learned about it earlier, and it keeps changing :)
// Override the flags | ||
return FlagBitSets.SKIP_CACHE_LOAD; | ||
// Override the flags to always include skip cache load | ||
return flags | FlagBitSets.SKIP_CACHE_LOAD; |
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.
Maybe it would be nicer to add the SKIP_CACHE_LOAD
flag in CacheImpl
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.
Hrmm, okay. Let me try to reorg it.
if (trace) | ||
log.tracef("Retrieved from container %s", ice); | ||
if (ice == null) { | ||
return NullCacheEntry.getInstance(); | ||
} | ||
return ice; | ||
} else if (isL1Enabled) { | ||
final InternalCacheEntry ice = innerGetFromContainer(key, segment, writeOperation, false); | ||
final InternalCacheEntry ice = innerGetFromContainer(key, segment, false, isPrimaryOwner(isOwner, segment)); |
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.
Could it really be a primary owner if it wasn't an owner?
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.
No, but it seemed a little cleaner than adding an && here and hard coding a value in isPrimaryOwner
. But either way.
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 meant we're already on the else
branch of if (isOwner)
, so isPrimaryOwner(isOwner, segment) == false
.
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 was going to change it yeah :)
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.
Gotcha, I wasn't look at which branch it was on 👍
// actually expire the entry from memory | ||
if (isPrimaryOwner) { | ||
// This method is always called from a write operation | ||
expirationManager.entryExpiredInMemory(ice, currentTime, true).join(); |
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 not sure about this, it feels like we're spreading the expiration logic in too many places. And wrapping entries shouldn't block...
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.
Where would you recommend the logic should go? I don't have a good spot since we don't want this in the DataContainer
. Unless you think we should move the other logic out of the DataContainer
into here?
Wrapping entries already blocks for max idle :)
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.
Why not in the DataContainer
? It already deals with expiration...
Although moving the expiration logic somewhere where it could be made non-blocking does sound better.
Sorry I missed the blocking in the max idle PR review :P
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 responded quickly to run out, but the more I am thinking I would rather probably have this in EntryFactoryImpl
and make both methods non blocking with interceptor stack. Although I haven't looked at details if this is possible.
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.
Now that I got a chance to look at this, I am not sure how to make this non blocking without changing the EntryFactory
interface. What do you think @danberindei ? You have had a lot more experience changing these things.
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, making it non-blocking is a lot more involved. It's in an impl package so changing the interface is not a big deal, but I wouldn't push for it in 9.4.
Not sure if moving everything expiration-related to EntryFactoryImpl
right now is feasible either, that's why my first thought was to keep everything in the data container.
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 to add it to the DataContainer
would require changing that interface, which is a lot more difficult to do. So I would rather move it into EntryFactoryImpl
at some point and eventually try to push that to the EntryWrappingInterceptor
, but I don't see a nice easy way to do that. I would say what is here is probably what will have to be for 9.4.0.
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.
Ok for now :)
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 agree.
@@ -126,25 +127,28 @@ private void addAndWaitIfFull(CompletableFuture future, List<CompletableFuture> | |||
} | |||
} | |||
|
|||
CompletableFuture<Void> handleLifespanExpireEntry(K key, V value, long lifespan) { | |||
// Skip locking is available in case if the entry was found expired due to a write, which would already | |||
// have the lock and remove lifespan expired is fired async |
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 a bit confused... If the RemoveExpiredCommand
is fired async, why would it need to skip locking? Also, does available
mean true
here?
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 needs to skip locking, because it has to remove the expired entry and generate the event before the current write can occur. The current write has the lock, so the async remove expired can't acquire locks. It waits for the remove to complete causing them to be serialized and guarantees event ordering so the expiration event comes before the write event.
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.
Ok, but if we block the current write until the RemoveExpiredCommand
finishes running, I wouldn't really call it async, even if it does run on another thread.
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.
okay
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.
technically it is async from this method :)
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.
Sure, but that doesn't explain why it needs to skip locking :)
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.
Sure, it just was supposed to say required instead of available. The problem is that the write already has the lock as it mentioned. I can add another sentence stating that it could cause a deadlock.
I think the confusion is because the blocking is done by the caller. I will also add a note mentioning that when skipLocking
is true it is up to the caller to properly ensure that the lock is held for the entire duration.
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 reworded and changed variable names so it should be more clear now.
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.
Thanks, it's a little more detail than I wanted now, but still much clearer :)
handleLifespanExpireEntry(entry.getKey(), value, lifespan); | ||
// If the entry as found expired in memory during a write, that means we already have the lock for the | ||
// given entry. Thus we must skip locking and wait for the remove expired to complete before continuing | ||
// forward with the operation to let the event be raised properly. |
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.
Do we guarantee that expiration listeners are notified synchronously with the read/write that triggered the expiration, or does "the event be raised properly" mean something else?
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.
yes they are done in a serial manner, expiration event has to come before the write event.
@@ -40,6 +41,11 @@ protected Object visitDataWriteCommand(InvocationContext ctx, DataWriteCommand c | |||
return visitNonTxDataWriteCommand(ctx, command); | |||
} | |||
|
|||
@Override | |||
public Object visitRemoveExpiredCommand(InvocationContext ctx, RemoveExpiredCommand command) throws Throwable { | |||
return super.visitRemoveExpiredCommand(ctx, command); |
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.
Is this really needed?
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.
No :) Was just there to add a debug break point and forgot to remove.
51e4078
to
7627763
Compare
@@ -16,7 +16,7 @@ | |||
*/ | |||
public abstract class AbstractDataCommand implements DataCommand, SegmentSpecificCommand { | |||
protected Object key; | |||
private long flags; | |||
protected long flags; |
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.
No longer needed
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.
👍
if (distributionManager != null) { | ||
return distributionManager.getCacheTopology().getSegmentDistribution(segment).isPrimary(); | ||
} else { | ||
return isOwner; |
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.
Actually distributionManager == null
only in LOCAL
caches, so you can return true
here and remove the parameter.
efd0dae
to
f30f028
Compare
f30f028
to
4ad07b4
Compare
Integrated, thanks Will! |
https://issues.jboss.org/browse/ISPN-9511