package org.infinispan.client.hotrod.impl.operations; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import org.infinispan.client.hotrod.configuration.ClientIntelligence; import org.infinispan.client.hotrod.exceptions.ParallelOperationException; import org.infinispan.client.hotrod.impl.protocol.Codec; import org.infinispan.client.hotrod.impl.transport.TransportFactory; import org.infinispan.client.hotrod.logging.Log; import org.infinispan.client.hotrod.logging.LogFactory; /** * An HotRod operation that span across multiple remote nodes concurrently (like getAll / putAll). * * @author Guillaume Darmont / guillaume@dropinocean.com */ public abstract class ParallelHotRodOperation<T, SUBOP extends HotRodOperation> extends HotRodOperation { private static final Log log = LogFactory.getLog(ParallelHotRodOperation.class, Log.class); protected final TransportFactory transportFactory; protected final CompletionService<T> completionService; protected ParallelHotRodOperation(Codec codec, TransportFactory transportFactory, byte[] cacheName, AtomicInteger topologyId, int flags, ClientIntelligence clientIntelligence, ExecutorService executorService) { super(codec, flags, clientIntelligence, cacheName, topologyId); this.transportFactory = transportFactory; this.completionService = new ExecutorCompletionService<>(executorService); } @Override public T execute() { List<SUBOP> operations = mapOperations(); if (operations.isEmpty()) { return createCollector(); } else if (operations.size() == 1) { // Only one operation to do, we stay in the caller thread return executeSequential(operations.get(0)); } else { // Multiple operation, submit to the thread poll return executeParallel(operations); } } private T executeSequential(SUBOP subop) { T collector = createCollector(); combine(collector, (T) subop.execute()); return collector; } private T executeParallel(List<SUBOP> operations) { Set<Future<T>> remainingTasks = new HashSet<>(operations.size()); for (SUBOP operation : operations) { remainingTasks.add(completionService.submit(() -> (T) operation.execute())); } T collector = createCollector(); for (int i = 0; i < operations.size(); i++) { try { Future<T> result = completionService.take(); combine(collector, result.get()); remainingTasks.remove(result); } catch (InterruptedException e) { Thread.currentThread().interrupt(); cancelRemainingTasks(remainingTasks); throw new ParallelOperationException(e); } catch (ExecutionException | RuntimeException e) { cancelRemainingTasks(remainingTasks); throw new ParallelOperationException(e); } } return collector; } private void cancelRemainingTasks(Set<Future<T>> remainingTasks) { remainingTasks.forEach(task -> task.cancel(true)); } protected abstract List<SUBOP> mapOperations(); protected abstract T createCollector(); protected abstract void combine(T collector, T result); }