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