package org.infinispan.manager.impl;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
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.JGroupsAddressCache;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
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 requests to a single node at a time
*
* @author wburns
* @since 9.1
*/
class SingleClusterExecutor extends AbstractClusterExecutor<SingleClusterExecutor> {
private static final Log log = LogFactory.getLog(SingleClusterExecutor.class);
private static final boolean isTrace = log.isTraceEnabled();
SingleClusterExecutor(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 SingleClusterExecutor sameClusterExecutor(Predicate<? super Address> predicate, long time, TimeUnit unit) {
return new SingleClusterExecutor(predicate, manager, transport, time, unit, localExecutor, timeoutExecutor);
}
private org.jgroups.Address findTarget() {
List<org.jgroups.Address> possibleTargets = getJGroupsTargets(true);
org.jgroups.Address target;
int size = possibleTargets.size();
if (size == 0) {
target = null;
} else if (size == 1) {
target = possibleTargets.get(0);
} else {
target = possibleTargets.get(ThreadLocalRandom.current().nextInt(size));
}
return target;
}
@Override
public void execute(Runnable runnable) {
org.jgroups.Address target = findTarget();
if (target != null) {
if (isTrace) {
log.tracef("Submitting runnable to single remote node - JGroups Address %s", target);
}
if (target == convertToJGroupsAddress(me)) {
// Interrupt does nothing
super.execute(runnable);
} else {
transport.getCommandAwareRpcDispatcher().invokeRemoteCommand(target, new ReplicableCommandRunnable(runnable),
ResponseMode.GET_NONE, unit.toMillis(time), DeliverOrder.NONE);
}
}
}
@Override
public CompletableFuture<Void> submit(Runnable command) {
org.jgroups.Address target = findTarget();
if (target == null) {
return CompletableFutures.completedExceptionFuture(new SuspectException("No available nodes!"));
}
if (isTrace) {
log.tracef("Submitting runnable to single remote node - JGroups Address %s", target);
}
CompletableFuture<Void> future = new CompletableFuture<>();
if (target == convertToJGroupsAddress(me)) {
return super.submit(command);
} else {
transport.getCommandAwareRpcDispatcher().invokeRemoteCommand(target, new ReplicableCommandRunnable(command),
ResponseMode.GET_FIRST, unit.toMillis(time), DeliverOrder.NONE).whenComplete((r, t) -> {
if (t != null) {
future.completeExceptionally(t);
} else {
consumeResponse(r, target, future::completeExceptionally);
future.complete(null);
}
});
}
return future;
}
@Override
public <V> CompletableFuture<Void> submitConsumer(Function<? super EmbeddedCacheManager, ? extends V> function,
TriConsumer<? super Address, ? super V, ? super Throwable> triConsumer) {
org.jgroups.Address target = findTarget();
if (target == null) {
return CompletableFutures.completedExceptionFuture(new SuspectException("No available nodes!"));
}
if (isTrace) {
log.tracef("Submitting runnable to single remote node - JGroups Address %s", target);
}
if (target == convertToJGroupsAddress(me)) {
return super.submitConsumer(function, triConsumer);
} else {
CompletableFuture<Void> future = new CompletableFuture<>();
transport.getCommandAwareRpcDispatcher().invokeRemoteCommand(target,
new ReplicableCommandManagerFunction(function), ResponseMode.GET_ALL, unit.toMillis(time),
DeliverOrder.NONE).whenComplete((r, t) -> {
try {
if (t != null) {
triConsumer.accept(JGroupsAddressCache.fromJGroupsAddress(target), null, t);
} else {
consumeResponse(r, target, v -> triConsumer.accept(JGroupsAddressCache.fromJGroupsAddress(target), (V) v, null),
throwable -> triConsumer.accept(JGroupsAddressCache.fromJGroupsAddress(target), null, throwable), future::completeExceptionally);
}
future.complete(null);
} catch (Throwable throwable) {
future.completeExceptionally(throwable);
}
});
return future;
}
}
@Override
public ClusterExecutor singleNodeSubmission() {
return this;
}
@Override
public ClusterExecutor singleNodeSubmission(int failOverCount) {
if (failOverCount == 0) {
return this;
}
return ClusterExecutors.singleNodeSubmissionExecutor(predicate, manager, transport, time, unit, localExecutor,
timeoutExecutor, failOverCount);
}
@Override
public ClusterExecutor allNodeSubmission() {
return ClusterExecutors.allSubmissionExecutor(predicate, manager, transport, time, unit, localExecutor,
timeoutExecutor);
}
}