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     /**
1313      * Called when we receive the result of a server query.
1314      *
1315      * @param multiAction    - the multiAction we sent
1316      * @param server       - the location. It's used as a server name.
1317      * @param responses      - the response, if any
1318      * @param numAttempt     - the attempt
1319      */
1320     private void receiveMultiAction(MultiAction<Row> multiAction,
1321         ServerName server, MultiResponse responses, int numAttempt) {
1322        assert responses != null;
1323 
1324       // Success or partial success
1325       // Analyze detailed results. We can still have individual failures to be redo.
1326       // two specific throwables are managed:
1327       //  - DoNotRetryIOException: we continue to retry for other actions
1328       //  - RegionMovedException: we update the cache with the new region location
1329 
1330       List<Action<Row>> toReplay = new ArrayList<Action<Row>>();
1331       Throwable throwable = null;
1332       int failureCount = 0;
1333       boolean canRetry = true;
1334 
1335       Map<byte[], MultiResponse.RegionResult> results = responses.getResults();
1336       updateStats(server, results);
1337 
1338       int failed = 0, stopped = 0;
1339       // Go by original action.
1340       for (Map.Entry<byte[], List<Action<Row>>> regionEntry : multiAction.actions.entrySet()) {
1341         byte[] regionName = regionEntry.getKey();
1342         Map<Integer, Object> regionResults = results.get(regionName) == null
1343             ?  null : results.get(regionName).result;
1344         if (regionResults == null) {
1345           if (!responses.getExceptions().containsKey(regionName)) {
1346             LOG.error("Server sent us neither results nor exceptions for "
1347                 + Bytes.toStringBinary(regionName));
1348             responses.getExceptions().put(regionName, new RuntimeException("Invalid response"));
1349           }
1350           continue;
1351         }
1352         boolean regionFailureRegistered = false;
1353         for (Action<Row> sentAction : regionEntry.getValue()) {
1354           Object result = regionResults.get(sentAction.getOriginalIndex());
1355           // Failure: retry if it's make sense else update the errors lists
1356           if (result == null || result instanceof Throwable) {
1357             Row row = sentAction.getAction();
1358             throwable = ClientExceptionsUtil.findException(result);
1359             // Register corresponding failures once per server/once per region.
1360             if (!regionFailureRegistered) {
1361               regionFailureRegistered = true;
1362               try {
1363                 connection.updateCachedLocations(
1364                   tableName, regionName, row.getRow(), result, server);
1365               } catch (Throwable ex) {
1366                 // That should never happen, but if it did, we want to make sure
1367                 // we still process errors
1368                 LOG.error("Couldn't update cached region locations: " + ex);
1369               }
1370             }
1371             if (failureCount == 0) {
1372               errorsByServer.reportServerError(server);
1373               // We determine canRetry only once for all calls, after reporting server failure.
1374               canRetry = errorsByServer.canRetryMore(numAttempt);
1375             }
1376             ++failureCount;
1377             Retry retry = manageError(sentAction.getOriginalIndex(), row,
1378                 canRetry ? Retry.YES : Retry.NO_RETRIES_EXHAUSTED, (Throwable) result, server);
1379             if (retry == Retry.YES) {
1380               toReplay.add(sentAction);
1381             } else if (retry == Retry.NO_OTHER_SUCCEEDED) {
1382               ++stopped;
1383             } else {
1384               ++failed;
1385             }
1386           } else {
1387             if (callback != null) {
1388               try {
1389                 //noinspection unchecked
1390                 // TODO: would callback expect a replica region name if it gets one?
1391                 this.callback.update(regionName, sentAction.getAction().getRow(), (CResult) result);
1392               } catch (Throwable t) {
1393                 LOG.error("User callback threw an exception for "
1394                     + Bytes.toStringBinary(regionName) + ", ignoring", t);
1395               }
1396             }
1397             setResult(sentAction, result);
1398           }
1399         }
1400       }
1401 
1402       // The failures global to a region. We will use for multiAction we sent previously to find the
1403       //   actions to replay.
1404       for (Map.Entry<byte[], Throwable> throwableEntry : responses.getExceptions().entrySet()) {
1405         throwable = throwableEntry.getValue();
1406         byte[] region = throwableEntry.getKey();
1407         List<Action<Row>> actions = multiAction.actions.get(region);
1408         if (actions == null || actions.isEmpty()) {
1409           throw new IllegalStateException("Wrong response for the region: " +
1410               HRegionInfo.encodeRegionName(region));
1411         }
1412 
1413         if (failureCount == 0) {
1414           errorsByServer.reportServerError(server);
1415           canRetry = errorsByServer.canRetryMore(numAttempt);
1416         }
1417         if (null == tableName && ClientExceptionsUtil.isMetaClearingException(throwable)) {
1418           // For multi-actions, we don't have a table name, but we want to make sure to clear the
1419           // cache in case there were location-related exceptions. We don't to clear the cache
1420           // for every possible exception that comes through, however.
1421           connection.clearCaches(server);
1422         } else {
1423           try {
1424             connection.updateCachedLocations(
1425               tableName, region, actions.get(0).getAction().getRow(), throwable, server);
1426           } catch (Throwable ex) {
1427             // That should never happen, but if it did, we want to make sure
1428             // we still process errors
1429             LOG.error("Couldn't update cached region locations: " + ex);
1430           }
1431         }
1432         failureCount += actions.size();
1433 
1434         for (Action<Row> action : actions) {
1435           Row row = action.getAction();
1436           Retry retry = manageError(action.getOriginalIndex(), row,
1437               canRetry ? Retry.YES : Retry.NO_RETRIES_EXHAUSTED, throwable, server);
1438           if (retry == Retry.YES) {
1439             toReplay.add(action);
1440           } else if (retry == Retry.NO_OTHER_SUCCEEDED) {
1441             ++stopped;
1442           } else {
1443             ++failed;
1444           }
1445         }
1446       }
1447       if (toReplay.isEmpty()) {
1448         logNoResubmit(server, numAttempt, failureCount, throwable, failed, stopped);
1449       } else {
1450         resubmit(server, toReplay, numAttempt, failureCount, throwable);
1451       }
1452     }
1453 
1454     private String createLog(int numAttempt, int failureCount, int replaySize, ServerName sn,
1455         Throwable error, long backOffTime, boolean willRetry, String startTime,
1456         int failed, int stopped) {
1457       StringBuilder sb = new StringBuilder();
1458       sb.append("#").append(id).append(", table=").append(tableName).append(", ")
1459         .append("attempt=").append(numAttempt)
1460         .append("/").append(numTries).append(" ");
1461 
1462       if (failureCount > 0 || error != null){
1463         sb.append("failed=").append(failureCount).append("ops").append(", last exception: ").
1464             append(error == null ? "null" : error);
1465       } else {
1466         sb.append("succeeded");
1467       }
1468 
1469       sb.append(" on ").append(sn).append(", tracking started ").append(startTime);
1470 
1471       if (willRetry) {
1472         sb.append(", retrying after=").append(backOffTime).append("ms").
1473             append(", replay=").append(replaySize).append("ops");
1474       } else if (failureCount > 0) {
1475         if (stopped > 0) {
1476           sb.append("; not retrying ").append(stopped).append(" due to success from other replica");
1477         }
1478         if (failed > 0) {
1479           sb.append("; not retrying ").append(failed).append(" - final failure");
1480         }
1481 
1482       }
1483 
1484       return sb.toString();
1485     }
1486 
1487     /**
1488      * Sets the non-error result from a particular action.
1489      * @param action Action (request) that the server responded to.
1490      * @param result The result.
1491      */
1492     private void setResult(Action<Row> action, Object result) {
1493       if (result == null) {
1494         throw new RuntimeException("Result cannot be null");
1495       }
1496       ReplicaResultState state = null;
1497       boolean isStale = !RegionReplicaUtil.isDefaultReplica(action.getReplicaId());
1498       int index = action.getOriginalIndex();
1499       if (results == null) {
1500          decActionCounter(index);
1501          return; // Simple case, no replica requests.
1502       } else if ((state = trySetResultSimple(
1503           index, action.getAction(), false, result, null, isStale)) == null) {
1504         return; // Simple case, no replica requests.
1505       }
1506       assert state != null;
1507       // At this point we know that state is set to replica tracking class.
1508       // It could be that someone else is also looking at it; however, we know there can
1509       // only be one state object, and only one thread can set callCount to 0. Other threads
1510       // will either see state with callCount 0 after locking it; or will not see state at all
1511       // we will replace it with the result.
1512       synchronized (state) {
1513         if (state.callCount == 0) {
1514           return; // someone already set the result
1515         }
1516         state.callCount = 0;
1517       }
1518       synchronized (replicaResultLock) {
1519         if (results[index] != state) {
1520           throw new AssertionError("We set the callCount but someone else replaced the result");
1521         }
1522         results[index] = result;
1523       }
1524 
1525       decActionCounter(index);
1526     }
1527 
1528     /**
1529      * Sets the error from a particular action.
1530      * @param index Original action index.
1531      * @param row Original request.
1532      * @param throwable The resulting error.
1533      * @param server The source server.
1534      */
1535     private void setError(int index, Row row, Throwable throwable, ServerName server) {
1536       ReplicaResultState state = null;
1537       if (results == null) {
1538         // Note that we currently cannot have replica requests with null results. So it shouldn't
1539         // happen that multiple replica calls will call dAC for same actions with results == null.
1540         // Only one call per action should be present in this case.
1541         errors.add(throwable, row, server);
1542         decActionCounter(index);
1543         return; // Simple case, no replica requests.
1544       } else if ((state = trySetResultSimple(
1545           index, row, true, throwable, server, false)) == null) {
1546         return; // Simple case, no replica requests.
1547       }
1548       assert state != null;
1549       BatchErrors target = null; // Error will be added to final errors, or temp replica errors.
1550       boolean isActionDone = false;
1551       synchronized (state) {
1552         switch (state.callCount) {
1553           case 0: return; // someone already set the result
1554           case 1: { // All calls failed, we are the last error.
1555             target = errors;
1556             isActionDone = true;
1557             break;
1558           }
1559           default: {
1560             assert state.callCount > 1;
1561             if (state.replicaErrors == null) {
1562               state.replicaErrors = new BatchErrors();
1563             }
1564             target = state.replicaErrors;
1565             break;
1566           }
1567         }
1568         --state.callCount;
1569       }
1570       target.add(throwable, row, server);
1571       if (isActionDone) {
1572         if (state.replicaErrors != null) { // last call, no need to lock
1573           errors.merge(state.replicaErrors);
1574         }
1575         // See setResult for explanations.
1576         synchronized (replicaResultLock) {
1577           if (results[index] != state) {
1578             throw new AssertionError("We set the callCount but someone else replaced the result");
1579           }
1580           results[index] = throwable;
1581         }
1582         decActionCounter(index);
1583       }
1584     }
1585 
1586     /**
1587      * Checks if the action is complete; used on error to prevent needless retries.
1588      * Does not synchronize, assuming element index/field accesses are atomic.
1589      * This is an opportunistic optimization check, doesn't have to be strict.
1590      * @param index Original action index.
1591      * @param row Original request.
1592      */
1593     private boolean isActionComplete(int index, Row row) {
1594       if (!isReplicaGet(row)) return false;
1595       Object resObj = results[index];
1596       return (resObj != null) && (!(resObj instanceof ReplicaResultState)
1597           || ((ReplicaResultState)resObj).callCount == 0);
1598     }
1599 
1600     /**
1601      * Tries to set the result or error for a particular action as if there were no replica calls.
1602      * @return null if successful; replica state if there were in fact replica calls.
1603      */
1604     private ReplicaResultState trySetResultSimple(int index, Row row, boolean isError,
1605         Object result, ServerName server, boolean isFromReplica) {
1606       Object resObj = null;
1607       if (!isReplicaGet(row)) {
1608         if (isFromReplica) {
1609           throw new AssertionError("Unexpected stale result for " + row);
1610         }
1611         results[index] = result;
1612       } else {
1613         synchronized (replicaResultLock) {
1614           if ((resObj = results[index]) == null) {
1615             if (isFromReplica) {
1616               throw new AssertionError("Unexpected stale result for " + row);
1617             }
1618             results[index] = result;
1619           }
1620         }
1621       }
1622 
1623       ReplicaResultState rrs =
1624           (resObj instanceof ReplicaResultState) ? (ReplicaResultState)resObj : null;
1625       if (rrs == null && isError) {
1626         // The resObj is not replica state (null or already set).
1627         errors.add((Throwable)result, row, server);
1628       }
1629 
1630       if (resObj == null) {
1631         // resObj is null - no replica calls were made.
1632         decActionCounter(index);
1633         return null;
1634       }
1635       return rrs;
1636     }
1637 
1638     private void decActionCounter(int index) {
1639       long actionsRemaining = actionsInProgress.decrementAndGet();
1640       if (actionsRemaining < 0) {
1641         String error = buildDetailedErrorMsg("Incorrect actions in progress", index);
1642         throw new AssertionError(error);
1643       } else if (actionsRemaining == 0) {
1644         synchronized (actionsInProgress) {
1645           actionsInProgress.notifyAll();
1646         }
1647       }
1648     }
1649 
1650     private String buildDetailedErrorMsg(String string, int index) {
1651       StringBuilder error = new StringBuilder(string);
1652       error.append("; called for ").
1653         append(index).
1654         append(", actionsInProgress ").
1655         append(actionsInProgress.get()).
1656         append("; replica gets: ");
1657       if (replicaGetIndices != null) {
1658         for (int i = 0; i < replicaGetIndices.length; ++i) {
1659           error.append(replicaGetIndices[i]).append(", ");
1660         }
1661       } else {
1662         error.append(hasAnyReplicaGets ? "all" : "none");
1663       }
1664       error.append("; results ");
1665       if (results != null) {
1666         for (int i = 0; i < results.length; ++i) {
1667           Object o = results[i];
1668           error.append(((o == null) ? "null" : o.toString())).append(", ");
1669         }
1670       }
1671       return error.toString();
1672     }
1673 
1674     @Override
1675     public void waitUntilDone() throws InterruptedIOException {
1676       try {
1677         waitUntilDone(Long.MAX_VALUE);
1678       } catch (InterruptedException iex) {
1679         throw new InterruptedIOException(iex.getMessage());
1680       } finally {
1681         if (callsInProgress != null) {
1682           for (PayloadCarryingServerCallable clb : callsInProgress) {
1683             clb.cancel();
1684           }
1685         }
1686       }
1687     }
1688 
1689     private boolean waitUntilDone(long cutoff) throws InterruptedException {
1690       boolean hasWait = cutoff != Long.MAX_VALUE;
1691       long lastLog = EnvironmentEdgeManager.currentTime();
1692       long currentInProgress;
1693       while (0 != (currentInProgress = actionsInProgress.get())) {
1694         long now = EnvironmentEdgeManager.currentTime();
1695         if (hasWait && (now * 1000L) > cutoff) {
1696           return false;
1697         }
1698         if (!hasWait) { // Only log if wait is infinite.
1699           if (now > lastLog + 10000) {
1700             lastLog = now;
1701             LOG.info("#" + id + ", waiting for " + currentInProgress
1702                 + "  actions to finish on table: " + tableName);
1703             if (currentInProgress <= thresholdToLogUndoneTaskDetails) {
1704               logDetailsOfUndoneTasks(currentInProgress);
1705             }
1706           }
1707         }
1708         synchronized (actionsInProgress) {
1709           if (actionsInProgress.get() == 0) break;
1710           if (!hasWait) {
1711             actionsInProgress.wait(10);
1712           } else {
1713             long waitMicroSecond = Math.min(100000L, (cutoff - now * 1000L));
1714             TimeUnit.MICROSECONDS.timedWait(actionsInProgress, waitMicroSecond);
1715           }
1716         }
1717       }
1718       return true;
1719     }
1720 
1721     @Override
1722     public boolean hasError() {
1723       return errors.hasErrors();
1724     }
1725 
1726     @Override
1727     public List<? extends Row> getFailedOperations() {
1728       return errors.actions;
1729     }
1730 
1731     @Override
1732     public RetriesExhaustedWithDetailsException getErrors() {
1733       return errors.makeException();
1734     }
1735 
1736     @Override
1737     public Object[] getResults() throws InterruptedIOException {
1738       waitUntilDone();
1739       return results;
1740     }
1741 
1742     /**
1743      * Create a callable. Isolated to be easily overridden in the tests.
1744      */
1745     @VisibleForTesting
1746     protected MultiServerCallable<Row> createCallable(final ServerName server,
1747         TableName tableName, final MultiAction<Row> multi) {
1748       return new MultiServerCallable<Row>(connection, tableName, server,
1749           AsyncProcess.this.rpcFactory, multi, rpcTimeout, tracker);
1750     }
1751   }
1752 
1753   private void updateStats(ServerName server, Map<byte[], MultiResponse.RegionResult> results) {
1754     boolean metrics = AsyncProcess.this.connection.getConnectionMetrics() != null;
1755     boolean stats = AsyncProcess.this.connection.getStatisticsTracker() != null;
1756     if (!stats && !metrics) {
1757       return;
1758     }
1759     for (Map.Entry<byte[], MultiResponse.RegionResult> regionStats : results.entrySet()) {
1760       byte[] regionName = regionStats.getKey();
1761       ClientProtos.RegionLoadStats stat = regionStats.getValue().getStat();
1762       ResultStatsUtil.updateStats(AsyncProcess.this.connection.getStatisticsTracker(), server,
1763           regionName, stat);
1764       ResultStatsUtil.updateStats(AsyncProcess.this.connection.getConnectionMetrics(),
1765           server, regionName, stat);
1766     }
1767   }
1768 
1769   protected <CResult> AsyncRequestFutureImpl<CResult> createAsyncRequestFuture(
1770       TableName tableName, List<Action<Row>> actions, long nonceGroup, ExecutorService pool,
1771       Batch.Callback<CResult> callback, Object[] results, boolean needResults,
1772       PayloadCarryingServerCallable callable, int operationTimeout, int rpcTimeout) {
1773     return new AsyncRequestFutureImpl<CResult>(
1774         tableName, actions, nonceGroup, getPool(pool), needResults,
1775         results, callback, callable, operationTimeout, rpcTimeout);
1776   }
1777 
1778   @VisibleForTesting
1779   /** Create AsyncRequestFuture. Isolated to be easily overridden in the tests. */
1780   protected <CResult> AsyncRequestFutureImpl<CResult> createAsyncRequestFuture(
1781       TableName tableName, List<Action<Row>> actions, long nonceGroup, ExecutorService pool,
1782       Batch.Callback<CResult> callback, Object[] results, boolean needResults) {
1783     return createAsyncRequestFuture(
1784         tableName, actions, nonceGroup, pool, callback, results, needResults, null,
1785         operationTimeout, rpcTimeout);
1786   }
1787 
1788   /**
1789    * Create a caller. Isolated to be easily overridden in the tests.
1790    */
1791   @VisibleForTesting
1792   protected RpcRetryingCaller<MultiResponse> createCaller(PayloadCarryingServerCallable callable,
1793       int rpcTimeout) {
1794     return rpcCallerFactory.<MultiResponse> newCaller(rpcTimeout);
1795   }
1796 
1797   @VisibleForTesting
1798   /** Waits until all outstanding tasks are done. Used in tests. */
1799   void waitUntilDone() throws InterruptedIOException {
1800     waitForMaximumCurrentTasks(0, null);
1801   }
1802 
1803   /** Wait until the async does not have more than max tasks in progress. */
1804   private void waitForMaximumCurrentTasks(int max, String tableName)
1805       throws InterruptedIOException {
1806     waitForMaximumCurrentTasks(max, tasksInProgress, id, tableName);
1807   }
1808 
1809   // Break out this method so testable
1810   @VisibleForTesting
1811   void waitForMaximumCurrentTasks(int max, final AtomicLong tasksInProgress, final long id,
1812       String tableName) throws InterruptedIOException {
1813     long lastLog = EnvironmentEdgeManager.currentTime();
1814     long currentInProgress, oldInProgress = Long.MAX_VALUE;
1815     while ((currentInProgress = tasksInProgress.get()) > max) {
1816       if (oldInProgress != currentInProgress) { // Wait for in progress to change.
1817         long now = EnvironmentEdgeManager.currentTime();
1818         if (now > lastLog + 10000) {
1819           lastLog = now;
1820           LOG.info("#" + id + ", waiting for some tasks to finish. Expected max="
1821               + max + ", tasksInProgress=" + currentInProgress +
1822               " hasError=" + hasError() + tableName == null ? "" : ", tableName=" + tableName);
1823           if (currentInProgress <= thresholdToLogUndoneTaskDetails) {
1824             logDetailsOfUndoneTasks(currentInProgress);
1825           }
1826         }
1827       }
1828       oldInProgress = currentInProgress;
1829       try {
1830         synchronized (tasksInProgress) {
1831           if (tasksInProgress.get() == oldInProgress) {
1832             tasksInProgress.wait(10);
1833           }
1834         }
1835       } catch (InterruptedException e) {
1836         throw new InterruptedIOException("#" + id + ", interrupted." +
1837             " currentNumberOfTask=" + currentInProgress);
1838       }
1839     }
1840   }
1841 
1842   private void logDetailsOfUndoneTasks(long taskInProgress) {
1843     ArrayList<ServerName> servers = new ArrayList<ServerName>();
1844     for (Map.Entry<ServerName, AtomicInteger> entry : taskCounterPerServer.entrySet()) {
1845       if (entry.getValue().get() > 0) {
1846         servers.add(entry.getKey());
1847       }
1848     }
1849     LOG.info("Left over " + taskInProgress + " task(s) are processed on server(s): " + servers);
1850     if (taskInProgress <= THRESHOLD_TO_LOG_REGION_DETAILS) {
1851       ArrayList<String> regions = new ArrayList<String>();
1852       for (Map.Entry<byte[], AtomicInteger> entry : taskCounterPerRegion.entrySet()) {
1853         if (entry.getValue().get() > 0) {
1854           regions.add(Bytes.toString(entry.getKey()));
1855         }
1856       }
1857       LOG.info("Regions against which left over task(s) are processed: " + regions);
1858     }
1859   }
1860 
1861   /**
1862    * Only used w/useGlobalErrors ctor argument, for HTable backward compat.
1863    * @return Whether there were any errors in any request since the last time
1864    *          {@link #waitForAllPreviousOpsAndReset(List)} was called, or AP was created.
1865    */
1866   public boolean hasError() {
1867     return globalErrors.hasErrors();
1868   }
1869 
1870   /**
1871    * Only used w/useGlobalErrors ctor argument, for HTable backward compat.
1872    * Waits for all previous operations to finish, and returns errors and (optionally)
1873    * failed operations themselves.
1874    * @param failedRows an optional list into which the rows that failed since the last time
1875    *        {@link #waitForAllPreviousOpsAndReset(List)} was called, or AP was created, are saved.
1876    * @param tableName name of the table
1877    * @return all the errors since the last time {@link #waitForAllPreviousOpsAndReset(List)}
1878    *          was called, or AP was created.
1879    */
1880   public RetriesExhaustedWithDetailsException waitForAllPreviousOpsAndReset(
1881       List<Row> failedRows, String tableName) throws InterruptedIOException {
1882     waitForMaximumCurrentTasks(0, tableName);
1883     if (!globalErrors.hasErrors()) {
1884       return null;
1885     }
1886     if (failedRows != null) {
1887       failedRows.addAll(globalErrors.actions);
1888     }
1889     RetriesExhaustedWithDetailsException result = globalErrors.makeException();
1890     globalErrors.clear();
1891     return result;
1892   }
1893 
1894   /**
1895    * increment the tasks counters for a given set of regions. MT safe.
1896    */
1897   protected void incTaskCounters(Collection<byte[]> regions, ServerName sn) {
1898     tasksInProgress.incrementAndGet();
1899 
1900     AtomicInteger serverCnt = taskCounterPerServer.get(sn);
1901     if (serverCnt == null) {
1902       taskCounterPerServer.putIfAbsent(sn, new AtomicInteger());
1903       serverCnt = taskCounterPerServer.get(sn);
1904     }
1905     serverCnt.incrementAndGet();
1906 
1907     for (byte[] regBytes : regions) {
1908       AtomicInteger regionCnt = taskCounterPerRegion.get(regBytes);
1909       if (regionCnt == null) {
1910         regionCnt = new AtomicInteger();
1911         AtomicInteger oldCnt = taskCounterPerRegion.putIfAbsent(regBytes, regionCnt);
1912         if (oldCnt != null) {
1913           regionCnt = oldCnt;
1914         }
1915       }
1916       regionCnt.incrementAndGet();
1917     }
1918   }
1919 
1920   /**
1921    * Decrements the counters for a given region and the region server. MT Safe.
1922    */
1923   protected void decTaskCounters(Collection<byte[]> regions, ServerName sn) {
1924     for (byte[] regBytes : regions) {
1925       AtomicInteger regionCnt = taskCounterPerRegion.get(regBytes);
1926       regionCnt.decrementAndGet();
1927     }
1928 
1929     taskCounterPerServer.get(sn).decrementAndGet();
1930     tasksInProgress.decrementAndGet();
1931     synchronized (tasksInProgress) {
1932       tasksInProgress.notifyAll();
1933     }
1934   }
1935 
1936   /**
1937    * Creates the server error tracker to use inside process.
1938    * Currently, to preserve the main assumption about current retries, and to work well with
1939    * the retry-limit-based calculation, the calculation is local per Process object.
1940    * We may benefit from connection-wide tracking of server errors.
1941    * @return ServerErrorTracker to use, null if there is no ServerErrorTracker on this connection
1942    */
1943   protected ConnectionManager.ServerErrorTracker createServerErrorTracker() {
1944     return new ConnectionManager.ServerErrorTracker(
1945         this.serverTrackerTimeout, this.numTries);
1946   }
1947 
1948   private static boolean isReplicaGet(Row row) {
1949     return (row instanceof Get) && (((Get)row).getConsistency() == Consistency.TIMELINE);
1950   }
1951 
1952   /**
1953    * For manageError. Only used to make logging more clear, we don't actually care why we don't retry.
1954    */
1955   private enum Retry {
1956     YES,
1957     NO_LOCATION_PROBLEM,
1958     NO_NOT_RETRIABLE,
1959     NO_RETRIES_EXHAUSTED,
1960     NO_OTHER_SUCCEEDED
1961   }
1962 }