View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.client;
21  
22  import java.io.IOException;
23  import java.io.InterruptedIOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Date;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.ConcurrentMap;
35  import java.util.concurrent.ConcurrentSkipListMap;
36  import java.util.concurrent.ExecutorService;
37  import java.util.concurrent.RejectedExecutionException;
38  import java.util.concurrent.TimeUnit;
39  import java.util.concurrent.atomic.AtomicInteger;
40  import java.util.concurrent.atomic.AtomicLong;
41  
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.apache.hadoop.hbase.RetryImmediatelyException;
45  import org.apache.hadoop.hbase.classification.InterfaceAudience;
46  import org.apache.hadoop.conf.Configuration;
47  import org.apache.hadoop.hbase.DoNotRetryIOException;
48  import org.apache.hadoop.hbase.HConstants;
49  import org.apache.hadoop.hbase.HRegionInfo;
50  import org.apache.hadoop.hbase.HRegionLocation;
51  import org.apache.hadoop.hbase.RegionLocations;
52  import org.apache.hadoop.hbase.ServerName;
53  import org.apache.hadoop.hbase.TableName;
54  import org.apache.hadoop.hbase.client.backoff.ServerStatistics;
55  import org.apache.hadoop.hbase.client.coprocessor.Batch;
56  import org.apache.hadoop.hbase.exceptions.ClientExceptionsUtil;
57  import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
58  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
59  import org.apache.hadoop.hbase.util.Bytes;
60  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
61  import org.apache.htrace.Trace;
62  
63  import com.google.common.annotations.VisibleForTesting;
64  
65  /**
66   * This class  allows a continuous flow of requests. It's written to be compatible with a
67   * synchronous caller such as HTable.
68   * <p>
69   * The caller sends a buffer of operation, by calling submit. This class extract from this list
70   * the operations it can send, i.e. the operations that are on region that are not considered
71   * as busy. The process is asynchronous, i.e. it returns immediately when if has finished to
72   * iterate on the list. If, and only if, the maximum number of current task is reached, the call
73   * to submit will block. Alternatively, the caller can call submitAll, in which case all the
74   * operations will be sent. Each call to submit returns a future-like object that can be used
75   * to track operation progress.
76   * </p>
77   * <p>
78   * The class manages internally the retries.
79   * </p>
80   * <p>
81   * The class can be constructed in regular mode, or "global error" mode. In global error mode,
82   * AP tracks errors across all calls (each "future" also has global view of all errors). That
83   * mode is necessary for backward compat with HTable behavior, where multiple submissions are
84   * made and the errors can propagate using any put/flush call, from previous calls.
85   * In "regular" mode, the errors are tracked inside the Future object that is returned.
86   * The results are always tracked inside the Future object and can be retrieved when the call
87   * has finished. Partial results can also be retrieved if some part of multi-request failed.
88   * </p>
89   * <p>
90   * This class is thread safe in regular mode; in global error code, submitting operations and
91   * retrieving errors from different threads may be not thread safe.
92   * Internally, the class is thread safe enough to manage simultaneously new submission and results
93   * arising from older operations.
94   * </p>
95   * <p>
96   * Internally, this class works with {@link Row}, this mean it could be theoretically used for
97   * gets as well.
98   * </p>
99   */
100 @InterfaceAudience.Private
101 class AsyncProcess {
102   private static final Log LOG = LogFactory.getLog(AsyncProcess.class);
103   protected static final AtomicLong COUNTER = new AtomicLong();
104 
105   public static final String PRIMARY_CALL_TIMEOUT_KEY = "hbase.client.primaryCallTimeout.multiget";
106 
107   /**
108    * Configure the number of failures after which the client will start logging. A few failures
109    * is fine: region moved, then is not opened, then is overloaded. We try to have an acceptable
110    * heuristic for the number of errors we don't log. 9 was chosen because we wait for 1s at
111    * this stage.
112    */
113   public static final String START_LOG_ERRORS_AFTER_COUNT_KEY =
114       "hbase.client.start.log.errors.counter";
115   public static final int DEFAULT_START_LOG_ERRORS_AFTER_COUNT = 9;
116 
117   /**
118    * Configuration to decide whether to log details for batch error
119    */
120   public static final String LOG_DETAILS_FOR_BATCH_ERROR = "hbase.client.log.batcherrors.details";
121 
122   private final int thresholdToLogUndoneTaskDetails;
123   private static final String THRESHOLD_TO_LOG_UNDONE_TASK_DETAILS =
124       "hbase.client.threshold.log.details";
125   private static final int DEFAULT_THRESHOLD_TO_LOG_UNDONE_TASK_DETAILS = 10;
126   private final int THRESHOLD_TO_LOG_REGION_DETAILS = 2;
127 
128   /**
129    * The context used to wait for results from one submit call.
130    * 1) If AsyncProcess is set to track errors globally, and not per call (for HTable puts),
131    *    then errors and failed operations in this object will reflect global errors.
132    * 2) If submit call is made with needResults false, results will not be saved.
133    *  */
134   public static interface AsyncRequestFuture {
135     public boolean hasError();
136     public RetriesExhaustedWithDetailsException getErrors();
137     public List<? extends Row> getFailedOperations();
138     public Object[] getResults() throws InterruptedIOException;
139     /** Wait until all tasks are executed, successfully or not. */
140     public void waitUntilDone() throws InterruptedIOException;
141   }
142 
143   /**
144    * Return value from a submit that didn't contain any requests.
145    */
146   private static final AsyncRequestFuture NO_REQS_RESULT = new AsyncRequestFuture() {
147 
148     final Object[] result = new Object[0];
149 
150     @Override
151     public boolean hasError() {
152       return false;
153     }
154 
155     @Override
156     public RetriesExhaustedWithDetailsException getErrors() {
157       return null;
158     }
159 
160     @Override
161     public List<? extends Row> getFailedOperations() {
162       return null;
163     }
164 
165     @Override
166     public Object[] getResults() {
167       return result;
168     }
169 
170     @Override
171     public void waitUntilDone() throws InterruptedIOException {
172     }
173   };
174 
175   /** Sync point for calls to multiple replicas for the same user request (Get).
176    * Created and put in the results array (we assume replica calls require results) when
177    * the replica calls are launched. See results for details of this process.
178    * POJO, all fields are public. To modify them, the object itself is locked. */
179   private static class ReplicaResultState {
180     public ReplicaResultState(int callCount) {
181       this.callCount = callCount;
182     }
183 
184     /** Number of calls outstanding, or 0 if a call succeeded (even with others outstanding). */
185     int callCount;
186     /** Errors for which it is not decided whether we will report them to user. If one of the
187      * calls succeeds, we will discard the errors that may have happened in the other calls. */
188     BatchErrors replicaErrors = null;
189 
190     @Override
191     public String toString() {
192       return "[call count " + callCount + "; errors " + replicaErrors + "]";
193     }
194   }
195 
196 
197   // TODO: many of the fields should be made private
198   protected final long id;
199 
200   protected final ClusterConnection connection;
201   protected final RpcRetryingCallerFactory rpcCallerFactory;
202   protected final RpcControllerFactory rpcFactory;
203   protected final BatchErrors globalErrors;
204   protected final ExecutorService pool;
205 
206   protected final AtomicLong tasksInProgress = new AtomicLong(0);
207   protected final ConcurrentMap<byte[], AtomicInteger> taskCounterPerRegion =
208       new ConcurrentSkipListMap<byte[], AtomicInteger>(Bytes.BYTES_COMPARATOR);
209   protected final ConcurrentMap<ServerName, AtomicInteger> taskCounterPerServer =
210       new ConcurrentHashMap<ServerName, AtomicInteger>();
211 
212   // Start configuration settings.
213   private final int startLogErrorsCnt;
214 
215   /**
216    * The number of tasks simultaneously executed on the cluster.
217    */
218   protected final int maxTotalConcurrentTasks;
219 
220   /**
221    * The number of tasks we run in parallel on a single region.
222    * With 1 (the default) , we ensure that the ordering of the queries is respected: we don't start
223    * a set of operations on a region before the previous one is done. As well, this limits
224    * the pressure we put on the region server.
225    */
226   protected final int maxConcurrentTasksPerRegion;
227 
228   /**
229    * The number of task simultaneously executed on a single region server.
230    */
231   protected final int maxConcurrentTasksPerServer;
232   protected final long pause;
233   protected int numTries;
234   protected int serverTrackerTimeout;
235   protected int rpcTimeout;
236   protected int operationTimeout;
237   protected long primaryCallTimeoutMicroseconds;
238   // End configuration settings.
239 
240   protected static class BatchErrors {
241     private final List<Throwable> throwables = new ArrayList<Throwable>();
242     private final List<Row> actions = new ArrayList<Row>();
243     private final List<String> addresses = new ArrayList<String>();
244 
245     public synchronized void add(Throwable ex, Row row, ServerName serverName) {
246       if (row == null){
247         throw new IllegalArgumentException("row cannot be null. location=" + serverName);
248       }
249 
250       throwables.add(ex);
251       actions.add(row);
252       addresses.add(serverName != null ? serverName.toString() : "null");
253     }
254 
255     public boolean hasErrors() {
256       return !throwables.isEmpty();
257     }
258 
259     private synchronized RetriesExhaustedWithDetailsException makeException() {
260       return new RetriesExhaustedWithDetailsException(
261           new ArrayList<Throwable>(throwables),
262           new ArrayList<Row>(actions), new ArrayList<String>(addresses));
263     }
264 
265     public synchronized void clear() {
266       throwables.clear();
267       actions.clear();
268       addresses.clear();
269     }
270 
271     public synchronized void merge(BatchErrors other) {
272       throwables.addAll(other.throwables);
273       actions.addAll(other.actions);
274       addresses.addAll(other.addresses);
275     }
276   }
277 
278   public AsyncProcess(ClusterConnection hc, Configuration conf, ExecutorService pool,
279       RpcRetryingCallerFactory rpcCaller, boolean useGlobalErrors, RpcControllerFactory rpcFactory,
280       int rpcTimeout) {
281     if (hc == null) {
282       throw new IllegalArgumentException("HConnection cannot be null.");
283     }
284 
285     this.connection = hc;
286     this.pool = pool;
287     this.globalErrors = useGlobalErrors ? new BatchErrors() : null;
288 
289     this.id = COUNTER.incrementAndGet();
290 
291     this.pause = conf.getLong(HConstants.HBASE_CLIENT_PAUSE,
292         HConstants.DEFAULT_HBASE_CLIENT_PAUSE);
293     this.numTries = conf.getInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER,
294         HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER);
295     this.rpcTimeout = rpcTimeout;
296     this.operationTimeout = conf.getInt(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT,
297         HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT);
298     this.primaryCallTimeoutMicroseconds = conf.getInt(PRIMARY_CALL_TIMEOUT_KEY, 10000);
299 
300     this.maxTotalConcurrentTasks = conf.getInt(HConstants.HBASE_CLIENT_MAX_TOTAL_TASKS,
301       HConstants.DEFAULT_HBASE_CLIENT_MAX_TOTAL_TASKS);
302     this.maxConcurrentTasksPerServer = conf.getInt(HConstants.HBASE_CLIENT_MAX_PERSERVER_TASKS,
303           HConstants.DEFAULT_HBASE_CLIENT_MAX_PERSERVER_TASKS);
304     this.maxConcurrentTasksPerRegion = conf.getInt(HConstants.HBASE_CLIENT_MAX_PERREGION_TASKS,
305           HConstants.DEFAULT_HBASE_CLIENT_MAX_PERREGION_TASKS);
306 
307     this.startLogErrorsCnt =
308         conf.getInt(START_LOG_ERRORS_AFTER_COUNT_KEY, DEFAULT_START_LOG_ERRORS_AFTER_COUNT);
309 
310     if (this.maxTotalConcurrentTasks <= 0) {
311       throw new IllegalArgumentException("maxTotalConcurrentTasks=" + maxTotalConcurrentTasks);
312     }
313     if (this.maxConcurrentTasksPerServer <= 0) {
314       throw new IllegalArgumentException("maxConcurrentTasksPerServer=" +
315           maxConcurrentTasksPerServer);
316     }
317     if (this.maxConcurrentTasksPerRegion <= 0) {
318       throw new IllegalArgumentException("maxConcurrentTasksPerRegion=" +
319           maxConcurrentTasksPerRegion);
320     }
321 
322     // Server tracker allows us to do faster, and yet useful (hopefully), retries.
323     // However, if we are too useful, we might fail very quickly due to retry count limit.
324     // To avoid this, we are going to cheat for now (see HBASE-7659), and calculate maximum
325     // retry time if normal retries were used. Then we will retry until this time runs out.
326     // If we keep hitting one server, the net effect will be the incremental backoff, and
327     // essentially the same number of retries as planned. If we have to do faster retries,
328     // we will do more retries in aggregate, but the user will be none the wiser.
329     this.serverTrackerTimeout = 0;
330     for (int i = 0; i < this.numTries; ++i) {
331       serverTrackerTimeout += ConnectionUtils.getPauseTime(this.pause, i);
332     }
333 
334     this.rpcCallerFactory = rpcCaller;
335     this.rpcFactory = rpcFactory;
336 
337     this.thresholdToLogUndoneTaskDetails =
338         conf.getInt(THRESHOLD_TO_LOG_UNDONE_TASK_DETAILS,
339           DEFAULT_THRESHOLD_TO_LOG_UNDONE_TASK_DETAILS);
340   }
341 
342   public void setRpcTimeout(int rpcTimeout) {
343     this.rpcTimeout = rpcTimeout;
344   }
345 
346   public void setOperationTimeout(int operationTimeout) {
347     this.operationTimeout = operationTimeout;
348   }
349 
350   /**
351    * @return pool if non null, otherwise returns this.pool if non null, otherwise throws
352    *         RuntimeException
353    */
354   private ExecutorService getPool(ExecutorService pool) {
355     if (pool != null) {
356       return pool;
357     }
358     if (this.pool != null) {
359       return this.pool;
360     }
361     throw new RuntimeException("Neither AsyncProcess nor request have ExecutorService");
362   }
363 
364   /**
365    * See {@link #submit(ExecutorService, TableName, List, boolean, org.apache.hadoop.hbase.client.coprocessor.Batch.Callback, boolean)}.
366    * Uses default ExecutorService for this AP (must have been created with one).
367    */
368   public <CResult> AsyncRequestFuture submit(TableName tableName, List<? extends Row> rows,
369       boolean atLeastOne, Batch.Callback<CResult> callback, boolean needResults)
370       throws InterruptedIOException {
371     return submit(null, tableName, rows, atLeastOne, callback, needResults);
372   }
373 
374   /**
375    * Extract from the rows list what we can submit. The rows we can not submit are kept in the
376    * list. Does not send requests to replicas (not currently used for anything other
377    * than streaming puts anyway).
378    *
379    * @param pool ExecutorService to use.
380    * @param tableName The table for which this request is needed.
381    * @param callback Batch callback. Only called on success (94 behavior).
382    * @param needResults Whether results are needed, or can be discarded.
383    * @param rows - the submitted row. Modified by the method: we remove the rows we took.
384    * @param atLeastOne true if we should submit at least a subset.
385    */
386   public <CResult> AsyncRequestFuture submit(ExecutorService pool, TableName tableName,
387       List<? extends Row> rows, boolean atLeastOne, Batch.Callback<CResult> callback,
388       boolean needResults) throws InterruptedIOException {
389     if (rows.isEmpty()) {
390       return NO_REQS_RESULT;
391     }
392 
393     Map<ServerName, MultiAction<Row>> actionsByServer =
394         new HashMap<ServerName, MultiAction<Row>>();
395     List<Action<Row>> retainedActions = new ArrayList<Action<Row>>(rows.size());
396 
397     NonceGenerator ng = this.connection.getNonceGenerator();
398     long nonceGroup = ng.getNonceGroup(); // Currently, nonce group is per entire client.
399 
400     // Location errors that happen before we decide what requests to take.
401     List<Exception> locationErrors = null;
402     List<Integer> locationErrorRows = null;
403     do {
404       // Wait until there is at least one slot for a new task.
405       waitForMaximumCurrentTasks(maxTotalConcurrentTasks - 1, tableName.getNameAsString());
406 
407       // Remember the previous decisions about regions or region servers we put in the
408       //  final multi.
409       Map<HRegionInfo, Boolean> regionIncluded = new HashMap<HRegionInfo, Boolean>();
410       Map<ServerName, Boolean> serverIncluded = new HashMap<ServerName, Boolean>();
411 
412       int posInList = -1;
413       Iterator<? extends Row> it = rows.iterator();
414       while (it.hasNext()) {
415         Row r = it.next();
416         HRegionLocation loc;
417         try {
418           if (r == null) {
419             throw new IllegalArgumentException("#" + id + ", row cannot be null");
420           }
421           // Make sure we get 0-s replica.
422           RegionLocations locs = connection.locateRegion(
423               tableName, r.getRow(), true, true, RegionReplicaUtil.DEFAULT_REPLICA_ID);
424           if (locs == null || locs.isEmpty() || locs.getDefaultRegionLocation() == null) {
425             throw new IOException("#" + id + ", no location found, aborting submit for"
426                 + " tableName=" + tableName + " rowkey=" + Bytes.toStringBinary(r.getRow()));
427           }
428           loc = locs.getDefaultRegionLocation();
429         } catch (IOException ex) {
430           locationErrors = new ArrayList<Exception>();
431           locationErrorRows = new ArrayList<Integer>();
432           LOG.error("Failed to get region location ", ex);
433           // This action failed before creating ars. Retain it, but do not add to submit list.
434           // We will then add it to ars in an already-failed state.
435           retainedActions.add(new Action<Row>(r, ++posInList));
436           locationErrors.add(ex);
437           locationErrorRows.add(posInList);
438           it.remove();
439           break; // Backward compat: we stop considering actions on location error.
440         }
441 
442         if (canTakeOperation(loc, regionIncluded, serverIncluded)) {
443           Action<Row> action = new Action<Row>(r, ++posInList);
444           setNonce(ng, r, action);
445           retainedActions.add(action);
446           // TODO: replica-get is not supported on this path
447           byte[] regionName = loc.getRegionInfo().getRegionName();
448           addAction(loc.getServerName(), regionName, action, actionsByServer, nonceGroup);
449           it.remove();
450         }
451       }
452     } while (retainedActions.isEmpty() && atLeastOne && (locationErrors == null));
453 
454     if (retainedActions.isEmpty()) return NO_REQS_RESULT;
455 
456     return submitMultiActions(tableName, retainedActions, nonceGroup, callback, null, needResults,
457         locationErrors, locationErrorRows, actionsByServer, pool);
458   }
459 
460   <CResult> AsyncRequestFuture submitMultiActions(TableName tableName,
461       List<Action<Row>> retainedActions, long nonceGroup, Batch.Callback<CResult> callback,
462       Object[] results, boolean needResults, List<Exception> locationErrors,
463       List<Integer> locationErrorRows, Map<ServerName, MultiAction<Row>> actionsByServer,
464       ExecutorService pool) {
465     AsyncRequestFutureImpl<CResult> ars = createAsyncRequestFuture(
466       tableName, retainedActions, nonceGroup, pool, callback, results, needResults);
467     // Add location errors if any
468     if (locationErrors != null) {
469       for (int i = 0; i < locationErrors.size(); ++i) {
470         int originalIndex = locationErrorRows.get(i);
471         Row row = retainedActions.get(originalIndex).getAction();
472         ars.manageError(originalIndex, row,
473             Retry.NO_LOCATION_PROBLEM, locationErrors.get(i), null);
474       }
475     }
476     ars.sendMultiAction(actionsByServer, 1, null, false);
477     return ars;
478   }
479 
480   /**
481    * Helper that is used when grouping the actions per region server.
482    *
483    * @param loc - the destination. Must not be null.
484    * @param action - the action to add to the multiaction
485    * @param actionsByServer the multiaction per server
486    * @param nonceGroup Nonce group.
487    */
488   private static void addAction(ServerName server, byte[] regionName, Action<Row> action,
489       Map<ServerName, MultiAction<Row>> actionsByServer, long nonceGroup) {
490     MultiAction<Row> multiAction = actionsByServer.get(server);
491     if (multiAction == null) {
492       multiAction = new MultiAction<Row>();
493       actionsByServer.put(server, multiAction);
494     }
495     if (action.hasNonce() && !multiAction.hasNonceGroup()) {
496       multiAction.setNonceGroup(nonceGroup);
497     }
498 
499     multiAction.add(regionName, action);
500   }
501 
502   /**
503    * Check if we should send new operations to this region or region server.
504    * We're taking into account the past decision; if we have already accepted
505    * operation on a given region, we accept all operations for this region.
506    *
507    * @param loc; the region and the server name we want to use.
508    * @return true if this region is considered as busy.
509    */
510   protected boolean canTakeOperation(HRegionLocation loc,
511                                      Map<HRegionInfo, Boolean> regionsIncluded,
512                                      Map<ServerName, Boolean> serversIncluded) {
513     HRegionInfo regionInfo = loc.getRegionInfo();
514     Boolean regionPrevious = regionsIncluded.get(regionInfo);
515 
516     if (regionPrevious != null) {
517       // We already know what to do with this region.
518       return regionPrevious;
519     }
520 
521     Boolean serverPrevious = serversIncluded.get(loc.getServerName());
522     if (Boolean.FALSE.equals(serverPrevious)) {
523       // It's a new region, on a region server that we have already excluded.
524       regionsIncluded.put(regionInfo, Boolean.FALSE);
525       return false;
526     }
527 
528     AtomicInteger regionCnt = taskCounterPerRegion.get(loc.getRegionInfo().getRegionName());
529     if (regionCnt != null && regionCnt.get() >= maxConcurrentTasksPerRegion) {
530       // Too many tasks on this region already.
531       regionsIncluded.put(regionInfo, Boolean.FALSE);
532       return false;
533     }
534 
535     if (serverPrevious == null) {
536       // The region is ok, but we need to decide for this region server.
537       int newServers = 0; // number of servers we're going to contact so far
538       for (Map.Entry<ServerName, Boolean> kv : serversIncluded.entrySet()) {
539         if (kv.getValue()) {
540           newServers++;
541         }
542       }
543 
544       // Do we have too many total tasks already?
545       boolean ok = (newServers + tasksInProgress.get()) < maxTotalConcurrentTasks;
546 
547       if (ok) {
548         // If the total is fine, is it ok for this individual server?
549         AtomicInteger serverCnt = taskCounterPerServer.get(loc.getServerName());
550         ok = (serverCnt == null || serverCnt.get() < maxConcurrentTasksPerServer);
551       }
552 
553       if (!ok) {
554         regionsIncluded.put(regionInfo, Boolean.FALSE);
555         serversIncluded.put(loc.getServerName(), Boolean.FALSE);
556         return false;
557       }
558 
559       serversIncluded.put(loc.getServerName(), Boolean.TRUE);
560     } else {
561       assert serverPrevious.equals(Boolean.TRUE);
562     }
563 
564     regionsIncluded.put(regionInfo, Boolean.TRUE);
565 
566     return true;
567   }
568 
569   /**
570    * See {@link #submitAll(ExecutorService, TableName, List, org.apache.hadoop.hbase.client.coprocessor.Batch.Callback, Object[])}.
571    * Uses default ExecutorService for this AP (must have been created with one).
572    */
573   public <CResult> AsyncRequestFuture submitAll(TableName tableName,
574       List<? extends Row> rows, Batch.Callback<CResult> callback, Object[] results) {
575     return submitAll(null, tableName, rows, callback, results, null, operationTimeout, rpcTimeout);
576   }
577 
578   public <CResult> AsyncRequestFuture submitAll(ExecutorService pool, TableName tableName,
579       List<? extends Row> rows, Batch.Callback<CResult> callback, Object[] results) {
580     return submitAll(pool, tableName, rows, callback, results, null, operationTimeout, rpcTimeout);
581   }
582   /**
583    * Submit immediately the list of rows, whatever the server status. Kept for backward
584    * compatibility: it allows to be used with the batch interface that return an array of objects.
585    *
586    * @param pool ExecutorService to use.
587    * @param tableName name of the table for which the submission is made.
588    * @param rows the list of rows.
589    * @param callback the callback.
590    * @param results Optional array to return the results thru; backward compat.
591    */
592   public <CResult> AsyncRequestFuture submitAll(ExecutorService pool, TableName tableName,
593       List<? extends Row> rows, Batch.Callback<CResult> callback, Object[] results,
594       PayloadCarryingServerCallable callable, int operationTimeout, int rpcTimeout) {
595     List<Action<Row>> actions = new ArrayList<Action<Row>>(rows.size());
596 
597     // The position will be used by the processBatch to match the object array returned.
598     int posInList = -1;
599     NonceGenerator ng = this.connection.getNonceGenerator();
600     for (Row r : rows) {
601       posInList++;
602       if (r instanceof Put) {
603         Put put = (Put) r;
604         if (put.isEmpty()) {
605           throw new IllegalArgumentException("No columns to insert for #" + (posInList+1)+ " item");
606         }
607       }
608       Action<Row> action = new Action<Row>(r, posInList);
609       setNonce(ng, r, action);
610       actions.add(action);
611     }
612     AsyncRequestFutureImpl<CResult> ars = createAsyncRequestFuture(
613         tableName, actions, ng.getNonceGroup(), getPool(pool), callback, results, results != null,
614         callable, operationTimeout, rpcTimeout);
615     ars.groupAndSendMultiAction(actions, 1);
616     return ars;
617   }
618 
619   private static void setNonce(NonceGenerator ng, Row r, Action<Row> action) {
620     if (!(r instanceof Append) && !(r instanceof Increment)) return;
621     action.setNonce(ng.newNonce()); // Action handles NO_NONCE, so it's ok if ng is disabled.
622   }
623 
624   /**
625    * The context, and return value, for a single submit/submitAll call.
626    * Note on how this class (one AP submit) works. Initially, all requests are split into groups
627    * by server; request is sent to each server in parallel; the RPC calls are not async so a
628    * thread per server is used. Every time some actions fail, regions/locations might have
629    * changed, so we re-group them by server and region again and send these groups in parallel
630    * too. The result, in case of retries, is a "tree" of threads, with parent exiting after
631    * scheduling children. This is why lots of code doesn't require any synchronization.
632    */
633   protected class AsyncRequestFutureImpl<CResult> implements AsyncRequestFuture {
634 
635     /**
636      * Runnable (that can be submitted to thread pool) that waits for when it's time
637      * to issue replica calls, finds region replicas, groups the requests by replica and
638      * issues the calls (on separate threads, via sendMultiAction).
639      * This is done on a separate thread because we don't want to wait on user thread for
640      * our asynchronous call, and usually we have to wait before making replica calls.
641      */
642     private final class ReplicaCallIssuingRunnable implements Runnable {
643       private final long startTime;
644       private final List<Action<Row>> initialActions;
645 
646       public ReplicaCallIssuingRunnable(List<Action<Row>> initialActions, long startTime) {
647         this.initialActions = initialActions;
648         this.startTime = startTime;
649       }
650 
651       @Override
652       public void run() {
653         boolean done = false;
654         if (primaryCallTimeoutMicroseconds > 0) {
655           try {
656             done = waitUntilDone(startTime * 1000L + primaryCallTimeoutMicroseconds);
657           } catch (InterruptedException ex) {
658             LOG.error("Replica thread was interrupted - no replica calls: " + ex.getMessage());
659             return;
660           }
661         }
662         if (done) return; // Done within primary timeout
663         Map<ServerName, MultiAction<Row>> actionsByServer =
664             new HashMap<ServerName, MultiAction<Row>>();
665         List<Action<Row>> unknownLocActions = new ArrayList<Action<Row>>();
666         if (replicaGetIndices == null) {
667           for (int i = 0; i < results.length; ++i) {
668             addReplicaActions(i, actionsByServer, unknownLocActions);
669           }
670         } else {
671           for (int replicaGetIndice : replicaGetIndices) {
672             addReplicaActions(replicaGetIndice, actionsByServer, unknownLocActions);
673           }
674         }
675         if (!actionsByServer.isEmpty()) {
676           sendMultiAction(actionsByServer, 1, null, unknownLocActions.isEmpty());
677         }
678         if (!unknownLocActions.isEmpty()) {
679           actionsByServer = new HashMap<ServerName, MultiAction<Row>>();
680           for (Action<Row> action : unknownLocActions) {
681             addReplicaActionsAgain(action, actionsByServer);
682           }
683           // Some actions may have completely failed, they are handled inside addAgain.
684           if (!actionsByServer.isEmpty()) {
685             sendMultiAction(actionsByServer, 1, null, true);
686           }
687         }
688       }
689 
690       /**
691        * Add replica actions to action map by server.
692        * @param index Index of the original action.
693        * @param actionsByServer The map by server to add it to.
694        */
695       private void addReplicaActions(int index, Map<ServerName, MultiAction<Row>> actionsByServer,
696           List<Action<Row>> unknownReplicaActions) {
697         if (results[index] != null) return; // opportunistic. Never goes from non-null to null.
698         Action<Row> action = initialActions.get(index);
699         RegionLocations loc = findAllLocationsOrFail(action, true);
700         if (loc == null) return;
701         HRegionLocation[] locs = loc.getRegionLocations();
702         if (locs.length == 1) {
703           LOG.warn("No replicas found for " + action.getAction());
704           return;
705         }
706         synchronized (replicaResultLock) {
707           // Don't run replica calls if the original has finished. We could do it e.g. if
708           // original has already failed before first replica call (unlikely given retries),
709           // but that would require additional synchronization w.r.t. returning to caller.
710           if (results[index] != null) return;
711           // We set the number of calls here. After that any path must call setResult/setError.
712           // True even for replicas that are not found - if we refuse to send we MUST set error.
713           results[index] = new ReplicaResultState(locs.length);
714         }
715         for (int i = 1; i < locs.length; ++i) {
716           Action<Row> replicaAction = new Action<Row>(action, i);
717           if (locs[i] != null) {
718             addAction(locs[i].getServerName(), locs[i].getRegionInfo().getRegionName(),
719                 replicaAction, actionsByServer, nonceGroup);
720           } else {
721             unknownReplicaActions.add(replicaAction);
722           }
723         }
724       }
725 
726       private void addReplicaActionsAgain(
727           Action<Row> action, Map<ServerName, MultiAction<Row>> actionsByServer) {
728         if (action.getReplicaId() == RegionReplicaUtil.DEFAULT_REPLICA_ID) {
729           throw new AssertionError("Cannot have default replica here");
730         }
731         HRegionLocation loc = getReplicaLocationOrFail(action);
732         if (loc == null) return;
733         addAction(loc.getServerName(), loc.getRegionInfo().getRegionName(),
734             action, actionsByServer, nonceGroup);
735       }
736     }
737 
738     /**
739      * Runnable (that can be submitted to thread pool) that submits MultiAction to a
740      * single server. The server call is synchronous, therefore we do it on a thread pool.
741      */
742     private final class SingleServerRequestRunnable implements Runnable {
743       private final MultiAction<Row> multiAction;
744       private final int numAttempt;
745       private final ServerName server;
746       private final Set<PayloadCarryingServerCallable> callsInProgress;
747 
748       private SingleServerRequestRunnable(
749           MultiAction<Row> multiAction, int numAttempt, ServerName server,
750           Set<PayloadCarryingServerCallable> callsInProgress) {
751         this.multiAction = multiAction;
752         this.numAttempt = numAttempt;
753         this.server = server;
754         this.callsInProgress = callsInProgress;
755       }
756 
757       @Override
758       public void run() {
759         MultiResponse res;
760         PayloadCarryingServerCallable callable = currentCallable;
761         try {
762           // setup the callable based on the actions, if we don't have one already from the request
763           if (callable == null) {
764             callable = createCallable(server, tableName, multiAction);
765           }
766           RpcRetryingCaller<MultiResponse> caller = createCaller(callable, rpcTimeout);
767           try {
768             if (callsInProgress != null) {
769               callsInProgress.add(callable);
770             }
771             res = caller.callWithoutRetries(callable, operationTimeout);
772             if (res == null) {
773               // Cancelled
774               return;
775             }
776           } catch (IOException e) {
777             // The service itself failed . It may be an error coming from the communication
778             //   layer, but, as well, a functional error raised by the server.
779             receiveGlobalFailure(multiAction, server, numAttempt, e);
780             return;
781           } catch (Throwable t) {
782             // This should not happen. Let's log & retry anyway.
783             LOG.error("#" + id + ", Caught throwable while calling. This is unexpected." +
784                 " Retrying. Server is " + server + ", tableName=" + tableName, t);
785             receiveGlobalFailure(multiAction, server, numAttempt, t);
786             return;
787           }
788 
789           // Normal case: we received an answer from the server, and it's not an exception.
790           receiveMultiAction(multiAction, server, res, numAttempt);
791         } catch (Throwable t) {
792           // Something really bad happened. We are on the send thread that will now die.
793           LOG.error("Internal AsyncProcess #" + id + " error for "
794               + tableName + " processing for " + server, t);
795           throw new RuntimeException(t);
796         } finally {
797           decTaskCounters(multiAction.getRegions(), server);
798           if (callsInProgress != null && callable != null) {
799             callsInProgress.remove(callable);
800           }
801         }
802       }
803     }
804 
805     private final Batch.Callback<CResult> callback;
806     private final BatchErrors errors;
807     private final ConnectionManager.ServerErrorTracker errorsByServer;
808     private final ExecutorService pool;
809     private final Set<PayloadCarryingServerCallable> callsInProgress;
810 
811 
812     private final TableName tableName;
813     private final AtomicLong actionsInProgress = new AtomicLong(-1);
814     /**
815      * The lock controls access to results. It is only held when populating results where
816      * there might be several callers (eventual consistency gets). For other requests,
817      * there's one unique call going on per result index.
818      */
819     private final Object replicaResultLock = new Object();
820     /**
821      * Result array.  Null if results are not needed. Otherwise, each index corresponds to
822      * the action index in initial actions submitted. For most request types, has null-s for
823      * requests that are not done, and result/exception for those that are done.
824      * For eventual-consistency gets, initially the same applies; at some point, replica calls
825      * might be started, and ReplicaResultState is put at the corresponding indices. The
826      * returning calls check the type to detect when this is the case. After all calls are done,
827      * ReplicaResultState-s are replaced with results for the user.
828      */
829     private final Object[] results;
830     /**
831      * Indices of replica gets in results. If null, all or no actions are replica-gets.
832      */
833     private final int[] replicaGetIndices;
834     private final boolean hasAnyReplicaGets;
835     private final long nonceGroup;
836     private PayloadCarryingServerCallable currentCallable;
837     private int operationTimeout;
838     private int rpcTimeout;
839     private RetryingTimeTracker tracker;
840 
841     public AsyncRequestFutureImpl(TableName tableName, List<Action<Row>> actions, long nonceGroup,
842         ExecutorService pool, boolean needResults, Object[] results,
843         Batch.Callback<CResult> callback, PayloadCarryingServerCallable callable,
844         int operationTimeout, int rpcTimeout) {
845       this.pool = pool;
846       this.callback = callback;
847       this.nonceGroup = nonceGroup;
848       this.tableName = tableName;
849       this.actionsInProgress.set(actions.size());
850       if (results != null) {
851         assert needResults;
852         if (results.length != actions.size()) {
853           throw new AssertionError("results.length");
854         }
855         this.results = results;
856         for (int i = 0; i != this.results.length; ++i) {
857           results[i] = null;
858         }
859       } else {
860         this.results = needResults ? new Object[actions.size()] : null;
861       }
862       List<Integer> replicaGetIndices = null;
863       boolean hasAnyReplicaGets = false;
864       if (needResults) {
865         // Check to see if any requests might require replica calls.
866         // We expect that many requests will consist of all or no multi-replica gets; in such
867         // cases we would just use a boolean (hasAnyReplicaGets). If there's a mix, we will
868         // store the list of action indexes for which replica gets are possible, and set
869         // hasAnyReplicaGets to true.
870         boolean hasAnyNonReplicaReqs = false;
871         int posInList = 0;
872         for (Action<Row> action : actions) {
873           boolean isReplicaGet = isReplicaGet(action.getAction());
874           if (isReplicaGet) {
875             hasAnyReplicaGets = true;
876             if (hasAnyNonReplicaReqs) { // Mixed case
877               if (replicaGetIndices == null) {
878                 replicaGetIndices = new ArrayList<Integer>(actions.size() - 1);
879               }
880               replicaGetIndices.add(posInList);
881             }
882           } else if (!hasAnyNonReplicaReqs) {
883             // The first non-multi-replica request in the action list.
884             hasAnyNonReplicaReqs = true;
885             if (posInList > 0) {
886               // Add all the previous requests to the index lists. We know they are all
887               // replica-gets because this is the first non-multi-replica request in the list.
888               replicaGetIndices = new ArrayList<Integer>(actions.size() - 1);
889               for (int i = 0; i < posInList; ++i) {
890                 replicaGetIndices.add(i);
891               }
892             }
893           }
894           ++posInList;
895         }
896       }
897       this.hasAnyReplicaGets = hasAnyReplicaGets;
898       if (replicaGetIndices != null) {
899         this.replicaGetIndices = new int[replicaGetIndices.size()];
900         int i = 0;
901         for (Integer el : replicaGetIndices) {
902           this.replicaGetIndices[i++] = el;
903         }
904       } else {
905         this.replicaGetIndices = null;
906       }
907       this.callsInProgress = !hasAnyReplicaGets ? null :
908           Collections.newSetFromMap(
909               new ConcurrentHashMap<PayloadCarryingServerCallable, Boolean>());
910 
911       this.errorsByServer = createServerErrorTracker();
912       this.errors = (globalErrors != null) ? globalErrors : new BatchErrors();
913       this.currentCallable = callable;
914       this.operationTimeout = operationTimeout;
915       this.rpcTimeout = rpcTimeout;
916       if (callable == null) {
917         this.tracker = new RetryingTimeTracker().start();
918       }
919     }
920 
921     public Set<PayloadCarryingServerCallable> getCallsInProgress() {
922       return callsInProgress;
923     }
924 
925     /**
926      * Group a list of actions per region servers, and send them.
927      *
928      * @param currentActions - the list of row to submit
929      * @param numAttempt - the current numAttempt (first attempt is 1)
930      */
931     private void groupAndSendMultiAction(List<Action<Row>> currentActions, int numAttempt) {
932       Map<ServerName, MultiAction<Row>> actionsByServer =
933           new HashMap<ServerName, MultiAction<Row>>();
934 
935       boolean isReplica = false;
936       List<Action<Row>> unknownReplicaActions = null;
937       for (Action<Row> action : currentActions) {
938         RegionLocations locs = findAllLocationsOrFail(action, true);
939         if (locs == null) continue;
940         boolean isReplicaAction = !RegionReplicaUtil.isDefaultReplica(action.getReplicaId());
941         if (isReplica && !isReplicaAction) {
942           // This is the property of the current implementation, not a requirement.
943           throw new AssertionError("Replica and non-replica actions in the same retry");
944         }
945         isReplica = isReplicaAction;
946         HRegionLocation loc = locs.getRegionLocation(action.getReplicaId());
947         if (loc == null || loc.getServerName() == null) {
948           if (isReplica) {
949             if (unknownReplicaActions == null) {
950               unknownReplicaActions = new ArrayList<Action<Row>>();
951             }
952             unknownReplicaActions.add(action);
953           } else {
954             // TODO: relies on primary location always being fetched
955             manageLocationError(action, null);
956           }
957         } else {
958           byte[] regionName = loc.getRegionInfo().getRegionName();
959           addAction(loc.getServerName(), regionName, action, actionsByServer, nonceGroup);
960         }
961       }
962       boolean doStartReplica = (numAttempt == 1 && !isReplica && hasAnyReplicaGets);
963       boolean hasUnknown = unknownReplicaActions != null && !unknownReplicaActions.isEmpty();
964 
965       if (!actionsByServer.isEmpty()) {
966         // If this is a first attempt to group and send, no replicas, we need replica thread.
967         sendMultiAction(actionsByServer, numAttempt, (doStartReplica && !hasUnknown)
968             ? currentActions : null, numAttempt > 1 && !hasUnknown);
969       }
970 
971       if (hasUnknown) {
972         actionsByServer = new HashMap<ServerName, MultiAction<Row>>();
973         for (Action<Row> action : unknownReplicaActions) {
974           HRegionLocation loc = getReplicaLocationOrFail(action);
975           if (loc == null) continue;
976           byte[] regionName = loc.getRegionInfo().getRegionName();
977           addAction(loc.getServerName(), regionName, action, actionsByServer, nonceGroup);
978         }
979         if (!actionsByServer.isEmpty()) {
980           sendMultiAction(
981               actionsByServer, numAttempt, doStartReplica ? currentActions : null, true);
982         }
983       }
984     }
985 
986     private HRegionLocation getReplicaLocationOrFail(Action<Row> action) {
987       // We are going to try get location once again. For each action, we'll do it once
988       // from cache, because the previous calls in the loop might populate it.
989       int replicaId = action.getReplicaId();
990       RegionLocations locs = findAllLocationsOrFail(action, true);
991       if (locs == null) return null; // manageError already called
992       HRegionLocation loc = locs.getRegionLocation(replicaId);
993       if (loc == null || loc.getServerName() == null) {
994         locs = findAllLocationsOrFail(action, false);
995         if (locs == null) return null; // manageError already called
996         loc = locs.getRegionLocation(replicaId);
997       }
998       if (loc == null || loc.getServerName() == null) {
999         manageLocationError(action, null);
1000         return null;
1001       }
1002       return loc;
1003     }
1004 
1005     private void manageLocationError(Action<Row> action, Exception ex) {
1006       String msg = "Cannot get replica " + action.getReplicaId()
1007           + " location for " + action.getAction();
1008       LOG.error(msg);
1009       if (ex == null) {
1010         ex = new IOException(msg);
1011       }
1012       manageError(action.getOriginalIndex(), action.getAction(),
1013           Retry.NO_LOCATION_PROBLEM, ex, null);
1014     }
1015 
1016     private RegionLocations findAllLocationsOrFail(Action<Row> action, boolean useCache) {
1017       if (action.getAction() == null) throw new IllegalArgumentException("#" + id +
1018           ", row cannot be null");
1019       RegionLocations loc = null;
1020       try {
1021         loc = connection.locateRegion(
1022             tableName, action.getAction().getRow(), useCache, true, action.getReplicaId());
1023       } catch (IOException ex) {
1024         manageLocationError(action, ex);
1025       }
1026       return loc;
1027     }
1028 
1029     /**
1030      * Send a multi action structure to the servers, after a delay depending on the attempt
1031      * number. Asynchronous.
1032      *
1033      * @param actionsByServer the actions structured by regions
1034      * @param numAttempt the attempt number.
1035      * @param actionsForReplicaThread original actions for replica thread; null on non-first call.
1036      */
1037     private void sendMultiAction(Map<ServerName, MultiAction<Row>> actionsByServer,
1038         int numAttempt, List<Action<Row>> actionsForReplicaThread, boolean reuseThread) {
1039       // Run the last item on the same thread if we are already on a send thread.
1040       // We hope most of the time it will be the only item, so we can cut down on threads.
1041       int actionsRemaining = actionsByServer.size();
1042       // This iteration is by server (the HRegionLocation comparator is by server portion only).
1043       for (Map.Entry<ServerName, MultiAction<Row>> e : actionsByServer.entrySet()) {
1044         ServerName server = e.getKey();
1045         MultiAction<Row> multiAction = e.getValue();
1046         incTaskCounters(multiAction.getRegions(), server);
1047         Collection<? extends Runnable> runnables = getNewMultiActionRunnable(server, multiAction,
1048             numAttempt);
1049         // make sure we correctly count the number of runnables before we try to reuse the send
1050         // thread, in case we had to split the request into different runnables because of backoff
1051         if (runnables.size() > actionsRemaining) {
1052           actionsRemaining = runnables.size();
1053         }
1054 
1055         // run all the runnables
1056         // HBASE-17475: Do not reuse the thread after stack reach a certain depth to prevent stack overflow
1057         // for now, we use HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER to control the depth
1058         for (Runnable runnable : runnables) {
1059           if ((--actionsRemaining == 0) && reuseThread
1060               && numAttempt % HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER != 0) {
1061             runnable.run();
1062           } else {
1063             try {
1064               pool.submit(runnable);
1065             } catch (Throwable t) {
1066               if (t instanceof RejectedExecutionException) {
1067                 // This should never happen. But as the pool is provided by the end user,
1068                // let's secure this a little.
1069                LOG.warn("#" + id + ", the task was rejected by the pool. This is unexpected." +
1070                   " Server is " + server.getServerName(), t);
1071               } else {
1072                 // see #HBASE-14359 for more details
1073                 LOG.warn("Caught unexpected exception/error: ", t);
1074               }
1075               decTaskCounters(multiAction.getRegions(), server);
1076               // We're likely to fail again, but this will increment the attempt counter,
1077              // so it will finish.
1078               receiveGlobalFailure(multiAction, server, numAttempt, t);
1079             }
1080           }
1081         }
1082       }
1083 
1084       if (actionsForReplicaThread != null) {
1085         startWaitingForReplicaCalls(actionsForReplicaThread);
1086       }
1087     }
1088 
1089     private Collection<? extends Runnable> getNewMultiActionRunnable(ServerName server,
1090         MultiAction<Row> multiAction,
1091         int numAttempt) {
1092       // no stats to manage, just do the standard action
1093       if (AsyncProcess.this.connection.getStatisticsTracker() == null) {
1094         if (connection.getConnectionMetrics() != null) {
1095           connection.getConnectionMetrics().incrNormalRunners();
1096         }
1097         return Collections.singletonList(Trace.wrap("AsyncProcess.sendMultiAction",
1098             new SingleServerRequestRunnable(multiAction, numAttempt, server, callsInProgress)));
1099       }
1100 
1101       // group the actions by the amount of delay
1102       Map<Long, DelayingRunner> actions = new HashMap<Long, DelayingRunner>(multiAction
1103           .size());
1104 
1105       // split up the actions
1106       for (Map.Entry<byte[], List<Action<Row>>> e : multiAction.actions.entrySet()) {
1107         Long backoff = getBackoff(server, e.getKey());
1108         DelayingRunner runner = actions.get(backoff);
1109         if (runner == null) {
1110           actions.put(backoff, new DelayingRunner(backoff, e));
1111         } else {
1112           runner.add(e);
1113         }
1114       }
1115 
1116       List<Runnable> toReturn = new ArrayList<Runnable>(actions.size());
1117       for (DelayingRunner runner : actions.values()) {
1118         String traceText = "AsyncProcess.sendMultiAction";
1119         Runnable runnable =
1120             new SingleServerRequestRunnable(runner.getActions(), numAttempt, server,
1121                 callsInProgress);
1122         // use a delay runner only if we need to sleep for some time
1123         if (runner.getSleepTime() > 0) {
1124           runner.setRunner(runnable);
1125           traceText = "AsyncProcess.clientBackoff.sendMultiAction";
1126           runnable = runner;
1127           if (connection.getConnectionMetrics() != null) {
1128             connection.getConnectionMetrics().incrDelayRunners();
1129             connection.getConnectionMetrics().updateDelayInterval(runner.getSleepTime());
1130           }
1131         } else {
1132           if (connection.getConnectionMetrics() != null) {
1133             connection.getConnectionMetrics().incrNormalRunners();
1134           }
1135         }
1136         runnable = Trace.wrap(traceText, runnable);
1137         toReturn.add(runnable);
1138 
1139       }
1140       return toReturn;
1141     }
1142 
1143     /**
1144      * @param server server location where the target region is hosted
1145      * @param regionName name of the region which we are going to write some data
1146      * @return the amount of time the client should wait until it submit a request to the
1147      * specified server and region
1148      */
1149     private Long getBackoff(ServerName server, byte[] regionName) {
1150       ServerStatisticTracker tracker = AsyncProcess.this.connection.getStatisticsTracker();
1151       ServerStatistics stats = tracker.getStats(server);
1152       return AsyncProcess.this.connection.getBackoffPolicy()
1153           .getBackoffTime(server, regionName, stats);
1154     }
1155 
1156     /**
1157      * Starts waiting to issue replica calls on a different thread; or issues them immediately.
1158      */
1159     private void startWaitingForReplicaCalls(List<Action<Row>> actionsForReplicaThread) {
1160       long startTime = EnvironmentEdgeManager.currentTime();
1161       ReplicaCallIssuingRunnable replicaRunnable = new ReplicaCallIssuingRunnable(
1162           actionsForReplicaThread, startTime);
1163       if (primaryCallTimeoutMicroseconds == 0) {
1164         // Start replica calls immediately.
1165         replicaRunnable.run();
1166       } else {
1167         // Start the thread that may kick off replica gets.
1168         // TODO: we could do it on the same thread, but it's a user thread, might be a bad idea.
1169         try {
1170           pool.submit(replicaRunnable);
1171         } catch (RejectedExecutionException ree) {
1172           LOG.warn("#" + id + ", replica task was rejected by the pool - no replica calls", ree);
1173         }
1174       }
1175     }
1176 
1177     /**
1178      * Check that we can retry acts accordingly: logs, set the error status.
1179      *
1180      * @param originalIndex the position in the list sent
1181      * @param row           the row
1182      * @param canRetry      if false, we won't retry whatever the settings.
1183      * @param throwable     the throwable, if any (can be null)
1184      * @param server        the location, if any (can be null)
1185      * @return true if the action can be retried, false otherwise.
1186      */
1187     public Retry manageError(int originalIndex, Row row, Retry canRetry,
1188                                 Throwable throwable, ServerName server) {
1189       if (canRetry == Retry.YES
1190           && throwable != null && (throwable instanceof DoNotRetryIOException ||
1191           throwable instanceof NeedUnmanagedConnectionException)) {
1192         canRetry = Retry.NO_NOT_RETRIABLE;
1193       }
1194 
1195       if (canRetry != Retry.YES) {
1196         // Batch.Callback<Res> was not called on failure in 0.94. We keep this.
1197         setError(originalIndex, row, throwable, server);
1198       } else if (isActionComplete(originalIndex, row)) {
1199         canRetry = Retry.NO_OTHER_SUCCEEDED;
1200       }
1201       return canRetry;
1202     }
1203 
1204     /**
1205      * Resubmit all the actions from this multiaction after a failure.
1206      *
1207      * @param rsActions  the actions still to do from the initial list
1208      * @param server   the destination
1209      * @param numAttempt the number of attempts so far
1210      * @param t the throwable (if any) that caused the resubmit
1211      */
1212     private void receiveGlobalFailure(
1213         MultiAction<Row> rsActions, ServerName server, int numAttempt, Throwable t) {
1214       errorsByServer.reportServerError(server);
1215       Retry canRetry = errorsByServer.canRetryMore(numAttempt)
1216           ? Retry.YES : Retry.NO_RETRIES_EXHAUSTED;
1217 
1218       if (tableName == null && ClientExceptionsUtil.isMetaClearingException(t)) {
1219         // tableName is null when we made a cross-table RPC call.
1220         connection.clearCaches(server);
1221       }
1222       int failed = 0, stopped = 0;
1223       List<Action<Row>> toReplay = new ArrayList<Action<Row>>();
1224       for (Map.Entry<byte[], List<Action<Row>>> e : rsActions.actions.entrySet()) {
1225         byte[] regionName = e.getKey();
1226         byte[] row = e.getValue().iterator().next().getAction().getRow();
1227         // Do not use the exception for updating cache because it might be coming from
1228         // any of the regions in the MultiAction.
1229         try {
1230           if (tableName != null) {
1231             connection.updateCachedLocations(tableName, regionName, row,
1232               ClientExceptionsUtil.isMetaClearingException(t) ? null : t, server);
1233           }
1234         } catch (Throwable ex) {
1235           // That should never happen, but if it did, we want to make sure
1236           // we still process errors
1237           LOG.error("Couldn't update cached region locations: " + ex);
1238         }
1239         for (Action<Row> action : e.getValue()) {
1240           Retry retry = manageError(
1241               action.getOriginalIndex(), action.getAction(), canRetry, t, server);
1242           if (retry == Retry.YES) {
1243             toReplay.add(action);
1244           } else if (retry == Retry.NO_OTHER_SUCCEEDED) {
1245             ++stopped;
1246           } else {
1247             ++failed;
1248           }
1249         }
1250       }
1251 
1252       if (toReplay.isEmpty()) {
1253         logNoResubmit(server, numAttempt, rsActions.size(), t, failed, stopped);
1254       } else {
1255         resubmit(server, toReplay, numAttempt, rsActions.size(), t);
1256       }
1257     }
1258 
1259     /**
1260      * Log as much info as possible, and, if there is something to replay,
1261      * submit it again after a back off sleep.
1262      */
1263     private void resubmit(ServerName oldServer, List<Action<Row>> toReplay,
1264         int numAttempt, int failureCount, Throwable throwable) {
1265       // We have something to replay. We're going to sleep a little before.
1266 
1267       // We have two contradicting needs here:
1268       //  1) We want to get the new location after having slept, as it may change.
1269       //  2) We want to take into account the location when calculating the sleep time.
1270       //  3) If all this is just because the response needed to be chunked try again FAST.
1271       // It should be possible to have some heuristics to take the right decision. Short term,
1272       //  we go for one.
1273       boolean retryImmediately = throwable instanceof RetryImmediatelyException;
1274       int nextAttemptNumber = retryImmediately ? numAttempt : numAttempt + 1;
1275       long backOffTime = retryImmediately ? 0 :
1276           errorsByServer.calculateBackoffTime(oldServer, pause);
1277       if (numAttempt > startLogErrorsCnt) {
1278         // We use this value to have some logs when we have multiple failures, but not too many
1279         //  logs, as errors are to be expected when a region moves, splits and so on
1280         LOG.info(createLog(numAttempt, failureCount, toReplay.size(),
1281             oldServer, throwable, backOffTime, true, null, -1, -1));
1282       }
1283 
1284       try {
1285         if (backOffTime > 0) {
1286           Thread.sleep(backOffTime);
1287         }
1288       } catch (InterruptedException e) {
1289         LOG.warn("#" + id + ", not sent: " + toReplay.size() + " operations, " + oldServer, e);
1290         Thread.currentThread().interrupt();
1291         return;
1292       }
1293 
1294       groupAndSendMultiAction(toReplay, nextAttemptNumber);
1295     }
1296 
1297     private void logNoResubmit(ServerName oldServer, int numAttempt,
1298         int failureCount, Throwable throwable, int failed, int stopped) {
1299       if (failureCount != 0 || numAttempt > startLogErrorsCnt + 1) {
1300         String timeStr = new Date(errorsByServer.getStartTrackingTime()).toString();
1301         String logMessage = createLog(numAttempt, failureCount, 0, oldServer,
1302             throwable, -1, false, timeStr, failed, stopped);
1303         if (failed != 0) {
1304           // Only log final failures as warning
1305           LOG.warn(logMessage);
1306         } else {
1307           LOG.info(logMessage);
1308         }
1309       }
1310     }
1311 
1312     @VisibleForTesting
1313     long getActionsInProgress() {
1314       return actionsInProgress.get();
1315     }
1316     /**
1317      * Called when we receive the result of a server query.
1318      *
1319      * @param multiAction    - the multiAction we sent
1320      * @param server       - the location. It's used as a server name.
1321      * @param responses      - the response, if any
1322      * @param numAttempt     - the attempt
1323      */
1324     private void receiveMultiAction(MultiAction<Row> multiAction,
1325         ServerName server, MultiResponse responses, int numAttempt) {
1326        assert responses != null;
1327 
1328       // Success or partial success
1329       // Analyze detailed results. We can still have individual failures to be redo.
1330       // two specific throwables are managed:
1331       //  - DoNotRetryIOException: we continue to retry for other actions
1332       //  - RegionMovedException: we update the cache with the new region location
1333 
1334       List<Action<Row>> toReplay = new ArrayList<Action<Row>>();
1335       Throwable throwable = null;
1336       int failureCount = 0;
1337       Retry canRetry = null;
1338 
1339       Map<byte[], MultiResponse.RegionResult> results = responses.getResults();
1340       updateStats(server, results);
1341 
1342       int failed = 0;
1343       int stopped = 0;
1344       // Go by original action.
1345       for (Map.Entry<byte[], List<Action<Row>>> regionEntry : multiAction.actions.entrySet()) {
1346         byte[] regionName = regionEntry.getKey();
1347         Throwable regionException = responses.getExceptions().get(regionName);
1348         if (tableName == null && regionException != null &&
1349               ClientExceptionsUtil.isMetaClearingException(regionException)) {
1350           // For multi-actions, we don't have a table name, but we want to make sure to clear the
1351           // cache in case there were location-related exceptions. We don't to clear the cache
1352           // for every possible exception that comes through, however.
1353           connection.clearCaches(server);
1354         }
1355         Map<Integer, Object> regionResults;
1356         if (results.containsKey(regionName)) {
1357           regionResults = results.get(regionName).result;
1358         } else {
1359           regionResults = Collections.emptyMap();
1360         }
1361         boolean regionFailureRegistered = false;
1362         for (Action<Row> sentAction : regionEntry.getValue()) {
1363           Object result = regionResults.get(sentAction.getOriginalIndex());
1364           if (result == null) {
1365             if (regionException == null) {
1366               LOG.error("Server sent us neither results nor exceptions for " + Bytes
1367                 .toStringBinary(regionName) + ", numAttempt:" + numAttempt);
1368               regionException = new RuntimeException("Invalid response");
1369             }
1370             // If the row operation encounters the region-lever error, the exception of action
1371             // may be null.
1372             result = regionException;
1373           }
1374           // Failure: retry if it's make sense else update the errors lists
1375           if (result instanceof Throwable) {
1376             Row row = sentAction.getAction();
1377             throwable = regionException != null ? regionException
1378               : ClientExceptionsUtil.findException(result);
1379             // Register corresponding failures once per server/once per region.
1380             if (!regionFailureRegistered) {
1381               regionFailureRegistered = true;
1382               try {
1383                 connection.updateCachedLocations(
1384                   tableName, regionName, row.getRow(), result, server);
1385               } catch (Throwable ex) {
1386                 // That should never happen, but if it did, we want to make sure
1387                 // we still process errors
1388                 LOG.error("Couldn't update cached region locations: " + ex);
1389               }
1390             }
1391             if (canRetry == null) {
1392               errorsByServer.reportServerError(server);
1393               // We determine canRetry only once for all calls, after reporting server failure.
1394               canRetry =
1395                 errorsByServer.canRetryMore(numAttempt) ? Retry.YES : Retry.NO_RETRIES_EXHAUSTED;
1396             }
1397             ++failureCount;
1398             switch (manageError(sentAction.getOriginalIndex(), row, canRetry, (Throwable) result,
1399               server)) {
1400               case YES:
1401                 toReplay.add(sentAction);
1402                 break;
1403               case NO_OTHER_SUCCEEDED:
1404                 ++stopped;
1405                 break;
1406               default:
1407                 ++failed;
1408                 break;
1409             }
1410           } else {
1411             if (callback != null) {
1412               try {
1413                 //noinspection unchecked
1414                 // TODO: would callback expect a replica region name if it gets one?
1415                 this.callback.update(regionName, sentAction.getAction().getRow(), (CResult) result);
1416               } catch (Throwable t) {
1417                 LOG.error("User callback threw an exception for "
1418                     + Bytes.toStringBinary(regionName) + ", ignoring", t);
1419               }
1420             }
1421             setResult(sentAction, result);
1422           }
1423         }
1424       }
1425       if (toReplay.isEmpty()) {
1426         logNoResubmit(server, numAttempt, failureCount, throwable, failed, stopped);
1427       } else {
1428         resubmit(server, toReplay, numAttempt, failureCount, throwable);
1429       }
1430     }
1431 
1432     private String createLog(int numAttempt, int failureCount, int replaySize, ServerName sn,
1433         Throwable error, long backOffTime, boolean willRetry, String startTime,
1434         int failed, int stopped) {
1435       StringBuilder sb = new StringBuilder();
1436       sb.append("#").append(id).append(", table=").append(tableName).append(", ")
1437         .append("attempt=").append(numAttempt)
1438         .append("/").append(numTries).append(" ");
1439 
1440       if (failureCount > 0 || error != null){
1441         sb.append("failed=").append(failureCount).append("ops").append(", last exception: ").
1442             append(error == null ? "null" : error);
1443       } else {
1444         sb.append("succeeded");
1445       }
1446 
1447       sb.append(" on ").append(sn).append(", tracking started ").append(startTime);
1448 
1449       if (willRetry) {
1450         sb.append(", retrying after=").append(backOffTime).append("ms").
1451             append(", replay=").append(replaySize).append("ops");
1452       } else if (failureCount > 0) {
1453         if (stopped > 0) {
1454           sb.append("; not retrying ").append(stopped).append(" due to success from other replica");
1455         }
1456         if (failed > 0) {
1457           sb.append("; not retrying ").append(failed).append(" - final failure");
1458         }
1459 
1460       }
1461 
1462       return sb.toString();
1463     }
1464 
1465     /**
1466      * Sets the non-error result from a particular action.
1467      * @param action Action (request) that the server responded to.
1468      * @param result The result.
1469      */
1470     private void setResult(Action<Row> action, Object result) {
1471       if (result == null) {
1472         throw new RuntimeException("Result cannot be null");
1473       }
1474       ReplicaResultState state = null;
1475       boolean isStale = !RegionReplicaUtil.isDefaultReplica(action.getReplicaId());
1476       int index = action.getOriginalIndex();
1477       if (results == null) {
1478          decActionCounter(index);
1479          return; // Simple case, no replica requests.
1480       } else if ((state = trySetResultSimple(
1481           index, action.getAction(), false, result, null, isStale)) == null) {
1482         return; // Simple case, no replica requests.
1483       }
1484       assert state != null;
1485       // At this point we know that state is set to replica tracking class.
1486       // It could be that someone else is also looking at it; however, we know there can
1487       // only be one state object, and only one thread can set callCount to 0. Other threads
1488       // will either see state with callCount 0 after locking it; or will not see state at all
1489       // we will replace it with the result.
1490       synchronized (state) {
1491         if (state.callCount == 0) {
1492           return; // someone already set the result
1493         }
1494         state.callCount = 0;
1495       }
1496       synchronized (replicaResultLock) {
1497         if (results[index] != state) {
1498           throw new AssertionError("We set the callCount but someone else replaced the result");
1499         }
1500         results[index] = result;
1501       }
1502 
1503       decActionCounter(index);
1504     }
1505 
1506     /**
1507      * Sets the error from a particular action.
1508      * @param index Original action index.
1509      * @param row Original request.
1510      * @param throwable The resulting error.
1511      * @param server The source server.
1512      */
1513     private void setError(int index, Row row, Throwable throwable, ServerName server) {
1514       ReplicaResultState state = null;
1515       if (results == null) {
1516         // Note that we currently cannot have replica requests with null results. So it shouldn't
1517         // happen that multiple replica calls will call dAC for same actions with results == null.
1518         // Only one call per action should be present in this case.
1519         errors.add(throwable, row, server);
1520         decActionCounter(index);
1521         return; // Simple case, no replica requests.
1522       } else if ((state = trySetResultSimple(
1523           index, row, true, throwable, server, false)) == null) {
1524         return; // Simple case, no replica requests.
1525       }
1526       assert state != null;
1527       BatchErrors target = null; // Error will be added to final errors, or temp replica errors.
1528       boolean isActionDone = false;
1529       synchronized (state) {
1530         switch (state.callCount) {
1531           case 0: return; // someone already set the result
1532           case 1: { // All calls failed, we are the last error.
1533             target = errors;
1534             isActionDone = true;
1535             break;
1536           }
1537           default: {
1538             assert state.callCount > 1;
1539             if (state.replicaErrors == null) {
1540               state.replicaErrors = new BatchErrors();
1541             }
1542             target = state.replicaErrors;
1543             break;
1544           }
1545         }
1546         --state.callCount;
1547       }
1548       target.add(throwable, row, server);
1549       if (isActionDone) {
1550         if (state.replicaErrors != null) { // last call, no need to lock
1551           errors.merge(state.replicaErrors);
1552         }
1553         // See setResult for explanations.
1554         synchronized (replicaResultLock) {
1555           if (results[index] != state) {
1556             throw new AssertionError("We set the callCount but someone else replaced the result");
1557           }
1558           results[index] = throwable;
1559         }
1560         decActionCounter(index);
1561       }
1562     }
1563 
1564     /**
1565      * Checks if the action is complete; used on error to prevent needless retries.
1566      * Does not synchronize, assuming element index/field accesses are atomic.
1567      * This is an opportunistic optimization check, doesn't have to be strict.
1568      * @param index Original action index.
1569      * @param row Original request.
1570      */
1571     private boolean isActionComplete(int index, Row row) {
1572       if (!isReplicaGet(row)) return false;
1573       Object resObj = results[index];
1574       return (resObj != null) && (!(resObj instanceof ReplicaResultState)
1575           || ((ReplicaResultState)resObj).callCount == 0);
1576     }
1577 
1578     /**
1579      * Tries to set the result or error for a particular action as if there were no replica calls.
1580      * @return null if successful; replica state if there were in fact replica calls.
1581      */
1582     private ReplicaResultState trySetResultSimple(int index, Row row, boolean isError,
1583         Object result, ServerName server, boolean isFromReplica) {
1584       Object resObj = null;
1585       if (!isReplicaGet(row)) {
1586         if (isFromReplica) {
1587           throw new AssertionError("Unexpected stale result for " + row);
1588         }
1589         results[index] = result;
1590       } else {
1591         synchronized (replicaResultLock) {
1592           if ((resObj = results[index]) == null) {
1593             if (isFromReplica) {
1594               throw new AssertionError("Unexpected stale result for " + row);
1595             }
1596             results[index] = result;
1597           }
1598         }
1599       }
1600 
1601       ReplicaResultState rrs =
1602           (resObj instanceof ReplicaResultState) ? (ReplicaResultState)resObj : null;
1603       if (rrs == null && isError) {
1604         // The resObj is not replica state (null or already set).
1605         errors.add((Throwable)result, row, server);
1606       }
1607 
1608       if (resObj == null) {
1609         // resObj is null - no replica calls were made.
1610         decActionCounter(index);
1611         return null;
1612       }
1613       return rrs;
1614     }
1615 
1616     private void decActionCounter(int index) {
1617       long actionsRemaining = actionsInProgress.decrementAndGet();
1618       if (actionsRemaining < 0) {
1619         String error = buildDetailedErrorMsg("Incorrect actions in progress", index);
1620         throw new AssertionError(error);
1621       } else if (actionsRemaining == 0) {
1622         synchronized (actionsInProgress) {
1623           actionsInProgress.notifyAll();
1624         }
1625       }
1626     }
1627 
1628     private String buildDetailedErrorMsg(String string, int index) {
1629       StringBuilder error = new StringBuilder(string);
1630       error.append("; called for ").
1631         append(index).
1632         append(", actionsInProgress ").
1633         append(actionsInProgress.get()).
1634         append("; replica gets: ");
1635       if (replicaGetIndices != null) {
1636         for (int i = 0; i < replicaGetIndices.length; ++i) {
1637           error.append(replicaGetIndices[i]).append(", ");
1638         }
1639       } else {
1640         error.append(hasAnyReplicaGets ? "all" : "none");
1641       }
1642       error.append("; results ");
1643       if (results != null) {
1644         for (int i = 0; i < results.length; ++i) {
1645           Object o = results[i];
1646           error.append(((o == null) ? "null" : o.toString())).append(", ");
1647         }
1648       }
1649       return error.toString();
1650     }
1651 
1652     @Override
1653     public void waitUntilDone() throws InterruptedIOException {
1654       try {
1655         waitUntilDone(Long.MAX_VALUE);
1656       } catch (InterruptedException iex) {
1657         throw new InterruptedIOException(iex.getMessage());
1658       } finally {
1659         if (callsInProgress != null) {
1660           for (PayloadCarryingServerCallable clb : callsInProgress) {
1661             clb.cancel();
1662           }
1663         }
1664       }
1665     }
1666 
1667     private boolean waitUntilDone(long cutoff) throws InterruptedException {
1668       boolean hasWait = cutoff != Long.MAX_VALUE;
1669       long lastLog = EnvironmentEdgeManager.currentTime();
1670       long currentInProgress;
1671       while (0 != (currentInProgress = actionsInProgress.get())) {
1672         long now = EnvironmentEdgeManager.currentTime();
1673         if (hasWait && (now * 1000L) > cutoff) {
1674           return false;
1675         }
1676         if (!hasWait) { // Only log if wait is infinite.
1677           if (now > lastLog + 10000) {
1678             lastLog = now;
1679             LOG.info("#" + id + ", waiting for " + currentInProgress
1680                 + "  actions to finish on table: " + tableName);
1681             if (currentInProgress <= thresholdToLogUndoneTaskDetails) {
1682               logDetailsOfUndoneTasks(currentInProgress);
1683             }
1684           }
1685         }
1686         synchronized (actionsInProgress) {
1687           if (actionsInProgress.get() == 0) break;
1688           if (!hasWait) {
1689             actionsInProgress.wait(10);
1690           } else {
1691             long waitMicroSecond = Math.min(100000L, (cutoff - now * 1000L));
1692             TimeUnit.MICROSECONDS.timedWait(actionsInProgress, waitMicroSecond);
1693           }
1694         }
1695       }
1696       return true;
1697     }
1698 
1699     @Override
1700     public boolean hasError() {
1701       return errors.hasErrors();
1702     }
1703 
1704     @Override
1705     public List<? extends Row> getFailedOperations() {
1706       return errors.actions;
1707     }
1708 
1709     @Override
1710     public RetriesExhaustedWithDetailsException getErrors() {
1711       return errors.makeException();
1712     }
1713 
1714     @Override
1715     public Object[] getResults() throws InterruptedIOException {
1716       waitUntilDone();
1717       return results;
1718     }
1719 
1720     /**
1721      * Create a callable. Isolated to be easily overridden in the tests.
1722      */
1723     @VisibleForTesting
1724     protected MultiServerCallable<Row> createCallable(final ServerName server,
1725         TableName tableName, final MultiAction<Row> multi) {
1726       return new MultiServerCallable<Row>(connection, tableName, server,
1727           AsyncProcess.this.rpcFactory, multi, rpcTimeout, tracker);
1728     }
1729   }
1730 
1731   private void updateStats(ServerName server, Map<byte[], MultiResponse.RegionResult> results) {
1732     boolean metrics = AsyncProcess.this.connection.getConnectionMetrics() != null;
1733     boolean stats = AsyncProcess.this.connection.getStatisticsTracker() != null;
1734     if (!stats && !metrics) {
1735       return;
1736     }
1737     for (Map.Entry<byte[], MultiResponse.RegionResult> regionStats : results.entrySet()) {
1738       byte[] regionName = regionStats.getKey();
1739       ClientProtos.RegionLoadStats stat = regionStats.getValue().getStat();
1740       ResultStatsUtil.updateStats(AsyncProcess.this.connection.getStatisticsTracker(), server,
1741           regionName, stat);
1742       ResultStatsUtil.updateStats(AsyncProcess.this.connection.getConnectionMetrics(),
1743           server, regionName, stat);
1744     }
1745   }
1746 
1747   protected <CResult> AsyncRequestFutureImpl<CResult> createAsyncRequestFuture(
1748       TableName tableName, List<Action<Row>> actions, long nonceGroup, ExecutorService pool,
1749       Batch.Callback<CResult> callback, Object[] results, boolean needResults,
1750       PayloadCarryingServerCallable callable, int operationTimeout, int rpcTimeout) {
1751     return new AsyncRequestFutureImpl<CResult>(
1752         tableName, actions, nonceGroup, getPool(pool), needResults,
1753         results, callback, callable, operationTimeout, rpcTimeout);
1754   }
1755 
1756   @VisibleForTesting
1757   /** Create AsyncRequestFuture. Isolated to be easily overridden in the tests. */
1758   protected <CResult> AsyncRequestFutureImpl<CResult> createAsyncRequestFuture(
1759       TableName tableName, List<Action<Row>> actions, long nonceGroup, ExecutorService pool,
1760       Batch.Callback<CResult> callback, Object[] results, boolean needResults) {
1761     return createAsyncRequestFuture(
1762         tableName, actions, nonceGroup, pool, callback, results, needResults, null,
1763         operationTimeout, rpcTimeout);
1764   }
1765 
1766   /**
1767    * Create a caller. Isolated to be easily overridden in the tests.
1768    */
1769   @VisibleForTesting
1770   protected RpcRetryingCaller<MultiResponse> createCaller(PayloadCarryingServerCallable callable,
1771       int rpcTimeout) {
1772     return rpcCallerFactory.<MultiResponse> newCaller(rpcTimeout);
1773   }
1774 
1775   @VisibleForTesting
1776   /** Waits until all outstanding tasks are done. Used in tests. */
1777   void waitUntilDone() throws InterruptedIOException {
1778     waitForMaximumCurrentTasks(0, null);
1779   }
1780 
1781   /** Wait until the async does not have more than max tasks in progress. */
1782   private void waitForMaximumCurrentTasks(int max, String tableName)
1783       throws InterruptedIOException {
1784     waitForMaximumCurrentTasks(max, tasksInProgress, id, tableName);
1785   }
1786 
1787   // Break out this method so testable
1788   @VisibleForTesting
1789   void waitForMaximumCurrentTasks(int max, final AtomicLong tasksInProgress, final long id,
1790       String tableName) throws InterruptedIOException {
1791     long lastLog = EnvironmentEdgeManager.currentTime();
1792     long currentInProgress, oldInProgress = Long.MAX_VALUE;
1793     while ((currentInProgress = tasksInProgress.get()) > max) {
1794       if (oldInProgress != currentInProgress) { // Wait for in progress to change.
1795         long now = EnvironmentEdgeManager.currentTime();
1796         if (now > lastLog + 10000) {
1797           lastLog = now;
1798           LOG.info("#" + id + ", waiting for some tasks to finish. Expected max="
1799               + max + ", tasksInProgress=" + currentInProgress +
1800               " hasError=" + hasError() + (tableName == null ? "" : ", tableName=" + tableName));
1801           if (currentInProgress <= thresholdToLogUndoneTaskDetails) {
1802             logDetailsOfUndoneTasks(currentInProgress);
1803           }
1804         }
1805       }
1806       oldInProgress = currentInProgress;
1807       try {
1808         synchronized (tasksInProgress) {
1809           if (tasksInProgress.get() == oldInProgress) {
1810             tasksInProgress.wait(10);
1811           }
1812         }
1813       } catch (InterruptedException e) {
1814         throw new InterruptedIOException("#" + id + ", interrupted." +
1815             " currentNumberOfTask=" + currentInProgress);
1816       }
1817     }
1818   }
1819 
1820   private void logDetailsOfUndoneTasks(long taskInProgress) {
1821     ArrayList<ServerName> servers = new ArrayList<ServerName>();
1822     for (Map.Entry<ServerName, AtomicInteger> entry : taskCounterPerServer.entrySet()) {
1823       if (entry.getValue().get() > 0) {
1824         servers.add(entry.getKey());
1825       }
1826     }
1827     LOG.info("Left over " + taskInProgress + " task(s) are processed on server(s): " + servers);
1828     if (taskInProgress <= THRESHOLD_TO_LOG_REGION_DETAILS) {
1829       ArrayList<String> regions = new ArrayList<String>();
1830       for (Map.Entry<byte[], AtomicInteger> entry : taskCounterPerRegion.entrySet()) {
1831         if (entry.getValue().get() > 0) {
1832           regions.add(Bytes.toString(entry.getKey()));
1833         }
1834       }
1835       LOG.info("Regions against which left over task(s) are processed: " + regions);
1836     }
1837   }
1838 
1839   /**
1840    * Only used w/useGlobalErrors ctor argument, for HTable backward compat.
1841    * @return Whether there were any errors in any request since the last time
1842    *          {@link #waitForAllPreviousOpsAndReset(List)} was called, or AP was created.
1843    */
1844   public boolean hasError() {
1845     return globalErrors.hasErrors();
1846   }
1847 
1848   /**
1849    * Only used w/useGlobalErrors ctor argument, for HTable backward compat.
1850    * Waits for all previous operations to finish, and returns errors and (optionally)
1851    * failed operations themselves.
1852    * @param failedRows an optional list into which the rows that failed since the last time
1853    *        {@link #waitForAllPreviousOpsAndReset(List)} was called, or AP was created, are saved.
1854    * @param tableName name of the table
1855    * @return all the errors since the last time {@link #waitForAllPreviousOpsAndReset(List)}
1856    *          was called, or AP was created.
1857    */
1858   public RetriesExhaustedWithDetailsException waitForAllPreviousOpsAndReset(
1859       List<Row> failedRows, String tableName) throws InterruptedIOException {
1860     waitForMaximumCurrentTasks(0, tableName);
1861     if (!globalErrors.hasErrors()) {
1862       return null;
1863     }
1864     if (failedRows != null) {
1865       failedRows.addAll(globalErrors.actions);
1866     }
1867     RetriesExhaustedWithDetailsException result = globalErrors.makeException();
1868     globalErrors.clear();
1869     return result;
1870   }
1871 
1872   /**
1873    * increment the tasks counters for a given set of regions. MT safe.
1874    */
1875   protected void incTaskCounters(Collection<byte[]> regions, ServerName sn) {
1876     tasksInProgress.incrementAndGet();
1877 
1878     AtomicInteger serverCnt = taskCounterPerServer.get(sn);
1879     if (serverCnt == null) {
1880       taskCounterPerServer.putIfAbsent(sn, new AtomicInteger());
1881       serverCnt = taskCounterPerServer.get(sn);
1882     }
1883     serverCnt.incrementAndGet();
1884 
1885     for (byte[] regBytes : regions) {
1886       AtomicInteger regionCnt = taskCounterPerRegion.get(regBytes);
1887       if (regionCnt == null) {
1888         regionCnt = new AtomicInteger();
1889         AtomicInteger oldCnt = taskCounterPerRegion.putIfAbsent(regBytes, regionCnt);
1890         if (oldCnt != null) {
1891           regionCnt = oldCnt;
1892         }
1893       }
1894       regionCnt.incrementAndGet();
1895     }
1896   }
1897 
1898   /**
1899    * Decrements the counters for a given region and the region server. MT Safe.
1900    */
1901   protected void decTaskCounters(Collection<byte[]> regions, ServerName sn) {
1902     for (byte[] regBytes : regions) {
1903       AtomicInteger regionCnt = taskCounterPerRegion.get(regBytes);
1904       regionCnt.decrementAndGet();
1905     }
1906 
1907     taskCounterPerServer.get(sn).decrementAndGet();
1908     tasksInProgress.decrementAndGet();
1909     synchronized (tasksInProgress) {
1910       tasksInProgress.notifyAll();
1911     }
1912   }
1913 
1914   /**
1915    * Creates the server error tracker to use inside process.
1916    * Currently, to preserve the main assumption about current retries, and to work well with
1917    * the retry-limit-based calculation, the calculation is local per Process object.
1918    * We may benefit from connection-wide tracking of server errors.
1919    * @return ServerErrorTracker to use, null if there is no ServerErrorTracker on this connection
1920    */
1921   protected ConnectionManager.ServerErrorTracker createServerErrorTracker() {
1922     return new ConnectionManager.ServerErrorTracker(
1923         this.serverTrackerTimeout, this.numTries);
1924   }
1925 
1926   private static boolean isReplicaGet(Row row) {
1927     return (row instanceof Get) && (((Get)row).getConsistency() == Consistency.TIMELINE);
1928   }
1929 
1930   /**
1931    * For manageError. Only used to make logging more clear, we don't actually care why we don't retry.
1932    */
1933   private enum Retry {
1934     YES,
1935     NO_LOCATION_PROBLEM,
1936     NO_NOT_RETRIABLE,
1937     NO_RETRIES_EXHAUSTED,
1938     NO_OTHER_SUCCEEDED
1939   }
1940 }