View Javadoc

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