/** * Algo: - we put the query into the execution pool. - after x ms, if we don't have a result, we * add the queries for the secondary replicas - we take the first answer - when done, we cancel * what's left. Cancelling means: - removing from the pool if the actual call was not started - * interrupting the call if it has started Client side, we need to take into account - a call is * not executed immediately after being put into the pool - a call is a thread. Let's not multiply * the number of thread by the number of replicas. Server side, if we can cancel when it's still * in the handler pool, it's much better, as a call can take some i/o. * * <p>Globally, the number of retries, timeout and so on still applies, but it's per replica, not * global. We continue until all retries are done, or all timeouts are exceeded. */ public synchronized Result call() throws DoNotRetryIOException, InterruptedIOException, RetriesExhaustedException { boolean isTargetReplicaSpecified = (get.getReplicaId() >= 0); RegionLocations rl = getRegionLocations( true, (isTargetReplicaSpecified ? get.getReplicaId() : RegionReplicaUtil.DEFAULT_REPLICA_ID), cConnection, tableName, get.getRow()); ResultBoundedCompletionService<Result> cs = new ResultBoundedCompletionService<Result>(this.rpcRetryingCallerFactory, pool, rl.size()); if (isTargetReplicaSpecified) { addCallsForReplica(cs, rl, get.getReplicaId(), get.getReplicaId()); } else { addCallsForReplica(cs, rl, 0, 0); try { // wait for the timeout to see whether the primary responds back Future<Result> f = cs.poll(timeBeforeReplicas, TimeUnit.MICROSECONDS); // Yes, microseconds if (f != null) { return f.get(); // great we got a response } } catch (ExecutionException e) { throwEnrichedException(e, retries); } catch (CancellationException e) { throw new InterruptedIOException(); } catch (InterruptedException e) { throw new InterruptedIOException(); } // submit call for the all of the secondaries at once addCallsForReplica(cs, rl, 1, rl.size() - 1); } try { try { Future<Result> f = cs.take(); return f.get(); } catch (ExecutionException e) { throwEnrichedException(e, retries); } } catch (CancellationException e) { throw new InterruptedIOException(); } catch (InterruptedException e) { throw new InterruptedIOException(); } finally { // We get there because we were interrupted or because one or more of the // calls succeeded or failed. In all case, we stop all our tasks. cs.cancelAll(); } return null; // unreachable }
/** * Creates the calls and submit them * * @param cs - the completion service to use for submitting * @param rl - the region locations * @param min - the id of the first replica, inclusive * @param max - the id of the last replica, inclusive. */ private void addCallsForReplica( ResultBoundedCompletionService<Result> cs, RegionLocations rl, int min, int max) { for (int id = min; id <= max; id++) { HRegionLocation hrl = rl.getRegionLocation(id); ReplicaRegionServerCallable callOnReplica = new ReplicaRegionServerCallable(id, hrl); cs.submit(callOnReplica, callTimeout, id); } }