package org.infinispan.manager.impl; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; import org.infinispan.manager.ClusterExecutor; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.remoting.inboundhandler.DeliverOrder; import org.infinispan.remoting.transport.Address; import org.infinispan.remoting.transport.jgroups.CommandAwareRpcDispatcher; import org.infinispan.remoting.transport.jgroups.JGroupsAddressCache; import org.infinispan.remoting.transport.jgroups.JGroupsTransport; import org.infinispan.remoting.transport.jgroups.SingleResponseFuture; import org.infinispan.remoting.transport.jgroups.SuspectException; import org.infinispan.util.concurrent.CompletableFutures; import org.infinispan.util.function.TriConsumer; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.jgroups.blocks.ResponseMode; /** * Cluster executor implementation that sends a request to all available nodes * * @author wburns * @since 8.2 */ class AllClusterExecutor extends AbstractClusterExecutor<AllClusterExecutor> { private static final Log log = LogFactory.getLog(AllClusterExecutor.class); private static final boolean isTrace = log.isTraceEnabled(); AllClusterExecutor(Predicate<? super Address> predicate, EmbeddedCacheManager manager, JGroupsTransport transport, long time, TimeUnit unit, Executor localExecutor, ScheduledExecutorService timeoutExecutor) { super(predicate, manager, transport, time, unit, localExecutor, timeoutExecutor); } @Override public Log getLog() { return log; } @Override protected AllClusterExecutor sameClusterExecutor(Predicate<? super Address> predicate, long time, TimeUnit unit) { return new AllClusterExecutor(predicate, manager, transport, time, unit, localExecutor, timeoutExecutor); } private <T> CompletableFuture<Void> startLocalInvocation(Function<? super EmbeddedCacheManager, ? extends T> callable, TriConsumer<? super Address, ? super T, ? super Throwable> triConsumer) { if (me == null || predicate == null || predicate.test(me)) { if (isTrace) { log.trace("Submitting callable to local node on executor thread! - Usually remote command thread pool"); } return super.submitConsumer(callable, triConsumer); } else { return null; } } protected CompletableFuture<Void> startLocalInvocation(Runnable runnable) { if (me == null || predicate == null || predicate.test(me)) { if (isTrace) { log.trace("Submitting runnable to local node on executor thread! - Usually remote command thread pool"); } return super.submit(runnable); } else { return null; } } @Override public void execute(Runnable runnable) { executeRunnable(runnable, ResponseMode.GET_ALL); } private CompletableFuture<?> executeRunnable(Runnable runnable, ResponseMode mode) { CompletableFuture<?> localFuture = startLocalInvocation(runnable); List<org.jgroups.Address> targets = getJGroupsTargets(false); int size = targets.size(); CompletableFuture<?> remoteFuture; if (size == 1) { org.jgroups.Address target = targets.get(0); if (isTrace) { log.tracef("Submitting runnable to single remote node - JGroups Address %s", target); } CommandAwareRpcDispatcher card = transport.getCommandAwareRpcDispatcher(); remoteFuture = new CompletableFuture<>(); card.invokeRemoteCommand(target, new ReplicableCommandRunnable(runnable), mode, unit.toMillis(time), DeliverOrder.NONE).handle((r, t) -> { if (t != null) { remoteFuture.completeExceptionally(t); } else { consumeResponse(r, target, remoteFuture::completeExceptionally); // This won't override exception if there was one remoteFuture.complete(null); } return null; }); } else if (size > 1) { CommandAwareRpcDispatcher card = transport.getCommandAwareRpcDispatcher(); remoteFuture = new CompletableFuture<>(); card.invokeRemoteCommands(targets, new ReplicableCommandRunnable(runnable), mode, unit.toMillis(time), null, DeliverOrder.NONE).handle((r, t) -> { if (t != null) { remoteFuture.completeExceptionally(t); } else { r.forEach(e -> consumeResponse(e.getValue(), e.getKey(), remoteFuture::completeExceptionally)); remoteFuture.complete(null); } return null; }); } else if (localFuture != null) { return localFuture; } else { return CompletableFutures.completedExceptionFuture(new SuspectException("No available nodes!")); } // remoteFuture is guaranteed to be non null at this point if (localFuture != null && mode != ResponseMode.GET_NONE) { CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture.allOf(localFuture, remoteFuture).whenComplete((v, t) -> { if (t != null) { if (t instanceof CompletionException) { future.completeExceptionally(t.getCause()); } else { future.completeExceptionally(t); } } else { future.complete(null); } }); return future; } return remoteFuture; } @Override public CompletableFuture<Void> submit(Runnable command) { CompletableFuture<Void> future = new CompletableFuture<>(); executeRunnable(command, ResponseMode.GET_ALL).handle((r, t) -> { if (t != null) { future.completeExceptionally(t); } future.complete(null); return null; }); return future; } @Override public <V> CompletableFuture<Void> submitConsumer(Function<? super EmbeddedCacheManager, ? extends V> function, TriConsumer<? super Address, ? super V, ? super Throwable> triConsumer) { CompletableFuture<Void> localFuture = startLocalInvocation(function, triConsumer); List<org.jgroups.Address> targets = getJGroupsTargets(false); int size = targets.size(); if (size > 0) { CompletableFuture<?>[] futures = new CompletableFuture[size]; for (int i = 0; i < size; ++i) { CommandAwareRpcDispatcher card = transport.getCommandAwareRpcDispatcher(); org.jgroups.Address target = targets.get(i); if (isTrace) { log.tracef("Submitting consumer to single remote node - JGroups Address %s", target); } SingleResponseFuture srf = card.invokeRemoteCommand(target, new ReplicableCommandManagerFunction(function), ResponseMode.GET_ALL, unit.toMillis(time), DeliverOrder.NONE); futures[i] = srf.whenComplete((r, t) -> { if (t != null) { triConsumer.accept(JGroupsAddressCache.fromJGroupsAddress(target), null, t); } else { consumeResponse(r, target, v -> triConsumer.accept(JGroupsAddressCache.fromJGroupsAddress(target), (V) v, null), // TODO: check if obtrude works here? throwable -> triConsumer.accept(JGroupsAddressCache.fromJGroupsAddress(target), null, throwable), srf::obtrudeException); } }); } CompletableFuture<Void> resultFuture = new CompletableFuture<>(); CompletableFuture<Void> remoteFutures = CompletableFuture.allOf(futures); (localFuture != null ? localFuture.thenCombine(remoteFutures, (t, u) -> null) : remoteFutures).whenComplete((v, t) -> { if (t != null) { if (t instanceof CompletionException) { resultFuture.completeExceptionally(t.getCause()); } } else { resultFuture.complete(null); } }); return resultFuture; } else if (localFuture != null) { return localFuture; } else { return CompletableFutures.completedExceptionFuture(new SuspectException("No available nodes!")); } } @Override public ClusterExecutor singleNodeSubmission() { return ClusterExecutors.singleNodeSubmissionExecutor(predicate, manager, transport, time, unit, localExecutor, timeoutExecutor, 0); } @Override public ClusterExecutor singleNodeSubmission(int failOverCount) { return ClusterExecutors.singleNodeSubmissionExecutor(predicate, manager, transport, time, unit, localExecutor, timeoutExecutor, failOverCount); } @Override public ClusterExecutor allNodeSubmission() { return this; } }