private Object broadcastInvalidateForPrepare(InvocationContext rCtx, VisitableCommand rCommand, Object rv) throws Throwable { log.tracef( "Entering InvalidationInterceptor's prepare phase. Ctx flags are empty" ); // fetch the modifications before the transaction is committed (and thus removed from the txTable) TxInvocationContext txCtx = (TxInvocationContext) rCtx; if ( shouldInvokeRemoteTxCommand( txCtx ) ) { if ( txCtx.getTransaction() == null ) { throw new IllegalStateException( "We must have an associated transaction" ); } PrepareCommand prepareCmd = (PrepareCommand) rCommand; List<WriteCommand> mods = Arrays.asList( prepareCmd.getModifications() ); CompletionStage<Void> completion = broadcastInvalidateForPrepare(mods, txCtx); if (completion != null) { return asyncValue(completion); } else { return rv; } } else { log.tracef( "Nothing to invalidate - no modifications in the transaction." ); } return rv; }
private boolean isUpdatingKeyWithValue(VisitableCommand command, Object key, Object value) { if (command instanceof PutKeyValueCommand) { return key.equals(((PutKeyValueCommand) command).getKey()) && value.equals(((PutKeyValueCommand) command).getValue()); } else if (command instanceof RemoveCommand) { return key.equals(((RemoveCommand) command).getKey()); } else if (command instanceof ClearCommand) { return true; } else if (command instanceof WriteOnlyManyEntriesCommand) { InternalCacheValue icv = (InternalCacheValue) ((WriteOnlyManyEntriesCommand) command).getArguments().get(key); return Objects.equals(icv.getValue(), value); } else if (command instanceof PrepareCommand) { for (WriteCommand writeCommand : ((PrepareCommand) command).getModifications()) { if (isUpdatingKeyWithValue(writeCommand, key, value)) { return true; } } } return false; }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (ctx.isOriginLocal()) { // We can't wait to commit phase to remove the entry locally (invalidations are processed in 1pc // on remote nodes, so only local case matters here). The problem is that while the entry is locked // reads still can take place and we can read outdated collection after reading updated entity // owning this collection from DB; when this happens, the version lock on entity cannot protect // us against concurrent modification of the collection. Therefore, we need to remove the entry // here (even without lock!) and let possible update happen in commit phase. for (WriteCommand wc : command.getModifications()) { for (Object key : wc.getAffectedKeys()) { dataContainer.remove(key); } } } else { for (WriteCommand wc : command.getModifications()) { Collection<?> keys = wc.getAffectedKeys(); if (log.isTraceEnabled()) { log.tracef("Invalidating keys %s with lock owner %s", keys, ctx.getLockOwner()); } for (Object key : keys ) { putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key); } } } return invokeNext(ctx, command); }
@Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand cmd) throws Throwable { try { // first pass up chain return invokeNextInterceptor(ctx, cmd); } finally { if (!ctx.isOriginLocal() || watchLocal) { logCommand(cmd); for (WriteCommand mod : cmd.getModifications()) logCommand(mod); } } }
final WriteCommand[] writeCommands = command.getModifications(); final Object[] stateBeforePrepare = new Object[writeCommands.length];