package org.infinispan.util; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.infinispan.commands.ReplicableCommand; import org.infinispan.commons.marshall.StreamingMarshaller; import org.infinispan.factories.GlobalComponentRegistry; import org.infinispan.factories.KnownComponentNames; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.remoting.inboundhandler.DeliverOrder; import org.infinispan.remoting.inboundhandler.InboundInvocationHandler; import org.infinispan.remoting.transport.Transport; import org.infinispan.remoting.transport.jgroups.CommandAwareRpcDispatcher; import org.infinispan.remoting.transport.jgroups.JGroupsTransport; import org.infinispan.remoting.transport.jgroups.SingleResponseFuture; import org.infinispan.test.TestingUtil; import org.jgroups.Address; import org.jgroups.blocks.MessageDispatcher; import org.jgroups.blocks.ResponseMode; /** * Dispatcher that counts actually ongoing unicast RPCs. Its purpose is to isolate RPCs started before * {@link #advanceGenerationAndAwait(long, TimeUnit)} and those afterwards. It can handle staggered calls as well. */ public class CountingCARD extends CommandAwareRpcDispatcher { private final GenerationalScheduledThreadPoolExecutor timeoutExecutor; private int awaitingReponses; public static CountingCARD replaceDispatcher(EmbeddedCacheManager cacheManager) { GlobalComponentRegistry gcr = cacheManager.getGlobalComponentRegistry(); JGroupsTransport transport = (JGroupsTransport) gcr.getComponent(Transport.class); InboundInvocationHandler handler = gcr.getComponent(InboundInvocationHandler.class); GenerationalScheduledThreadPoolExecutor timeoutExecutor = new GenerationalScheduledThreadPoolExecutor(transport.getAddress().toString()); TimeService timeService = gcr.getComponent(TimeService.class); StreamingMarshaller marshaller = gcr.getComponent(StreamingMarshaller.class); Executor remoteExecutor = gcr.getComponent(KnownComponentNames.REMOTE_COMMAND_EXECUTOR); CountingCARD instance = new CountingCARD(transport, handler, timeoutExecutor, timeService, remoteExecutor, marshaller); TestingUtil.replaceField(instance, "dispatcher", transport, JGroupsTransport.class); return instance; } public CountingCARD(JGroupsTransport transport, InboundInvocationHandler handler, GenerationalScheduledThreadPoolExecutor timeoutExecutor, TimeService timeService, Executor remoteExecutor, StreamingMarshaller marshaller) { super(transport.getChannel(), transport, handler, timeoutExecutor, timeService, remoteExecutor, marshaller); installUpHandler(prot_adapter, true); // Make sure that this is the up handler this.timeoutExecutor = timeoutExecutor; start(); } @Override public <X extends MessageDispatcher> X stop() { // Because of a quirk of CommandAwareRpcDispatcher, stop() is also called during the constructor if (timeoutExecutor != null) { timeoutExecutor.shutdownNow(); } return super.stop(); } @Override protected SingleResponseFuture processSingleCall(ReplicableCommand command, long timeout, Address destination, ResponseMode mode, DeliverOrder deliverOrder) throws Exception { synchronized (this) { awaitingReponses++; } SingleResponseFuture srf = super.processSingleCall(command, timeout, destination, mode, deliverOrder); if (srf == null) { synchronized (this) { if (--awaitingReponses == 0) notifyAll(); } } else { srf.whenComplete((responseRsp, throwable) -> { synchronized (CountingCARD.this) { if (--awaitingReponses == 0) notifyAll(); } }); } return srf; } /** * Prohibit staggered calls started before call to this happen to execute, and wait until we get responses * for all already invoked RPCs. */ public void advanceGenerationAndAwait(long timeout, TimeUnit timeUnit) throws InterruptedException { timeoutExecutor.advanceGeneration(); long now = System.currentTimeMillis(); long deadline = now + timeUnit.toMillis(timeout); synchronized (this) { while (awaitingReponses > 0) { this.wait(deadline - now); now = System.currentTimeMillis(); } } } /** * Executor that records a 'generation' when the task is scheduled, and later, when the task is about * to be executed, checks this generation. If the generation has advanced through {@link #advanceGeneration()} * the task is silently ignored. */ static class GenerationalScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { private AtomicLong generation = new AtomicLong(); public GenerationalScheduledThreadPoolExecutor(String name) { super(1, r -> new Thread(r, "counting-timeout-thread-" + name)); setRemoveOnCancelPolicy(true); } @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { long taskGeneration = generation.get(); return super.schedule(() -> { if (taskGeneration < generation.get()) { return; } command.run(); }, delay, unit); } public void advanceGeneration() { generation.incrementAndGet(); } } }