private void invalidateAcrossCluster(boolean synchronous, Object[] keys, InvocationContext ctx) throws Throwable { // increment invalidations counter if statistics maintained incrementInvalidations(); final InvalidateCommand invalidateCommand = commandsFactory.buildInvalidateCommand(InfinispanCollections.<Flag>emptySet(), keys); if (log.isDebugEnabled()) log.debug("Cache [" + rpcManager.getAddress() + "] replicating " + invalidateCommand); ReplicableCommand command = invalidateCommand; if (ctx.isInTxScope()) { TxInvocationContext txCtx = (TxInvocationContext) ctx; // A Prepare command containing the invalidation command in its 'modifications' list is sent // to the remote nodes // so that the invalidation is executed in the same transaction and locks can be acquired and // released properly. // This is 1PC on purpose, as an optimisation, even if the current TX is 2PC. // If the cache uses 2PC it's possible that the remotes will commit the invalidation and the // originator rolls back, // but this does not impact consistency and the speed benefit is worth it. command = commandsFactory.buildPrepareCommand( txCtx.getGlobalTransaction(), Collections.<WriteCommand>singletonList(invalidateCommand), true); } rpcManager.invokeRemotely(null, command, rpcManager.getDefaultRpcOptions(synchronous)); }
private static XSiteStatePushCommand newStatePushCommand( Cache<?, ?> cache, List<XSiteState> stateList) { CommandsFactory commandsFactory = cache.getAdvancedCache().getComponentRegistry().getCommandsFactory(); return commandsFactory.buildXSiteStatePushCommand( stateList.toArray(new XSiteState[stateList.size()])); }
public void commit(Xid xid, boolean isOnePhase) throws XAException { // always call prepare() - even if this is just a 1PC! if (isOnePhase) prepare(xid); if (trace) log.trace("committing TransactionXaAdapter: " + globalTx); try { LocalTxInvocationContext ctx = icc.createTxInvocationContext(); ctx.setXaCache(this); if (configuration.isOnePhaseCommit()) { checkMarkedForRollback(); if (trace) log.trace("Doing an 1PC prepare call on the interceptor chain"); PrepareCommand command = commandsFactory.buildPrepareCommand(globalTx, modifications, true); try { invoker.invoke(ctx, command); } catch (Throwable e) { log.error("Error while processing 1PC PrepareCommand", e); throw new XAException(XAException.XAER_RMERR); } } else { CommitCommand commitCommand = commandsFactory.buildCommitCommand(globalTx); try { invoker.invoke(ctx, commitCommand); } catch (Throwable e) { log.error("Error while processing 1PC PrepareCommand", e); throw new XAException(XAException.XAER_RMERR); } } } finally { txTable.removeLocalTransaction(transaction); this.modifications = null; } }
@Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { if (ctx.getCacheTransaction() instanceof RemoteTransaction) { // If a commit is received for a transaction that doesn't have its 'lookedUpEntries' populated // we know for sure this transaction is 2PC and was received via state transfer but the // preceding PrepareCommand // was not received by local node because it was executed on the previous key owners. We need // to re-prepare // the transaction on local node to ensure its locks are acquired and lookedUpEntries is // properly populated. RemoteTransaction remoteTx = (RemoteTransaction) ctx.getCacheTransaction(); if (remoteTx.isMissingLookedUpEntries()) { remoteTx.setMissingLookedUpEntries(false); PrepareCommand prepareCommand; if (useVersioning) { prepareCommand = commandFactory.buildVersionedPrepareCommand( ctx.getGlobalTransaction(), ctx.getModifications(), false); } else { prepareCommand = commandFactory.buildPrepareCommand( ctx.getGlobalTransaction(), ctx.getModifications(), false); } commandFactory.initializeReplicableCommand(prepareCommand, true); prepareCommand.setOrigin(ctx.getOrigin()); log.tracef( "Replaying the transactions received as a result of state transfer %s", prepareCommand); prepareCommand.perform(null); } } return handleTxCommand(ctx, command); }
public NotifyingNotifiableFuture<Object> flushCache( Collection<Object> keys, Object retval, Address origin) { if (trace) log.tracef("Invalidating L1 caches for keys %s", keys); NotifyingNotifiableFuture<Object> future = new AggregatingNotifyingFutureImpl(retval, 2); Collection<Address> invalidationAddresses = buildInvalidationAddressList(keys, origin); int nodes = invalidationAddresses.size(); if (nodes > 0) { // No need to invalidate at all if there is no one to invalidate! boolean multicast = isUseMulticast(nodes); if (trace) log.tracef( "There are %s nodes involved in invalidation. Threshold is: %s; using multicast: %s", nodes, threshold, multicast); if (multicast) { if (trace) log.tracef("Invalidating keys %s via multicast", keys); InvalidateCommand ic = commandsFactory.buildInvalidateFromL1Command(origin, false, keys); rpcManager.broadcastRpcCommandInFuture(ic, future); } else { InvalidateCommand ic = commandsFactory.buildInvalidateFromL1Command(origin, false, keys); // Ask the caches who have requested from us to remove if (trace) log.tracef("Keys %s needs invalidation on %s", keys, invalidationAddresses); rpcManager.invokeRemotelyInFuture( invalidationAddresses, ic, true, future, rpcTimeout, true); return future; } } else if (trace) log.trace("No L1 caches to invalidate"); return future; }
private List<TransactionInfo> getTransactions( Address source, Set<Integer> segments, int topologyId) { if (trace) { log.tracef( "Requesting transactions for segments %s of cache %s from node %s", segments, cacheName, source); } // get transactions and locks try { StateRequestCommand cmd = commandsFactory.buildStateRequestCommand( StateRequestCommand.Type.GET_TRANSACTIONS, rpcManager.getAddress(), topologyId, segments); Map<Address, Response> responses = rpcManager.invokeRemotely( Collections.singleton(source), cmd, ResponseMode.SYNCHRONOUS_IGNORE_LEAVERS, timeout); Response response = responses.get(source); if (response instanceof SuccessfulResponse) { return (List<TransactionInfo>) ((SuccessfulResponse) response).getResponseValue(); } log.failedToRetrieveTransactionsForSegments(segments, cacheName, source, null); } catch (CacheException e) { log.failedToRetrieveTransactionsForSegments(segments, cacheName, source, e); } return null; }
@SuppressWarnings("unchecked") final V get(Object key, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextForRead(null, explicitFlags, explicitClassLoader); GetKeyValueCommand command = commandsFactory.buildGetKeyValueCommand(key, ctx.getFlags()); return (V) invoker.invoke(ctx, command); }
public InternalCacheEntry retrieveFromRemoteSource(Object key, InvocationContext ctx) throws Exception { ClusteredGetCommand get = cf.buildClusteredGetCommand(key, ctx.getFlags()); List<Address> targets = locate(key); targets.remove(getSelf()); ResponseFilter filter = new ClusteredGetResponseValidityFilter(targets); Map<Address, Response> responses = rpcManager.invokeRemotely( targets, get, ResponseMode.SYNCHRONOUS, configuration.getSyncReplTimeout(), false, filter); if (!responses.isEmpty()) { for (Response r : responses.values()) { if (r instanceof SuccessfulResponse) { InternalCacheValue cacheValue = (InternalCacheValue) ((SuccessfulResponse) r).getResponseValue(); return cacheValue.toInternalCacheEntry(key); } } } return null; }
protected Object invalidateAcrossCluster( boolean synchronous, InvocationContext ctx, Object[] keys, boolean useFuture, final Object retvalForFuture) throws Throwable { if (!isLocalModeForced(ctx)) { // increment invalidations counter if statistics maintained incrementInvalidations(); final InvalidateCommand command = commandsFactory.buildInvalidateCommand(keys); if (log.isDebugEnabled()) log.debug("Cache [" + rpcManager.getTransport().getAddress() + "] replicating " + command); // voila, invalidated! if (useFuture) { NotifyingNotifiableFuture<Object> future = new NotifyingFutureImpl(retvalForFuture); rpcManager.broadcastRpcCommandInFuture(command, future); return future; } else { rpcManager.broadcastRpcCommand(command, synchronous, false); } } return retvalForFuture; }
@SuppressWarnings("unchecked") final V remove(Object key, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); RemoveCommand command = commandsFactory.buildRemoveCommand(key, null, ctx.getFlags()); return (V) executeCommandAndCommitIfNeeded(ctx, command); }
final boolean containsKey( Object key, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextForRead(null, explicitFlags, explicitClassLoader); GetKeyValueCommand command = commandsFactory.buildGetKeyValueCommand(key, ctx.getFlags()); Object response = invoker.invoke(ctx, command); return response != null; }
final boolean remove( Object key, Object value, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); RemoveCommand command = commandsFactory.buildRemoveCommand(key, value, ctx.getFlags()); return (Boolean) executeCommandAndCommitIfNeeded(ctx, command); }
final NotifyingFuture<Void> clearAsync( EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); ctx.setUseFutureReturnType(true); ClearCommand command = commandsFactory.buildClearCommand(ctx.getFlags()); return wrapInFuture(executeCommandAndCommitIfNeeded(ctx, command)); }
private void submitAsyncTasks( String id, Map<Address, Set<Integer>> targets, Map<Integer, Set<K>> keysToExclude, boolean parallelStream, Set<K> keysToInclude, boolean includeLoader, StreamRequestCommand.Type type, Object operation) { for (Map.Entry<Address, Set<Integer>> targetInfo : targets.entrySet()) { Set<Integer> segments = targetInfo.getValue(); Set<K> keysExcluded = determineExcludedKeys(keysToExclude, segments); Address dest = targetInfo.getKey(); log.tracef("Submitting async task to %s for %s excluding keys %s", dest, id, keysExcluded); CompletableFuture<Map<Address, Response>> completableFuture = rpc.invokeRemotelyAsync( Collections.singleton(dest), factory.buildStreamRequestCommand( id, parallelStream, type, segments, keysToInclude, keysExcluded, includeLoader, operation), rpc.getDefaultRpcOptions(true)); completableFuture.whenComplete( (v, e) -> { if (v != null) { Response response = v.values().iterator().next(); if (!response.isSuccessful()) { log.tracef( "Unsuccessful response for %s from %s - making segments suspect", id, targetInfo.getKey()); receiveResponse(id, targetInfo.getKey(), true, targetInfo.getValue(), null); } } else if (e != null) { boolean wasSuspect = containedSuspectException(e); if (!wasSuspect) { log.tracef(e, "Encounted exception for %s from %s", id, targetInfo.getKey()); RequestTracker tracker = currentlyRunning.get(id); if (tracker != null) { markTrackerWithException(tracker, dest, e, id); } else { log.warnf("Unhandled remote stream exception encountered", e); } } else { log.tracef( "Exception contained a SuspectException, making all segments %s suspect", targetInfo.getValue()); receiveResponse(id, targetInfo.getKey(), true, targetInfo.getValue(), null); } } }); } }
public void testGroupRequestSentToMemberAfterLeaving() { EmbeddedCacheManager cm1 = null, cm2 = null, cm3 = null; try { ConfigurationBuilder c = new ConfigurationBuilder(); c.clustering().cacheMode(CacheMode.DIST_SYNC).hash().numOwners(3); cm1 = TestCacheManagerFactory.createClusteredCacheManager(c); cm2 = TestCacheManagerFactory.createClusteredCacheManager(c); cm3 = TestCacheManagerFactory.createClusteredCacheManager(c); Cache<Object, Object> c1 = cm1.getCache(); Cache<Object, Object> c2 = cm2.getCache(); Cache<Object, Object> c3 = cm3.getCache(); TestingUtil.blockUntilViewsReceived(30000, c1, c2, c3); c2.put("k", "v1"); RpcManager rpcManager = TestingUtil.extractComponent(c1, RpcManager.class); Collection<Address> addresses = cm1.getMembers(); CommandsFactory cf = TestingUtil.extractCommandsFactory(c1); PutKeyValueCommand cmd = cf.buildPutKeyValueCommand( "k", "v2", new EmbeddedMetadata.Builder().build(), EnumUtil.EMPTY_BIT_SET); Map<Address, Response> responseMap = rpcManager.invokeRemotely( addresses, cmd, rpcManager.getDefaultRpcOptions(true, DeliverOrder.NONE)); assert responseMap.size() == 2; TestingUtil.killCacheManagers(cm2); TestingUtil.blockUntilViewsReceived(30000, false, c1, c3); try { rpcManager.invokeRemotely( addresses, cmd, rpcManager.getDefaultRpcOptions(true, DeliverOrder.NONE)); assert false : "invokeRemotely should have thrown an exception"; } catch (SuspectException e) { // expected } } finally { TestingUtil.killCacheManagers(cm1, cm2, cm3); } }
@Override public void markNodePushCompleted(int viewId, Address node) throws InterruptedException { waitForJoinToStart(); if (trace) log.tracef( "Coordinator: received push completed notification for %s, view id %s", node, viewId); // ignore all push confirmations for view ids smaller than our view id if (viewId < lastViewId) { if (log.isTraceEnabled()) log.tracef( "Coordinator: Ignoring old push completed confirmation for view %d, last view is %d", viewId, lastViewId); return; } synchronized (pushConfirmations) { if (viewId < lastViewIdFromPushConfirmation) { if (trace) log.tracef( "Coordinator: Ignoring old push completed confirmation for view %d, last confirmed view is %d", viewId, lastViewIdFromPushConfirmation); return; } // update the latest received view id if necessary if (viewId > lastViewIdFromPushConfirmation) { lastViewIdFromPushConfirmation = viewId; } pushConfirmations.put(node, viewId); if (trace) log.tracef("Coordinator: updated push confirmations map %s", pushConfirmations); // the view change listener ensures that all the member nodes have an entry in the map for (Map.Entry<Address, Integer> pushNode : pushConfirmations.entrySet()) { if (pushNode.getValue() < viewId) { return; } } if (trace) log.tracef( "Coordinator: sending rehash completed notification for view %d, lastView %d, notifications received: %s", viewId, lastViewId, pushConfirmations); // all the nodes are up-to-date, broadcast the rehash completed command final RehashControlCommand cmd = cf.buildRehashControlCommand( RehashControlCommand.Type.REHASH_COMPLETED, getSelf(), viewId); // all nodes will eventually receive the command, no need to wait here rpcManager.broadcastRpcCommand(cmd, false); // The broadcast doesn't send the message to the local node markRehashCompleted(viewId); } }
final NotifyingFuture<Boolean> removeAsync( Object key, Object value, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); ctx.setUseFutureReturnType(true); RemoveCommand command = commandsFactory.buildRemoveCommand(key, value, ctx.getFlags()); return wrapInFuture(executeCommandAndCommitIfNeeded(ctx, command)); }
boolean lock( Collection<? extends K> keys, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { if (keys == null || keys.isEmpty()) { throw new IllegalArgumentException("Cannot lock empty list of keys"); } InvocationContext ctx = getInvocationContextForWrite(explicitFlags, explicitClassLoader); LockControlCommand command = commandsFactory.buildLockControlCommand(keys, false, ctx.getFlags()); return (Boolean) invoker.invoke(ctx, command); }
private Object realRemoteGet( InvocationContext ctx, Object key, boolean storeInL1, boolean isWrite) throws Throwable { if (trace) log.tracef("Doing a remote get for key %s", key); boolean acquireRemoteLock = false; if (ctx.isInTxScope()) { TxInvocationContext txContext = (TxInvocationContext) ctx; acquireRemoteLock = isWrite && isPessimisticCache && !txContext.getAffectedKeys().contains(key); } // attempt a remote lookup InternalCacheEntry ice = dm.retrieveFromRemoteSource(key, ctx, acquireRemoteLock); if (acquireRemoteLock) { ((TxInvocationContext) ctx).addAffectedKey(key); } if (ice != null) { if (storeInL1) { if (isL1CacheEnabled) { if (trace) log.tracef("Caching remotely retrieved entry for key %s in L1", key); // This should be fail-safe try { long lifespan = ice.getLifespan() < 0 ? configuration.getL1Lifespan() : Math.min(ice.getLifespan(), configuration.getL1Lifespan()); PutKeyValueCommand put = cf.buildPutKeyValueCommand( ice.getKey(), ice.getValue(), lifespan, -1, ctx.getFlags()); lockAndWrap(ctx, key, ice); invokeNextInterceptor(ctx, put); } catch (Exception e) { // Couldn't store in L1 for some reason. But don't fail the transaction! log.infof("Unable to store entry %s in L1 cache", key); log.debug("Inability to store in L1 caused by", e); } } else { CacheEntry ce = ctx.lookupEntry(key); if (ce == null || ce.isNull() || ce.isLockPlaceholder() || ce.getValue() == null) { if (ce != null && ce.isChanged()) { ce.setValue(ice.getValue()); } else { if (isWrite) lockAndWrap(ctx, key, ice); else ctx.putLookedUpEntry(key, ice); } } } } else { if (trace) log.tracef("Not caching remotely retrieved entry for key %s in L1", key); } return ice.getValue(); } return null; }
private <R> Object commonRemoteStreamOperation( boolean parallelDistribution, boolean parallelStream, ConsistentHash ch, Set<Integer> segments, Set<K> keysToInclude, Map<Integer, Set<K>> keysToExclude, boolean includeLoader, SegmentAwareOperation operation, ResultsCallback<R> callback, StreamRequestCommand.Type type, Predicate<? super R> earlyTerminatePredicate) { Map<Address, Set<Integer>> targets = determineTargets(ch, segments); String id; if (!targets.isEmpty()) { id = localAddress.toString() + requestId.getAndIncrement(); log.tracef("Performing remote operations %s for id %s", targets, id); RequestTracker<R> tracker = new RequestTracker<>(callback, targets, earlyTerminatePredicate); currentlyRunning.put(id, tracker); if (parallelDistribution) { submitAsyncTasks( id, targets, keysToExclude, parallelStream, keysToInclude, includeLoader, type, operation); } else { for (Map.Entry<Address, Set<Integer>> targetInfo : targets.entrySet()) { // TODO: what if this throws exception? Set<Integer> targetSegments = targetInfo.getValue(); Set<K> keysExcluded = determineExcludedKeys(keysToExclude, targetSegments); rpc.invokeRemotely( Collections.singleton(targetInfo.getKey()), factory.buildStreamRequestCommand( id, parallelStream, type, targetSegments, keysToInclude, keysExcluded, includeLoader, operation), rpc.getDefaultRpcOptions(true)); } } } else { log.tracef("Not performing remote operation for request as no valid targets found"); id = null; } return id; }
@Override public VersionedPutKeyValueCommand buildVersionedPutKeyValueCommand( Object key, Object value, long lifespanMillis, long maxIdleTimeMillis, EntryVersion version, Set<Flag> flags) { return actual.buildVersionedPutKeyValueCommand( key, value, lifespanMillis, maxIdleTimeMillis, version, flags); }
@Override public ReplaceCommand buildReplaceCommand( Object key, Object oldValue, Object newValue, long lifespanMillis, long maxIdleTimeMillis, Set<Flag> flags) { return actual.buildReplaceCommand( key, oldValue, newValue, lifespanMillis, maxIdleTimeMillis, flags); }
public void applyRemoteTxLog(List<WriteCommand> commands) { for (WriteCommand cmd : commands) { try { // this is a remotely originating tx cf.initializeReplicableCommand(cmd, true); InvocationContext ctx = icc.createInvocationContext(); ctx.setFlags(SKIP_REMOTE_LOOKUP, CACHE_MODE_LOCAL, SKIP_SHARED_CACHE_STORE, SKIP_LOCKING); interceptorChain.invoke(ctx, cmd); } catch (Exception e) { log.exceptionWhenReplaying(cmd, e); } } }
private void removeTransactionInfoRemotely( LocalTransaction localTransaction, GlobalTransaction gtx) { if (mayHaveRemoteLocks(localTransaction) && !isSecondPhaseAsync) { final TxCompletionNotificationCommand command = commandsFactory.buildTxCompletionNotificationCommand(null, gtx); final Collection<Address> owners = clusteringLogic.getOwners(localTransaction.getAffectedKeys()); Collection<Address> commitNodes = localTransaction.getCommitNodes( owners, rpcManager.getTopologyId(), rpcManager.getMembers()); log.tracef("About to invoke tx completion notification on commitNodes: %s", commitNodes); rpcManager.invokeRemotely(commitNodes, command, false, true); } }
public void rollback(Xid xid) throws XAException { RollbackCommand rollbackCommand = commandsFactory.buildRollbackCommand(globalTx); LocalTxInvocationContext ctx = icc.createTxInvocationContext(); ctx.setXaCache(this); try { invoker.invoke(ctx, rollbackCommand); } catch (Throwable e) { log.error("Exception while rollback", e); throw new XAException(XAException.XA_HEURHAZ); } finally { txTable.removeLocalTransaction(transaction); this.modifications = null; } }
final NotifyingFuture<Void> putAllAsync( Map<? extends K, ? extends V> data, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeysNotNull(data); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); ctx.setUseFutureReturnType(true); PutMapCommand command = commandsFactory.buildPutMapCommand( data, lifespanUnit.toMillis(lifespan), maxIdleUnit.toMillis(maxIdle), ctx.getFlags()); return wrapInFuture(executeCommandAndCommitIfNeeded(ctx, command)); }
@Override public void initializeReplicableCommand(ReplicableCommand command, boolean isRemote) { log.tracef("Received command %s", command); receivedCommands.add(command); if (isRemote) { remoteCommandsReceived.incrementAndGet(); if (toBlock != null && command.getClass().isAssignableFrom(toBlock)) { blockTypeCommandsReceived.incrementAndGet(); try { gate.await(); log.tracef("gate is opened, processing the lock cleanup: %s", command); } catch (InterruptedException e) { throw new RuntimeException(e); } } } actual.initializeReplicableCommand(command, isRemote); }
final void putAll( Map<? extends K, ? extends V> map, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeysNotNull(map); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); PutMapCommand command = commandsFactory.buildPutMapCommand( map, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime), ctx.getFlags()); executeCommandAndCommitIfNeeded(ctx, command); }
@SuppressWarnings("unchecked") final V put( K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit idleTimeUnit, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); PutKeyValueCommand command = commandsFactory.buildPutKeyValueCommand( key, value, lifespanUnit.toMillis(lifespan), idleTimeUnit.toMillis(maxIdleTime), ctx.getFlags()); return (V) executeCommandAndCommitIfNeeded(ctx, command); }
final NotifyingFuture<V> replaceAsync( K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit, EnumSet<Flag> explicitFlags, ClassLoader explicitClassLoader) { assertKeyNotNull(key); InvocationContext ctx = getInvocationContextWithImplicitTransaction(explicitFlags, explicitClassLoader); ctx.setUseFutureReturnType(true); ReplaceCommand command = commandsFactory.buildReplaceCommand( key, null, value, lifespanUnit.toMillis(lifespan), maxIdleUnit.toMillis(maxIdle), ctx.getFlags()); return wrapInFuture(executeCommandAndCommitIfNeeded(ctx, command)); }