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  package org.apache.hadoop.hbase.regionserver;
20  
21  import com.google.common.annotations.VisibleForTesting;
22  import com.google.common.cache.Cache;
23  import com.google.common.cache.CacheBuilder;
24  import com.google.protobuf.ByteString;
25  import com.google.protobuf.Message;
26  import com.google.protobuf.RpcController;
27  import com.google.protobuf.ServiceException;
28  import com.google.protobuf.TextFormat;
29  
30  import java.io.FileNotFoundException;
31  import java.io.IOException;
32  import java.io.InterruptedIOException;
33  import java.net.BindException;
34  import java.net.InetSocketAddress;
35  import java.net.UnknownHostException;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collections;
39  import java.util.HashMap;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Map.Entry;
44  import java.util.NavigableMap;
45  import java.util.Set;
46  import java.util.TreeSet;
47  import java.util.concurrent.ConcurrentHashMap;
48  import java.util.concurrent.ConcurrentMap;
49  import java.util.concurrent.TimeUnit;
50  import java.util.concurrent.atomic.AtomicLong;
51  
52  import org.apache.commons.lang.mutable.MutableObject;
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  import org.apache.hadoop.conf.Configuration;
56  import org.apache.hadoop.hbase.Cell;
57  import org.apache.hadoop.hbase.CellScannable;
58  import org.apache.hadoop.hbase.CellScanner;
59  import org.apache.hadoop.hbase.CellUtil;
60  import org.apache.hadoop.hbase.DoNotRetryIOException;
61  import org.apache.hadoop.hbase.DroppedSnapshotException;
62  import org.apache.hadoop.hbase.HBaseIOException;
63  import org.apache.hadoop.hbase.HConstants;
64  import org.apache.hadoop.hbase.HRegionInfo;
65  import org.apache.hadoop.hbase.HTableDescriptor;
66  import org.apache.hadoop.hbase.MetaTableAccessor;
67  import org.apache.hadoop.hbase.MultiActionResultTooLarge;
68  import org.apache.hadoop.hbase.NotServingRegionException;
69  import org.apache.hadoop.hbase.ServerName;
70  import org.apache.hadoop.hbase.TableName;
71  import org.apache.hadoop.hbase.UnknownScannerException;
72  import org.apache.hadoop.hbase.classification.InterfaceAudience;
73  import org.apache.hadoop.hbase.client.Append;
74  import org.apache.hadoop.hbase.client.ConnectionUtils;
75  import org.apache.hadoop.hbase.client.Delete;
76  import org.apache.hadoop.hbase.client.Durability;
77  import org.apache.hadoop.hbase.client.Get;
78  import org.apache.hadoop.hbase.client.Increment;
79  import org.apache.hadoop.hbase.client.Mutation;
80  import org.apache.hadoop.hbase.client.Put;
81  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
82  import org.apache.hadoop.hbase.client.Result;
83  import org.apache.hadoop.hbase.client.RowMutations;
84  import org.apache.hadoop.hbase.client.Scan;
85  import org.apache.hadoop.hbase.client.VersionInfoUtil;
86  import org.apache.hadoop.hbase.conf.ConfigurationObserver;
87  import org.apache.hadoop.hbase.coordination.CloseRegionCoordination;
88  import org.apache.hadoop.hbase.coordination.OpenRegionCoordination;
89  import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException;
90  import org.apache.hadoop.hbase.exceptions.MergeRegionException;
91  import org.apache.hadoop.hbase.exceptions.OperationConflictException;
92  import org.apache.hadoop.hbase.exceptions.OutOfOrderScannerNextException;
93  import org.apache.hadoop.hbase.exceptions.ScannerResetException;
94  import org.apache.hadoop.hbase.filter.ByteArrayComparable;
95  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
96  import org.apache.hadoop.hbase.ipc.HBaseRPCErrorHandler;
97  import org.apache.hadoop.hbase.ipc.HBaseRpcController;
98  import org.apache.hadoop.hbase.ipc.PriorityFunction;
99  import org.apache.hadoop.hbase.ipc.QosPriority;
100 import org.apache.hadoop.hbase.ipc.RpcCallContext;
101 import org.apache.hadoop.hbase.ipc.RpcServer;
102 import org.apache.hadoop.hbase.ipc.RpcServer.BlockingServiceAndInterface;
103 import org.apache.hadoop.hbase.ipc.RpcServerInterface;
104 import org.apache.hadoop.hbase.ipc.ServerNotRunningYetException;
105 import org.apache.hadoop.hbase.ipc.ServerRpcController;
106 import org.apache.hadoop.hbase.master.MasterRpcServices;
107 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
108 import org.apache.hadoop.hbase.protobuf.RequestConverter;
109 import org.apache.hadoop.hbase.protobuf.ResponseConverter;
110 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.AdminService;
111 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.CloseRegionRequest;
112 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.CloseRegionResponse;
113 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.CompactRegionRequest;
114 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.CompactRegionResponse;
115 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.FlushRegionRequest;
116 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.FlushRegionResponse;
117 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetOnlineRegionRequest;
118 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetOnlineRegionResponse;
119 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoRequest;
120 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoResponse;
121 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetServerInfoRequest;
122 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetServerInfoResponse;
123 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetStoreFileRequest;
124 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetStoreFileResponse;
125 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.MergeRegionsRequest;
126 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.MergeRegionsResponse;
127 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.OpenRegionRequest;
128 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.OpenRegionRequest.RegionOpenInfo;
129 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.OpenRegionResponse;
130 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.OpenRegionResponse.RegionOpeningState;
131 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.ReplicateWALEntryRequest;
132 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.ReplicateWALEntryResponse;
133 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.RollWALWriterRequest;
134 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.RollWALWriterResponse;
135 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.SplitRegionRequest;
136 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.SplitRegionResponse;
137 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.StopServerRequest;
138 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.StopServerResponse;
139 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.UpdateConfigurationRequest;
140 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.UpdateConfigurationResponse;
141 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.UpdateFavoredNodesRequest;
142 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.UpdateFavoredNodesResponse;
143 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
144 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WarmupRegionRequest;
145 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WarmupRegionResponse;
146 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos;
147 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Action;
148 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.BulkLoadHFileRequest;
149 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.BulkLoadHFileRequest.FamilyPath;
150 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.BulkLoadHFileResponse;
151 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ClientService;
152 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.Condition;
153 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.CoprocessorServiceRequest;
154 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.CoprocessorServiceResponse;
155 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.GetRequest;
156 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.GetResponse;
157 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiRegionLoadStats;
158 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiRequest;
159 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MultiResponse;
160 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateRequest;
161 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateResponse;
162 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto;
163 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType;
164 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionAction;
165 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.RegionActionResult;
166 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ResultOrException;
167 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest;
168 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanResponse;
169 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.NameBytesPair;
170 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.NameInt64Pair;
171 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
172 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionSpecifier;
173 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType;
174 import org.apache.hadoop.hbase.protobuf.generated.MapReduceProtos.ScanMetrics;
175 import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.RequestHeader;
176 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.BulkLoadDescriptor;
177 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor;
178 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor;
179 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.RegionEventDescriptor;
180 import org.apache.hadoop.hbase.quotas.OperationQuota;
181 import org.apache.hadoop.hbase.quotas.RegionServerQuotaManager;
182 import org.apache.hadoop.hbase.regionserver.Leases.LeaseStillHeldException;
183 import org.apache.hadoop.hbase.regionserver.Region.Operation;
184 import org.apache.hadoop.hbase.regionserver.ScannerContext.LimitScope;
185 import org.apache.hadoop.hbase.regionserver.handler.OpenMetaHandler;
186 import org.apache.hadoop.hbase.regionserver.handler.OpenPriorityRegionHandler;
187 import org.apache.hadoop.hbase.regionserver.handler.OpenRegionHandler;
188 import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
189 import org.apache.hadoop.hbase.security.User;
190 import org.apache.hadoop.hbase.util.Bytes;
191 import org.apache.hadoop.hbase.util.Counter;
192 import org.apache.hadoop.hbase.util.DNS;
193 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
194 import org.apache.hadoop.hbase.util.Pair;
195 import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
196 import org.apache.hadoop.hbase.util.Strings;
197 import org.apache.hadoop.hbase.wal.WAL;
198 import org.apache.hadoop.hbase.wal.WALKey;
199 import org.apache.hadoop.hbase.wal.WALSplitter;
200 import org.apache.hadoop.hbase.zookeeper.ZKSplitLog;
201 import org.apache.zookeeper.KeeperException;
202 
203 import com.google.common.annotations.VisibleForTesting;
204 
205 /**
206  * Implements the regionserver RPC services.
207  */
208 @InterfaceAudience.Private
209 @SuppressWarnings("deprecation")
210 public class RSRpcServices implements HBaseRPCErrorHandler,
211     AdminService.BlockingInterface, ClientService.BlockingInterface, PriorityFunction,
212     ConfigurationObserver {
213   protected static final Log LOG = LogFactory.getLog(RSRpcServices.class);
214 
215   /** RPC scheduler to use for the region server. */
216   public static final String REGION_SERVER_RPC_SCHEDULER_FACTORY_CLASS =
217     "hbase.region.server.rpc.scheduler.factory.class";
218 
219   /**
220    * Minimum allowable time limit delta (in milliseconds) that can be enforced during scans. This
221    * configuration exists to prevent the scenario where a time limit is specified to be so
222    * restrictive that the time limit is reached immediately (before any cells are scanned).
223    */
224   private static final String REGION_SERVER_RPC_MINIMUM_SCAN_TIME_LIMIT_DELTA =
225       "hbase.region.server.rpc.minimum.scan.time.limit.delta";
226   /**
227    * Default value of {@link RSRpcServices#REGION_SERVER_RPC_MINIMUM_SCAN_TIME_LIMIT_DELTA}
228    */
229   private static final long DEFAULT_REGION_SERVER_RPC_MINIMUM_SCAN_TIME_LIMIT_DELTA = 10;
230 
231   /**
232    * Number of rows in a batch operation above which a warning will be logged.
233    */
234   static final String BATCH_ROWS_THRESHOLD_NAME = "hbase.rpc.rows.warning.threshold";
235   /**
236    * Default value of {@link RSRpcServices#BATCH_ROWS_THRESHOLD_NAME}
237    */
238   static final int BATCH_ROWS_THRESHOLD_DEFAULT = 5000;
239 
240   // Count only once for requests with multiple actions like multi/caching-scan/replayBatch
241   final Counter requestCount = new Counter();
242 
243   // Request counter for rpc get
244   final Counter rpcGetRequestCount = new Counter();
245 
246   // Request counter for rpc scan
247   final Counter rpcScanRequestCount = new Counter();
248 
249   // Request counter for rpc multi
250   final Counter rpcMultiRequestCount = new Counter();
251 
252   // Request counter for rpc mutate
253   final Counter rpcMutateRequestCount = new Counter();
254 
255   // Server to handle client requests.
256   final RpcServerInterface rpcServer;
257   final InetSocketAddress isa;
258 
259   private final HRegionServer regionServer;
260   private final long maxScannerResultSize;
261 
262   // The reference to the priority extraction function
263   private final PriorityFunction priority;
264 
265   private ScannerIdGenerator scannerIdGenerator;
266   private final ConcurrentMap<String, RegionScannerHolder> scanners = new ConcurrentHashMap<>();
267   // Hold the name of a closed scanner for a while. This is used to keep compatible for old clients
268   // which may send next or close request to a region scanner which has already been exhausted. The
269   // entries will be removed automatically after scannerLeaseTimeoutPeriod.
270   private final Cache<String, String> closedScanners;
271   /**
272    * The lease timeout period for client scanners (milliseconds).
273    */
274   private final int scannerLeaseTimeoutPeriod;
275 
276   /**
277    * The RPC timeout period (milliseconds)
278    */
279   private final int rpcTimeout;
280 
281   /**
282    * The minimum allowable delta to use for the scan limit
283    */
284   private final long minimumScanTimeLimitDelta;
285 
286   /**
287    * Row size threshold for multi requests above which a warning is logged
288    */
289   private final int rowSizeWarnThreshold;
290 
291   /**
292    * Holder class which holds the RegionScanner, nextCallSeq and RpcCallbacks together.
293    */
294   private static final class RegionScannerHolder {
295 
296     private final AtomicLong nextCallSeq = new AtomicLong(0);
297     private final String scannerName;
298     private final RegionScanner s;
299     private final Region r;
300     private byte[] rowOfLastPartialResult;
301     private boolean needCursor;
302 
303     public RegionScannerHolder(String scannerName, RegionScanner s, Region r, boolean needCursor) {
304       this.scannerName = scannerName;
305       this.s = s;
306       this.r = r;
307       this.needCursor = needCursor;
308     }
309 
310     public long getNextCallSeq() {
311       return nextCallSeq.get();
312     }
313 
314     public boolean incNextCallSeq(long currentSeq) {
315       // Use CAS to prevent multiple scan request running on the same scanner.
316       return nextCallSeq.compareAndSet(currentSeq, currentSeq + 1);
317     }
318   }
319 
320   /**
321    * Instantiated as a scanner lease. If the lease times out, the scanner is
322    * closed
323    */
324   private class ScannerListener implements LeaseListener {
325     private final String scannerName;
326 
327     ScannerListener(final String n) {
328       this.scannerName = n;
329     }
330 
331     @Override
332     public void leaseExpired() {
333       RegionScannerHolder rsh = scanners.remove(this.scannerName);
334       if (rsh != null) {
335         RegionScanner s = rsh.s;
336         LOG.info("Scanner " + this.scannerName + " lease expired on region "
337           + s.getRegionInfo().getRegionNameAsString());
338         Region region = null;
339         try {
340           region = regionServer.getRegion(s.getRegionInfo().getRegionName());
341           if (region != null && region.getCoprocessorHost() != null) {
342             region.getCoprocessorHost().preScannerClose(s);
343           }
344         } catch (IOException e) {
345           LOG.error("Closing scanner for " + s.getRegionInfo().getRegionNameAsString(), e);
346         } finally {
347           try {
348             s.close();
349             if (region != null && region.getCoprocessorHost() != null) {
350               region.getCoprocessorHost().postScannerClose(s);
351             }
352           } catch (IOException e) {
353             LOG.error("Closing scanner for " + s.getRegionInfo().getRegionNameAsString(), e);
354           }
355         }
356       } else {
357         LOG.warn("Scanner " + this.scannerName + " lease expired, but no related" +
358           " scanner found, hence no chance to close that related scanner!");
359       }
360     }
361   }
362 
363   private static ResultOrException getResultOrException(final ClientProtos.Result r,
364                                                         final int index){
365     return getResultOrException(ResponseConverter.buildActionResult(r), index);
366   }
367 
368   private static ResultOrException getResultOrException(final Exception e, final int index) {
369     return getResultOrException(ResponseConverter.buildActionResult(e), index);
370   }
371 
372   private static ResultOrException getResultOrException(
373       final ResultOrException.Builder builder, final int index) {
374     return builder.setIndex(index).build();
375   }
376 
377   /**
378    * Starts the nonce operation for a mutation, if needed.
379    * @param mutation Mutation.
380    * @param nonceGroup Nonce group from the request.
381    * @returns whether to proceed this mutation.
382    */
383   private boolean startNonceOperation(final MutationProto mutation, long nonceGroup)
384       throws IOException, OperationConflictException {
385     if (regionServer.nonceManager == null || !mutation.hasNonce()) return true;
386     boolean canProceed = false;
387     try {
388       canProceed = regionServer.nonceManager.startOperation(
389         nonceGroup, mutation.getNonce(), regionServer);
390     } catch (InterruptedException ex) {
391       throw new InterruptedIOException("Nonce start operation interrupted");
392     }
393     return canProceed;
394   }
395 
396   /**
397    * Ends nonce operation for a mutation, if needed.
398    * @param mutation Mutation.
399    * @param nonceGroup Nonce group from the request. Always 0 in initial implementation.
400    * @param success Whether the operation for this nonce has succeeded.
401    */
402   private void endNonceOperation(final MutationProto mutation,
403       long nonceGroup, boolean success) {
404     if (regionServer.nonceManager != null && mutation.hasNonce()) {
405       regionServer.nonceManager.endOperation(nonceGroup, mutation.getNonce(), success);
406     }
407   }
408 
409   /**
410    * @return True if current call supports cellblocks
411    */
412   private boolean isClientCellBlockSupport() {
413     return isClientCellBlockSupport(RpcServer.getCurrentCall());
414   }
415 
416   private boolean isClientCellBlockSupport(RpcCallContext context) {
417     return context != null && context.isClientCellBlockSupported();
418   }
419 
420   private void addResult(final MutateResponse.Builder builder,
421       final Result result, final HBaseRpcController rpcc) {
422     if (result == null) return;
423     if (isClientCellBlockSupport()) {
424       builder.setResult(ProtobufUtil.toResultNoData(result));
425       rpcc.setCellScanner(result.cellScanner());
426     } else {
427       ClientProtos.Result pbr = ProtobufUtil.toResult(result);
428       builder.setResult(pbr);
429     }
430   }
431 
432   private void addResults(final ScanResponse.Builder builder, final List<Result> results,
433       final HBaseRpcController controller, boolean isDefaultRegion) {
434     builder.setStale(!isDefaultRegion);
435     if (results.isEmpty()) {
436       return;
437     }
438     if (isClientCellBlockSupport()) {
439       for (Result res : results) {
440         builder.addCellsPerResult(res.size());
441         builder.addPartialFlagPerResult(res.mayHaveMoreCellsInRow());
442       }
443       controller.setCellScanner(CellUtil.createCellScanner(results));
444     } else {
445       for (Result res : results) {
446         ClientProtos.Result pbr = ProtobufUtil.toResult(res);
447         builder.addResults(pbr);
448       }
449     }
450   }
451 
452   /**
453    * Mutate a list of rows atomically.
454    *
455    * @param region
456    * @param actions
457    * @param cellScanner if non-null, the mutation data -- the Cell content.
458    * @throws IOException
459    */
460   private void mutateRows(final Region region,
461       final List<ClientProtos.Action> actions,
462       final CellScanner cellScanner, RegionActionResult.Builder builder) throws IOException {
463     int countOfCompleteMutation = 0;
464     try {
465       if (!region.getRegionInfo().isMetaTable()) {
466         regionServer.cacheFlusher.reclaimMemStoreMemory();
467       }
468       RowMutations rm = null;
469       int i = 0;
470       ClientProtos.ResultOrException.Builder resultOrExceptionOrBuilder =
471         ClientProtos.ResultOrException.newBuilder();
472       for (ClientProtos.Action action: actions) {
473         if (action.hasGet()) {
474           throw new DoNotRetryIOException("Atomic put and/or delete only, not a Get=" +
475             action.getGet());
476         }
477         MutationType type = action.getMutation().getMutateType();
478         if (rm == null) {
479           rm = new RowMutations(action.getMutation().getRow().toByteArray());
480         }
481         switch (type) {
482           case PUT:
483             Put put = ProtobufUtil.toPut(action.getMutation(), cellScanner);
484             ++countOfCompleteMutation;
485             checkCellSizeLimit(region, put);
486             rm.add(put);
487             break;
488           case DELETE:
489             Delete delete = ProtobufUtil.toDelete(action.getMutation(), cellScanner);
490             ++countOfCompleteMutation;
491             rm.add(delete);
492             break;
493           default:
494             throw new DoNotRetryIOException("Atomic put and/or delete only, not " + type.name());
495         }
496         // To unify the response format with doNonAtomicRegionMutation and read through client's
497         // AsyncProcess we have to add an empty result instance per operation
498         resultOrExceptionOrBuilder.clear();
499         resultOrExceptionOrBuilder.setIndex(i++);
500         builder.addResultOrException(
501           resultOrExceptionOrBuilder.build());
502       }
503       region.mutateRow(rm);
504     } finally {
505       // Currently, the checkAndMutate isn't supported by batch so it won't mess up the cell scanner
506       // even if the malformed cells are not skipped.
507       for (int i = countOfCompleteMutation; i < actions.size(); ++i) {
508         skipCellsForMutation(actions.get(i), cellScanner);
509       }
510     }
511   }
512 
513   /**
514    * Mutate a list of rows atomically.
515    *
516    * @param region
517    * @param actions
518    * @param cellScanner if non-null, the mutation data -- the Cell content.
519    * @param row
520    * @param family
521    * @param qualifier
522    * @param compareOp
523    * @param comparator @throws IOException
524    */
525   private boolean checkAndRowMutate(final Region region, final List<ClientProtos.Action> actions,
526       final CellScanner cellScanner, byte[] row, byte[] family, byte[] qualifier,
527       CompareOp compareOp, ByteArrayComparable comparator,
528                                     RegionActionResult.Builder builder) throws IOException {
529     int countOfCompleteMutation = 0;
530     try {
531       if (!region.getRegionInfo().isMetaTable()) {
532         regionServer.cacheFlusher.reclaimMemStoreMemory();
533       }
534       RowMutations rm = null;
535       int i = 0;
536       ClientProtos.ResultOrException.Builder resultOrExceptionOrBuilder =
537         ClientProtos.ResultOrException.newBuilder();
538       for (ClientProtos.Action action: actions) {
539         if (action.hasGet()) {
540           throw new DoNotRetryIOException("Atomic put and/or delete only, not a Get=" +
541             action.getGet());
542         }
543         MutationType type = action.getMutation().getMutateType();
544         if (rm == null) {
545           rm = new RowMutations(action.getMutation().getRow().toByteArray());
546         }
547         switch (type) {
548           case PUT:
549             Put put = ProtobufUtil.toPut(action.getMutation(), cellScanner);
550             ++countOfCompleteMutation;
551             checkCellSizeLimit(region, put);
552             rm.add(put);
553             break;
554           case DELETE:
555             Delete delete = ProtobufUtil.toDelete(action.getMutation(), cellScanner);
556             ++countOfCompleteMutation;
557             rm.add(delete);
558             break;
559           default:
560             throw new DoNotRetryIOException("Atomic put and/or delete only, not " + type.name());
561         }
562         // To unify the response format with doNonAtomicRegionMutation and read through client's
563         // AsyncProcess we have to add an empty result instance per operation
564         resultOrExceptionOrBuilder.clear();
565         resultOrExceptionOrBuilder.setIndex(i++);
566         builder.addResultOrException(
567           resultOrExceptionOrBuilder.build());
568       }
569       return region.checkAndRowMutate(row, family, qualifier, compareOp,
570         comparator, rm, Boolean.TRUE);
571     } finally {
572       // Currently, the checkAndMutate isn't supported by batch so it won't mess up the cell scanner
573       // even if the malformed cells are not skipped.
574       for (int i = countOfCompleteMutation; i < actions.size(); ++i) {
575         skipCellsForMutation(actions.get(i), cellScanner);
576       }
577     }
578   }
579 
580   /**
581    * Execute an append mutation.
582    *
583    * @param region
584    * @param mutation
585    * @param cellScanner
586    * @param nonce group
587    * @return result to return to client if default operation should be
588    * bypassed as indicated by RegionObserver, null otherwise
589    * @throws IOException
590    */
591   private Result append(final Region region, final OperationQuota quota,
592       final MutationProto mutation, final CellScanner cellScanner, long nonceGroup)
593       throws IOException {
594     long before = EnvironmentEdgeManager.currentTime();
595     Append append = ProtobufUtil.toAppend(mutation, cellScanner);
596     checkCellSizeLimit(region, append);
597     quota.addMutation(append);
598     Result r = null;
599     if (region.getCoprocessorHost() != null) {
600       r = region.getCoprocessorHost().preAppend(append);
601     }
602     if (r == null) {
603       boolean canProceed = startNonceOperation(mutation, nonceGroup);
604       boolean success = false;
605       try {
606         long nonce = mutation.hasNonce() ? mutation.getNonce() : HConstants.NO_NONCE;
607         if (canProceed) {
608           r = region.append(append, nonceGroup, nonce);
609         } else {
610           // convert duplicate append to get
611           List<Cell> results = region.get(ProtobufUtil.toGet(mutation, cellScanner), false,
612             nonceGroup, nonce);
613           r = Result.create(results);
614         }
615         success = true;
616       } finally {
617         if (canProceed) {
618           endNonceOperation(mutation, nonceGroup, success);
619         }
620       }
621       if (region.getCoprocessorHost() != null) {
622         r = region.getCoprocessorHost().postAppend(append, r);
623       }
624     }
625     if (regionServer.metricsRegionServer != null) {
626       regionServer.metricsRegionServer.updateAppend(
627           region.getTableDesc().getTableName(),
628         EnvironmentEdgeManager.currentTime() - before);
629     }
630     return r;
631   }
632 
633   /**
634    * Execute an increment mutation.
635    *
636    * @param region
637    * @param mutation
638    * @param cellScanner
639    * @param nonce group
640    * @return the Result
641    * @throws IOException
642    */
643   private Result increment(final Region region, final OperationQuota quota,
644       final MutationProto mutation, final CellScanner cellScanner, long nonceGroup)
645       throws IOException {
646     long before = EnvironmentEdgeManager.currentTime();
647     Increment increment = ProtobufUtil.toIncrement(mutation, cellScanner);
648     checkCellSizeLimit(region, increment);
649     quota.addMutation(increment);
650     Result r = null;
651     if (region.getCoprocessorHost() != null) {
652       r = region.getCoprocessorHost().preIncrement(increment);
653     }
654     if (r == null) {
655       boolean canProceed = startNonceOperation(mutation, nonceGroup);
656       boolean success = false;
657       try {
658         long nonce = mutation.hasNonce() ? mutation.getNonce() : HConstants.NO_NONCE;
659         if (canProceed) {
660           r = region.increment(increment, nonceGroup, nonce);
661         } else {
662           // convert duplicate increment to get
663           List<Cell> results = region.get(ProtobufUtil.toGet(mutation, cellScanner), false,
664             nonceGroup, nonce);
665           r = Result.create(results);
666         }
667         success = true;
668       } finally {
669         if (canProceed) {
670           endNonceOperation(mutation, nonceGroup, success);
671         }
672       }
673       if (region.getCoprocessorHost() != null) {
674         r = region.getCoprocessorHost().postIncrement(increment, r);
675       }
676     }
677     if (regionServer.metricsRegionServer != null) {
678       regionServer.metricsRegionServer.updateIncrement(
679           region.getTableDesc().getTableName(),
680           EnvironmentEdgeManager.currentTime() - before);
681     }
682     return r;
683   }
684 
685   /**
686    * Run through the regionMutation <code>rm</code> and per Mutation, do the work, and then when
687    * done, add an instance of a {@link ResultOrException} that corresponds to each Mutation.
688    * @param region
689    * @param actions
690    * @param cellScanner
691    * @param builder
692    * @param cellsToReturn  Could be null. May be allocated in this method.  This is what this
693    * method returns as a 'result'.
694    * @return Return the <code>cellScanner</code> passed
695    */
696   private List<CellScannable> doNonAtomicRegionMutation(final Region region,
697       final OperationQuota quota, final RegionAction actions, final CellScanner cellScanner,
698       final RegionActionResult.Builder builder, List<CellScannable> cellsToReturn, long nonceGroup) {
699     // Gather up CONTIGUOUS Puts and Deletes in this mutations List.  Idea is that rather than do
700     // one at a time, we instead pass them in batch.  Be aware that the corresponding
701     // ResultOrException instance that matches each Put or Delete is then added down in the
702     // doBatchOp call.  We should be staying aligned though the Put and Delete are deferred/batched
703     List<ClientProtos.Action> mutations = null;
704     long maxQuotaResultSize = Math.min(maxScannerResultSize, quota.getReadAvailable());
705     RpcCallContext context = RpcServer.getCurrentCall();
706     IOException sizeIOE = null;
707     Object lastBlock = null;
708     ClientProtos.ResultOrException.Builder resultOrExceptionBuilder = ResultOrException.newBuilder();
709     boolean hasResultOrException = false;
710     for (ClientProtos.Action action : actions.getActionList()) {
711       hasResultOrException = false;
712       resultOrExceptionBuilder.clear();
713       try {
714         Result r = null;
715 
716         if (context != null
717             && context.isRetryImmediatelySupported()
718             && (context.getResponseCellSize() > maxQuotaResultSize
719               || context.getResponseBlockSize() + context.getResponseExceptionSize()
720               > maxQuotaResultSize)) {
721 
722           // We're storing the exception since the exception and reason string won't
723           // change after the response size limit is reached.
724           if (sizeIOE == null ) {
725             // We don't need the stack un-winding do don't throw the exception.
726             // Throwing will kill the JVM's JIT.
727             //
728             // Instead just create the exception and then store it.
729             sizeIOE = new MultiActionResultTooLarge("Max size exceeded"
730                 + " CellSize: " + context.getResponseCellSize()
731                 + " BlockSize: " + context.getResponseBlockSize());
732 
733             // Only report the exception once since there's only one request that
734             // caused the exception. Otherwise this number will dominate the exceptions count.
735             rpcServer.getMetrics().exception(sizeIOE);
736           }
737 
738           // Now that there's an exception is known to be created
739           // use it for the response.
740           //
741           // This will create a copy in the builder.
742           hasResultOrException = true;
743           NameBytesPair pair = ResponseConverter.buildException(sizeIOE);
744           resultOrExceptionBuilder.setException(pair);
745           context.incrementResponseExceptionSize(pair.getSerializedSize());
746           resultOrExceptionBuilder.setIndex(action.getIndex());
747           builder.addResultOrException(resultOrExceptionBuilder.build());
748           skipCellsForMutation(action, cellScanner);
749           continue;
750         }
751         if (action.hasGet()) {
752           long before = EnvironmentEdgeManager.currentTime();
753           try {
754             Get get = ProtobufUtil.toGet(action.getGet());
755             r = region.get(get);
756           } finally {
757             if (regionServer.metricsRegionServer != null) {
758               regionServer.metricsRegionServer.updateGet(
759                   region.getTableDesc().getTableName(),
760                   EnvironmentEdgeManager.currentTime() - before);
761             }
762           }
763         } else if (action.hasServiceCall()) {
764           hasResultOrException = true;
765           try {
766             Message result = execServiceOnRegion(region, action.getServiceCall());
767             ClientProtos.CoprocessorServiceResult.Builder serviceResultBuilder =
768                 ClientProtos.CoprocessorServiceResult.newBuilder();
769             resultOrExceptionBuilder.setServiceResult(
770                 serviceResultBuilder.setValue(
771                   serviceResultBuilder.getValueBuilder()
772                     .setName(result.getClass().getName())
773                     .setValue(result.toByteString())));
774           } catch (IOException ioe) {
775             rpcServer.getMetrics().exception(ioe);
776             NameBytesPair pair = ResponseConverter.buildException(ioe);
777             resultOrExceptionBuilder.setException(pair);
778             context.incrementResponseExceptionSize(pair.getSerializedSize());
779           }
780         } else if (action.hasMutation()) {
781           MutationType type = action.getMutation().getMutateType();
782           if (type != MutationType.PUT && type != MutationType.DELETE && mutations != null &&
783               !mutations.isEmpty()) {
784             // Flush out any Puts or Deletes already collected.
785             doBatchOp(builder, region, quota, mutations, cellScanner);
786             mutations.clear();
787           }
788           switch (type) {
789           case APPEND:
790             r = append(region, quota, action.getMutation(), cellScanner, nonceGroup);
791             break;
792           case INCREMENT:
793             r = increment(region, quota, action.getMutation(), cellScanner,  nonceGroup);
794             break;
795           case PUT:
796           case DELETE:
797             // Collect the individual mutations and apply in a batch
798             if (mutations == null) {
799               mutations = new ArrayList<ClientProtos.Action>(actions.getActionCount());
800             }
801             mutations.add(action);
802             break;
803           default:
804             throw new DoNotRetryIOException("Unsupported mutate type: " + type.name());
805           }
806         } else {
807           throw new HBaseIOException("Unexpected Action type");
808         }
809         if (r != null) {
810           ClientProtos.Result pbResult = null;
811           if (isClientCellBlockSupport()) {
812             pbResult = ProtobufUtil.toResultNoData(r);
813             //  Hard to guess the size here.  Just make a rough guess.
814             if (cellsToReturn == null) {
815               cellsToReturn = new ArrayList<CellScannable>();
816             }
817             cellsToReturn.add(r);
818           } else {
819             pbResult = ProtobufUtil.toResult(r);
820           }
821           lastBlock = addSize(context, r, lastBlock);
822           hasResultOrException = true;
823           resultOrExceptionBuilder.setResult(pbResult);
824         }
825         // Could get to here and there was no result and no exception.  Presumes we added
826         // a Put or Delete to the collecting Mutations List for adding later.  In this
827         // case the corresponding ResultOrException instance for the Put or Delete will be added
828         // down in the doBatchOp method call rather than up here.
829       } catch (IOException ie) {
830         rpcServer.getMetrics().exception(ie);
831         hasResultOrException = true;
832         NameBytesPair pair = ResponseConverter.buildException(ie);
833         resultOrExceptionBuilder.setException(pair);
834         context.incrementResponseExceptionSize(pair.getSerializedSize());
835       }
836       if (hasResultOrException) {
837         // Propagate index.
838         resultOrExceptionBuilder.setIndex(action.getIndex());
839         builder.addResultOrException(resultOrExceptionBuilder.build());
840       }
841     }
842     // Finish up any outstanding mutations
843     if (mutations != null && !mutations.isEmpty()) {
844       doBatchOp(builder, region, quota, mutations, cellScanner);
845     }
846     return cellsToReturn;
847   }
848 
849   private void checkCellSizeLimit(final Region region, final Mutation m) throws IOException {
850     if (!(region instanceof HRegion)) {
851       return;
852     }
853     HRegion r = (HRegion)region;
854     if (r.maxCellSize > 0) {
855       CellScanner cells = m.cellScanner();
856       while (cells.advance()) {
857         int size = CellUtil.estimatedSerializedSizeOf(cells.current());
858         if (size > r.maxCellSize) {
859           String msg = "Cell with size " + size + " exceeds limit of " + r.maxCellSize + " bytes";
860           if (LOG.isDebugEnabled()) {
861             LOG.debug(msg);
862           }
863           throw new DoNotRetryIOException(msg);
864         }
865       }
866     }
867   }
868 
869   /**
870    * Execute a list of Put/Delete mutations.
871    *
872    * @param builder
873    * @param region
874    * @param mutations
875    */
876   private void doBatchOp(final RegionActionResult.Builder builder, final Region region,
877       final OperationQuota quota,
878       final List<ClientProtos.Action> mutations, final CellScanner cells) {
879     Mutation[] mArray = new Mutation[mutations.size()];
880     long before = EnvironmentEdgeManager.currentTime();
881     boolean batchContainsPuts = false, batchContainsDelete = false;
882     try {
883       /** HBASE-17924
884        * mutationActionMap is a map to map the relation between mutations and actions
885        * since mutation array may have been reoredered.In order to return the right
886        * result or exception to the corresponding actions, We need to know which action
887        * is the mutation belong to. We can't sort ClientProtos.Action array, since they
888        * are bonded to cellscanners.
889        */
890       Map<Mutation, ClientProtos.Action> mutationActionMap = new HashMap<Mutation, ClientProtos.Action>();
891       int i = 0;
892       for (ClientProtos.Action action: mutations) {
893         MutationProto m = action.getMutation();
894         Mutation mutation;
895         if (m.getMutateType() == MutationType.PUT) {
896           mutation = ProtobufUtil.toPut(m, cells);
897           batchContainsPuts = true;
898         } else {
899           mutation = ProtobufUtil.toDelete(m, cells);
900           batchContainsDelete = true;
901         }
902         mutationActionMap.put(mutation, action);
903         mArray[i++] = mutation;
904         checkCellSizeLimit(region, mutation);
905         quota.addMutation(mutation);
906       }
907 
908       if (!region.getRegionInfo().isMetaTable()) {
909         regionServer.cacheFlusher.reclaimMemStoreMemory();
910       }
911 
912       // HBASE-17924
913       // sort to improve lock efficiency
914       Arrays.sort(mArray);
915 
916       OperationStatus[] codes = region.batchMutate(mArray, HConstants.NO_NONCE,
917         HConstants.NO_NONCE);
918       for (i = 0; i < codes.length; i++) {
919         Mutation currentMutation = mArray[i];
920         ClientProtos.Action currentAction = mutationActionMap.get(currentMutation);
921         int index = currentAction.getIndex();
922         Exception e = null;
923         switch (codes[i].getOperationStatusCode()) {
924           case BAD_FAMILY:
925             e = new NoSuchColumnFamilyException(codes[i].getExceptionMsg());
926             builder.addResultOrException(getResultOrException(e, index));
927             break;
928 
929           case SANITY_CHECK_FAILURE:
930             e = new FailedSanityCheckException(codes[i].getExceptionMsg());
931             builder.addResultOrException(getResultOrException(e, index));
932             break;
933 
934           default:
935             e = new DoNotRetryIOException(codes[i].getExceptionMsg());
936             builder.addResultOrException(getResultOrException(e, index));
937             break;
938 
939           case SUCCESS:
940             builder.addResultOrException(getResultOrException(
941               ClientProtos.Result.getDefaultInstance(), index));
942             break;
943         }
944       }
945     } catch (IOException ie) {
946       int processedMutationIndex = 0;
947       for (Action mutation : mutations) {
948         // The non-null mArray[i] means the cell scanner has been read.
949         if (mArray[processedMutationIndex++] == null) {
950           skipCellsForMutation(mutation, cells);
951         }
952         builder.addResultOrException(getResultOrException(ie, mutation.getIndex()));
953       }
954     }
955     if (regionServer.metricsRegionServer != null) {
956       long after = EnvironmentEdgeManager.currentTime();
957       if (batchContainsPuts) {
958         regionServer.metricsRegionServer.updatePutBatch(
959             region.getTableDesc().getTableName(), after - before);
960       }
961       if (batchContainsDelete) {
962         regionServer.metricsRegionServer.updateDeleteBatch(
963             region.getTableDesc().getTableName(), after - before);
964       }
965     }
966   }
967 
968   /**
969    * Execute a list of Put/Delete mutations. The function returns OperationStatus instead of
970    * constructing MultiResponse to save a possible loop if caller doesn't need MultiResponse.
971    * @param region
972    * @param mutations
973    * @param replaySeqId
974    * @return an array of OperationStatus which internally contains the OperationStatusCode and the
975    *         exceptionMessage if any
976    * @throws IOException
977    */
978   private OperationStatus [] doReplayBatchOp(final Region region,
979       final List<WALSplitter.MutationReplay> mutations, long replaySeqId) throws IOException {
980     long before = EnvironmentEdgeManager.currentTime();
981     boolean batchContainsPuts = false, batchContainsDelete = false;
982     try {
983       for (Iterator<WALSplitter.MutationReplay> it = mutations.iterator(); it.hasNext();) {
984         WALSplitter.MutationReplay m = it.next();
985 
986         if (m.type == MutationType.PUT) {
987           batchContainsPuts = true;
988         } else {
989           batchContainsDelete = true;
990         }
991 
992         NavigableMap<byte[], List<Cell>> map = m.mutation.getFamilyCellMap();
993         List<Cell> metaCells = map.get(WALEdit.METAFAMILY);
994         if (metaCells != null && !metaCells.isEmpty()) {
995           for (Cell metaCell : metaCells) {
996             CompactionDescriptor compactionDesc = WALEdit.getCompaction(metaCell);
997             boolean isDefaultReplica = RegionReplicaUtil.isDefaultReplica(region.getRegionInfo());
998             HRegion hRegion = (HRegion)region;
999             if (compactionDesc != null) {
1000               // replay the compaction. Remove the files from stores only if we are the primary
1001               // region replica (thus own the files)
1002               hRegion.replayWALCompactionMarker(compactionDesc, !isDefaultReplica, isDefaultReplica,
1003                 replaySeqId);
1004               continue;
1005             }
1006             FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(metaCell);
1007             if (flushDesc != null && !isDefaultReplica) {
1008               hRegion.replayWALFlushMarker(flushDesc, replaySeqId);
1009               continue;
1010             }
1011             RegionEventDescriptor regionEvent = WALEdit.getRegionEventDescriptor(metaCell);
1012             if (regionEvent != null && !isDefaultReplica) {
1013               hRegion.replayWALRegionEventMarker(regionEvent);
1014               continue;
1015             }
1016             BulkLoadDescriptor bulkLoadEvent = WALEdit.getBulkLoadDescriptor(metaCell);
1017             if (bulkLoadEvent != null) {
1018               hRegion.replayWALBulkLoadEventMarker(bulkLoadEvent);
1019               continue;
1020             }
1021           }
1022           it.remove();
1023         }
1024       }
1025       requestCount.increment();
1026       if (!region.getRegionInfo().isMetaTable()) {
1027         regionServer.cacheFlusher.reclaimMemStoreMemory();
1028       }
1029       return region.batchReplay(mutations.toArray(
1030         new WALSplitter.MutationReplay[mutations.size()]), replaySeqId);
1031     } finally {
1032       if (regionServer.metricsRegionServer != null) {
1033         long after = EnvironmentEdgeManager.currentTime();
1034         if (batchContainsPuts) {
1035           regionServer.metricsRegionServer.updatePutBatch(
1036               region.getTableDesc().getTableName(), after - before);
1037         }
1038         if (batchContainsDelete) {
1039           regionServer.metricsRegionServer.updateDeleteBatch(
1040               region.getTableDesc().getTableName(), after - before);
1041         }
1042       }
1043     }
1044   }
1045 
1046   private void closeAllScanners() {
1047     // Close any outstanding scanners. Means they'll get an UnknownScanner
1048     // exception next time they come in.
1049     for (Map.Entry<String, RegionScannerHolder> e : scanners.entrySet()) {
1050       try {
1051         e.getValue().s.close();
1052       } catch (IOException ioe) {
1053         LOG.warn("Closing scanner " + e.getKey(), ioe);
1054       }
1055     }
1056   }
1057 
1058   // Exposed for testing
1059   static interface LogDelegate {
1060     void logBatchWarning(String firstRegionName, int sum, int rowSizeWarnThreshold);
1061   }
1062 
1063   private static LogDelegate DEFAULT_LOG_DELEGATE = new LogDelegate() {
1064     @Override
1065     public void logBatchWarning(String firstRegionName, int sum, int rowSizeWarnThreshold) {
1066       if (LOG.isWarnEnabled()) {
1067         LOG.warn("Large batch operation detected (greater than " + rowSizeWarnThreshold
1068             + ") (HBASE-18023)." + " Requested Number of Rows: " + sum + " Client: "
1069             + RpcServer.getRequestUserName() + "/" + RpcServer.getRemoteAddress()
1070             + " first region in multi=" + firstRegionName);
1071       }
1072     }
1073   };
1074 
1075   private final LogDelegate ld;
1076 
1077   public RSRpcServices(HRegionServer rs) throws IOException {
1078     this(rs, DEFAULT_LOG_DELEGATE);
1079   }
1080 
1081   // Directly invoked only for testing
1082   RSRpcServices(HRegionServer rs, LogDelegate ld) throws IOException {
1083     this.ld = ld;
1084     regionServer = rs;
1085     rowSizeWarnThreshold = rs.conf.getInt(BATCH_ROWS_THRESHOLD_NAME, BATCH_ROWS_THRESHOLD_DEFAULT);
1086     RpcSchedulerFactory rpcSchedulerFactory;
1087     try {
1088       Class<?> rpcSchedulerFactoryClass = rs.conf.getClass(
1089           REGION_SERVER_RPC_SCHEDULER_FACTORY_CLASS,
1090           SimpleRpcSchedulerFactory.class);
1091       rpcSchedulerFactory = (RpcSchedulerFactory)
1092           rpcSchedulerFactoryClass.getDeclaredConstructor().newInstance();
1093     } catch (Exception e) {
1094       throw new IllegalArgumentException(e);
1095     }
1096     // Server to handle client requests.
1097     InetSocketAddress initialIsa;
1098     InetSocketAddress bindAddress;
1099     if(this instanceof MasterRpcServices) {
1100       String hostname = getHostname(rs.conf, true);
1101       int port = rs.conf.getInt(HConstants.MASTER_PORT, HConstants.DEFAULT_MASTER_PORT);
1102       // Creation of a HSA will force a resolve.
1103       initialIsa = new InetSocketAddress(hostname, port);
1104       bindAddress = new InetSocketAddress(rs.conf.get("hbase.master.ipc.address", hostname), port);
1105     } else {
1106       String hostname = getHostname(rs.conf, false);
1107       int port = rs.conf.getInt(HConstants.REGIONSERVER_PORT,
1108         HConstants.DEFAULT_REGIONSERVER_PORT);
1109       // Creation of a HSA will force a resolve.
1110       initialIsa = new InetSocketAddress(hostname, port);
1111       bindAddress = new InetSocketAddress(
1112         rs.conf.get("hbase.regionserver.ipc.address", hostname), port);
1113     }
1114     if (initialIsa.getAddress() == null) {
1115       throw new IllegalArgumentException("Failed resolve of " + initialIsa);
1116     }
1117     priority = createPriority();
1118     String name = rs.getProcessName() + "/" + initialIsa.toString();
1119     // Set how many times to retry talking to another server over HConnection.
1120     ConnectionUtils.setServerSideHConnectionRetriesConfig(rs.conf, name, LOG);
1121     try {
1122       rpcServer = new RpcServer(rs, name, getServices(),
1123           bindAddress, // use final bindAddress for this server.
1124           rs.conf,
1125           rpcSchedulerFactory.create(rs.conf, this, rs));
1126       rpcServer.setRsRpcServices(this);
1127     } catch (BindException be) {
1128       String configName = (this instanceof MasterRpcServices) ? HConstants.MASTER_PORT :
1129           HConstants.REGIONSERVER_PORT;
1130       throw new IOException(be.getMessage() + ". To switch ports use the '" + configName +
1131           "' configuration property.", be.getCause() != null ? be.getCause() : be);
1132     }
1133 
1134     scannerLeaseTimeoutPeriod = rs.conf.getInt(
1135       HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD,
1136       HConstants.DEFAULT_HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD);
1137     maxScannerResultSize = rs.conf.getLong(
1138       HConstants.HBASE_SERVER_SCANNER_MAX_RESULT_SIZE_KEY,
1139       HConstants.DEFAULT_HBASE_SERVER_SCANNER_MAX_RESULT_SIZE);
1140     rpcTimeout = rs.conf.getInt(
1141       HConstants.HBASE_RPC_TIMEOUT_KEY,
1142       HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
1143     minimumScanTimeLimitDelta = rs.conf.getLong(
1144       REGION_SERVER_RPC_MINIMUM_SCAN_TIME_LIMIT_DELTA,
1145       DEFAULT_REGION_SERVER_RPC_MINIMUM_SCAN_TIME_LIMIT_DELTA);
1146 
1147     InetSocketAddress address = rpcServer.getListenerAddress();
1148     if (address == null) {
1149       throw new IOException("Listener channel is closed");
1150     }
1151     // Set our address, however we need the final port that was given to rpcServer
1152     isa = new InetSocketAddress(initialIsa.getHostName(), address.getPort());
1153     rpcServer.setErrorHandler(this);
1154     rs.setName(name);
1155 
1156     closedScanners = CacheBuilder.newBuilder()
1157         .expireAfterAccess(scannerLeaseTimeoutPeriod, TimeUnit.MILLISECONDS).build();
1158   }
1159 
1160   @Override
1161   public void onConfigurationChange(Configuration newConf) {
1162     if (rpcServer instanceof ConfigurationObserver) {
1163       ((ConfigurationObserver)rpcServer).onConfigurationChange(newConf);
1164     }
1165   }
1166 
1167   protected PriorityFunction createPriority() {
1168     return new AnnotationReadingPriorityFunction(this);
1169   }
1170 
1171   public static String getHostname(Configuration conf, boolean isMaster)
1172       throws UnknownHostException {
1173     String hostname = conf.get(isMaster? HRegionServer.MASTER_HOSTNAME_KEY :
1174       HRegionServer.RS_HOSTNAME_KEY);
1175     if (hostname == null || hostname.isEmpty()) {
1176       String masterOrRS = isMaster ? "master" : "regionserver";
1177       return Strings.domainNamePointerToHostName(DNS.getDefaultHost(
1178         conf.get("hbase." + masterOrRS + ".dns.interface", "default"),
1179         conf.get("hbase." + masterOrRS + ".dns.nameserver", "default")));
1180     } else {
1181       LOG.info("hostname is configured to be " + hostname);
1182       return hostname;
1183     }
1184   }
1185 
1186   public
1187   RegionScanner getScanner(long scannerId) {
1188     String scannerIdString = Long.toString(scannerId);
1189     RegionScannerHolder scannerHolder = scanners.get(scannerIdString);
1190     if (scannerHolder != null) {
1191       return scannerHolder.s;
1192     }
1193     return null;
1194   }
1195 
1196   public String getScanDetailsWithId(long scannerId) {
1197     RegionScanner scanner = getScanner(scannerId);
1198     if (scanner == null) {
1199       return null;
1200     }
1201     StringBuilder builder = new StringBuilder();
1202     builder.append("table: ").append(scanner.getRegionInfo().getTable().getNameAsString());
1203     builder.append(" region: ").append(scanner.getRegionInfo().getRegionNameAsString());
1204     return builder.toString();
1205   }
1206 
1207   /**
1208    * Get the vtime associated with the scanner.
1209    * Currently the vtime is the number of "next" calls.
1210    */
1211   long getScannerVirtualTime(long scannerId) {
1212     String scannerIdString = Long.toString(scannerId);
1213     RegionScannerHolder scannerHolder = scanners.get(scannerIdString);
1214     if (scannerHolder != null) {
1215       return scannerHolder.getNextCallSeq();
1216     }
1217     return 0L;
1218   }
1219 
1220   /**
1221    * Method to account for the size of retained cells and retained data blocks.
1222    * @return an object that represents the last referenced block from this response.
1223    */
1224   Object addSize(RpcCallContext context, Result r, Object lastBlock) {
1225     if (context != null && r != null && !r.isEmpty()) {
1226       for (Cell c : r.rawCells()) {
1227         context.incrementResponseCellSize(CellUtil.estimatedHeapSizeOf(c));
1228         // We're using the last block being the same as the current block as
1229         // a proxy for pointing to a new block. This won't be exact.
1230         // If there are multiple gets that bounce back and forth
1231         // Then it's possible that this will over count the size of
1232         // referenced blocks. However it's better to over count and
1233         // use two RPC's than to OOME the RegionServer.
1234         byte[] valueArray = c.getValueArray();
1235         if (valueArray != lastBlock) {
1236           context.incrementResponseBlockSize(valueArray.length);
1237           lastBlock = valueArray;
1238         }
1239       }
1240     }
1241     return lastBlock;
1242   }
1243 
1244   private RegionScannerHolder addScanner(String scannerName, RegionScanner s, Region r,
1245       boolean needCursor) throws LeaseStillHeldException {
1246     regionServer.leases.createLease(scannerName, this.scannerLeaseTimeoutPeriod,
1247       new ScannerListener(scannerName));
1248     RegionScannerHolder rsh = new RegionScannerHolder(scannerName, s, r, needCursor);
1249     RegionScannerHolder existing = scanners.putIfAbsent(scannerName, rsh);
1250     assert existing == null : "scannerId must be unique within regionserver's whole lifecycle!";
1251     return rsh;
1252   }
1253 
1254   /**
1255    * Find the HRegion based on a region specifier
1256    *
1257    * @param regionSpecifier the region specifier
1258    * @return the corresponding region
1259    * @throws IOException if the specifier is not null,
1260    *    but failed to find the region
1261    */
1262   @VisibleForTesting
1263   public Region getRegion(
1264       final RegionSpecifier regionSpecifier) throws IOException {
1265     ByteString value = regionSpecifier.getValue();
1266     RegionSpecifierType type = regionSpecifier.getType();
1267     switch (type) {
1268       case REGION_NAME:
1269         byte[] regionName = value.toByteArray();
1270         String encodedRegionName = HRegionInfo.encodeRegionName(regionName);
1271         return regionServer.getRegionByEncodedName(regionName, encodedRegionName);
1272       case ENCODED_REGION_NAME:
1273         return regionServer.getRegionByEncodedName(value.toStringUtf8());
1274       default:
1275         throw new DoNotRetryIOException(
1276           "Unsupported region specifier type: " + type);
1277     }
1278   }
1279 
1280   @VisibleForTesting
1281   public PriorityFunction getPriority() {
1282     return priority;
1283   }
1284 
1285   @VisibleForTesting
1286   public Configuration getConfiguration() {
1287     return regionServer.getConfiguration();
1288   }
1289 
1290   private RegionServerQuotaManager getQuotaManager() {
1291     return regionServer.getRegionServerQuotaManager();
1292   }
1293 
1294   void start() {
1295     this.scannerIdGenerator = new ScannerIdGenerator(this.regionServer.serverName);
1296     rpcServer.start();
1297   }
1298 
1299   void stop() {
1300     closeAllScanners();
1301     rpcServer.stop();
1302   }
1303 
1304   /**
1305    * Called to verify that this server is up and running.
1306    *
1307    * @throws IOException
1308    */
1309   protected void checkOpen() throws IOException {
1310     if (regionServer.isAborted()) {
1311       throw new RegionServerAbortedException("Server " + regionServer.serverName + " aborting");
1312     }
1313     if (regionServer.isStopped()) {
1314       throw new RegionServerStoppedException("Server " + regionServer.serverName + " stopping");
1315     }
1316     if (!regionServer.fsOk) {
1317       throw new RegionServerStoppedException("File system not available");
1318     }
1319     if (!regionServer.isOnline()) {
1320       throw new ServerNotRunningYetException("Server " + regionServer.serverName
1321           + " is not running yet");
1322     }
1323   }
1324 
1325   /**
1326    * @return list of blocking services and their security info classes that this server supports
1327    */
1328   protected List<BlockingServiceAndInterface> getServices() {
1329     List<BlockingServiceAndInterface> bssi = new ArrayList<BlockingServiceAndInterface>(2);
1330     bssi.add(new BlockingServiceAndInterface(
1331       ClientService.newReflectiveBlockingService(this),
1332       ClientService.BlockingInterface.class));
1333     bssi.add(new BlockingServiceAndInterface(
1334       AdminService.newReflectiveBlockingService(this),
1335       AdminService.BlockingInterface.class));
1336     return bssi;
1337   }
1338 
1339   public InetSocketAddress getSocketAddress() {
1340     return isa;
1341   }
1342 
1343   @Override
1344   public int getPriority(RequestHeader header, Message param, User user) {
1345     return priority.getPriority(header, param, user);
1346   }
1347 
1348   @Override
1349   public long getDeadline(RequestHeader header, Message param) {
1350     return priority.getDeadline(header, param);
1351   }
1352 
1353   /*
1354    * Check if an OOME and, if so, abort immediately to avoid creating more objects.
1355    *
1356    * @param e
1357    *
1358    * @return True if we OOME'd and are aborting.
1359    */
1360   @Override
1361   public boolean checkOOME(final Throwable e) {
1362     return exitIfOOME(e);
1363   }
1364 
1365   public static boolean exitIfOOME(final Throwable e ){
1366     boolean stop = false;
1367     try {
1368       if (e instanceof OutOfMemoryError
1369           || (e.getCause() != null && e.getCause() instanceof OutOfMemoryError)
1370           || (e.getMessage() != null && e.getMessage().contains(
1371               "java.lang.OutOfMemoryError"))) {
1372         stop = true;
1373         LOG.fatal("Run out of memory; " + RSRpcServices.class.getSimpleName()
1374           + " will abort itself immediately", e);
1375       }
1376     } finally {
1377       if (stop) {
1378         Runtime.getRuntime().halt(1);
1379       }
1380     }
1381     return stop;
1382   }
1383 
1384   /**
1385    * Close a region on the region server.
1386    *
1387    * @param controller the RPC controller
1388    * @param request the request
1389    * @throws ServiceException
1390    */
1391   @Override
1392   @QosPriority(priority=HConstants.ADMIN_QOS)
1393   public CloseRegionResponse closeRegion(final RpcController controller,
1394       final CloseRegionRequest request) throws ServiceException {
1395     final ServerName sn = (request.hasDestinationServer() ?
1396       ProtobufUtil.toServerName(request.getDestinationServer()) : null);
1397 
1398     try {
1399       checkOpen();
1400       if (request.hasServerStartCode()) {
1401         // check that we are the same server that this RPC is intended for.
1402         long serverStartCode = request.getServerStartCode();
1403         if (regionServer.serverName.getStartcode() !=  serverStartCode) {
1404           throw new ServiceException(new DoNotRetryIOException("This RPC was intended for a " +
1405               "different server with startCode: " + serverStartCode + ", this server is: "
1406               + regionServer.serverName));
1407         }
1408       }
1409       final String encodedRegionName = ProtobufUtil.getRegionEncodedName(request.getRegion());
1410 
1411       requestCount.increment();
1412       LOG.info("Close " + encodedRegionName + ", moving to " + sn);
1413       CloseRegionCoordination.CloseRegionDetails crd = regionServer.getCoordinatedStateManager()
1414         .getCloseRegionCoordination().parseFromProtoRequest(request);
1415 
1416       boolean closed = regionServer.closeRegion(encodedRegionName, false, crd, sn);
1417       CloseRegionResponse.Builder builder = CloseRegionResponse.newBuilder().setClosed(closed);
1418       return builder.build();
1419     } catch (IOException ie) {
1420       throw new ServiceException(ie);
1421     }
1422   }
1423 
1424   /**
1425    * Compact a region on the region server.
1426    *
1427    * @param controller the RPC controller
1428    * @param request the request
1429    * @throws ServiceException
1430    */
1431   @Override
1432   @QosPriority(priority=HConstants.ADMIN_QOS)
1433   public CompactRegionResponse compactRegion(final RpcController controller,
1434       final CompactRegionRequest request) throws ServiceException {
1435     try {
1436       checkOpen();
1437       requestCount.increment();
1438       Region region = getRegion(request.getRegion());
1439       region.startRegionOperation(Operation.COMPACT_REGION);
1440       LOG.info("Compacting " + region.getRegionInfo().getRegionNameAsString());
1441       boolean major = false;
1442       byte [] family = null;
1443       Store store = null;
1444       if (request.hasFamily()) {
1445         family = request.getFamily().toByteArray();
1446         store = region.getStore(family);
1447         if (store == null) {
1448           throw new ServiceException(new IOException("column family " + Bytes.toString(family)
1449             + " does not exist in region " + region.getRegionInfo().getRegionNameAsString()));
1450         }
1451       }
1452       if (request.hasMajor()) {
1453         major = request.getMajor();
1454       }
1455       if (major) {
1456         if (family != null) {
1457           store.triggerMajorCompaction();
1458         } else {
1459           region.triggerMajorCompaction();
1460         }
1461       }
1462 
1463       String familyLogMsg = (family != null)?" for column family: " + Bytes.toString(family):"";
1464       if (LOG.isTraceEnabled()) {
1465         LOG.trace("User-triggered compaction requested for region "
1466           + region.getRegionInfo().getRegionNameAsString() + familyLogMsg);
1467       }
1468       String log = "User-triggered " + (major ? "major " : "") + "compaction" + familyLogMsg;
1469       if(family != null) {
1470         regionServer.compactSplitThread.requestCompaction(region, store, log,
1471           Store.PRIORITY_USER, null, RpcServer.getRequestUser());
1472       } else {
1473         regionServer.compactSplitThread.requestCompaction(region, log,
1474           Store.PRIORITY_USER, null, RpcServer.getRequestUser());
1475       }
1476       return CompactRegionResponse.newBuilder().build();
1477     } catch (IOException ie) {
1478       throw new ServiceException(ie);
1479     }
1480   }
1481 
1482   /**
1483    * Flush a region on the region server.
1484    *
1485    * @param controller the RPC controller
1486    * @param request the request
1487    * @throws ServiceException
1488    */
1489   @Override
1490   @QosPriority(priority=HConstants.ADMIN_QOS)
1491   public FlushRegionResponse flushRegion(final RpcController controller,
1492       final FlushRegionRequest request) throws ServiceException {
1493     try {
1494       checkOpen();
1495       requestCount.increment();
1496       Region region = getRegion(request.getRegion());
1497       LOG.info("Flushing " + region.getRegionInfo().getRegionNameAsString());
1498       boolean shouldFlush = true;
1499       if (request.hasIfOlderThanTs()) {
1500         shouldFlush = region.getEarliestFlushTimeForAllStores() < request.getIfOlderThanTs();
1501       }
1502       FlushRegionResponse.Builder builder = FlushRegionResponse.newBuilder();
1503       if (shouldFlush) {
1504         boolean writeFlushWalMarker =  request.hasWriteFlushWalMarker() ?
1505             request.getWriteFlushWalMarker() : false;
1506         // Go behind the curtain so we can manage writing of the flush WAL marker
1507         HRegion.FlushResultImpl flushResult = (HRegion.FlushResultImpl)
1508             ((HRegion)region).flushcache(true, writeFlushWalMarker);
1509         boolean compactionNeeded = flushResult.isCompactionNeeded();
1510         if (compactionNeeded) {
1511           regionServer.compactSplitThread.requestSystemCompaction(region,
1512             "Compaction through user triggered flush");
1513         }
1514         builder.setFlushed(flushResult.isFlushSucceeded());
1515         builder.setWroteFlushWalMarker(flushResult.wroteFlushWalMarker);
1516       }
1517       builder.setLastFlushTime(region.getEarliestFlushTimeForAllStores());
1518       return builder.build();
1519     } catch (DroppedSnapshotException ex) {
1520       // Cache flush can fail in a few places. If it fails in a critical
1521       // section, we get a DroppedSnapshotException and a replay of wal
1522       // is required. Currently the only way to do this is a restart of
1523       // the server.
1524       regionServer.abort("Replay of WAL required. Forcing server shutdown", ex);
1525       throw new ServiceException(ex);
1526     } catch (IOException ie) {
1527       throw new ServiceException(ie);
1528     }
1529   }
1530 
1531   @Override
1532   @QosPriority(priority=HConstants.ADMIN_QOS)
1533   public GetOnlineRegionResponse getOnlineRegion(final RpcController controller,
1534       final GetOnlineRegionRequest request) throws ServiceException {
1535     try {
1536       checkOpen();
1537       requestCount.increment();
1538       Map<String, Region> onlineRegions = regionServer.onlineRegions;
1539       List<HRegionInfo> list = new ArrayList<HRegionInfo>(onlineRegions.size());
1540       for (Region region: onlineRegions.values()) {
1541         list.add(region.getRegionInfo());
1542       }
1543       Collections.sort(list);
1544       return ResponseConverter.buildGetOnlineRegionResponse(list);
1545     } catch (IOException ie) {
1546       throw new ServiceException(ie);
1547     }
1548   }
1549 
1550   @Override
1551   @QosPriority(priority=HConstants.ADMIN_QOS)
1552   public GetRegionInfoResponse getRegionInfo(final RpcController controller,
1553       final GetRegionInfoRequest request) throws ServiceException {
1554     try {
1555       checkOpen();
1556       requestCount.increment();
1557       Region region = getRegion(request.getRegion());
1558       HRegionInfo info = region.getRegionInfo();
1559       GetRegionInfoResponse.Builder builder = GetRegionInfoResponse.newBuilder();
1560       builder.setRegionInfo(HRegionInfo.convert(info));
1561       if (request.hasCompactionState() && request.getCompactionState()) {
1562         builder.setCompactionState(region.getCompactionState());
1563       }
1564       builder.setIsRecovering(region.isRecovering());
1565       return builder.build();
1566     } catch (IOException ie) {
1567       throw new ServiceException(ie);
1568     }
1569   }
1570 
1571   /**
1572    * Get some information of the region server.
1573    *
1574    * @param controller the RPC controller
1575    * @param request the request
1576    * @throws ServiceException
1577    */
1578   @Override
1579   @QosPriority(priority=HConstants.ADMIN_QOS)
1580   public GetServerInfoResponse getServerInfo(final RpcController controller,
1581       final GetServerInfoRequest request) throws ServiceException {
1582     try {
1583       checkOpen();
1584     } catch (IOException ie) {
1585       throw new ServiceException(ie);
1586     }
1587     requestCount.increment();
1588     int infoPort = regionServer.infoServer != null ? regionServer.infoServer.getPort() : -1;
1589     return ResponseConverter.buildGetServerInfoResponse(regionServer.serverName, infoPort);
1590   }
1591 
1592   @Override
1593   @QosPriority(priority=HConstants.ADMIN_QOS)
1594   public GetStoreFileResponse getStoreFile(final RpcController controller,
1595       final GetStoreFileRequest request) throws ServiceException {
1596     try {
1597       checkOpen();
1598       Region region = getRegion(request.getRegion());
1599       requestCount.increment();
1600       Set<byte[]> columnFamilies;
1601       if (request.getFamilyCount() == 0) {
1602         columnFamilies = region.getTableDesc().getFamiliesKeys();
1603       } else {
1604         columnFamilies = new TreeSet<byte[]>(Bytes.BYTES_RAWCOMPARATOR);
1605         for (ByteString cf: request.getFamilyList()) {
1606           columnFamilies.add(cf.toByteArray());
1607         }
1608       }
1609       int nCF = columnFamilies.size();
1610       List<String>  fileList = region.getStoreFileList(
1611         columnFamilies.toArray(new byte[nCF][]));
1612       GetStoreFileResponse.Builder builder = GetStoreFileResponse.newBuilder();
1613       builder.addAllStoreFile(fileList);
1614       return builder.build();
1615     } catch (IOException ie) {
1616       throw new ServiceException(ie);
1617     }
1618   }
1619 
1620   /**
1621    * Merge regions on the region server.
1622    *
1623    * @param controller the RPC controller
1624    * @param request the request
1625    * @return merge regions response
1626    * @throws ServiceException
1627    */
1628   @Override
1629   @QosPriority(priority = HConstants.ADMIN_QOS)
1630   public MergeRegionsResponse mergeRegions(final RpcController controller,
1631       final MergeRegionsRequest request) throws ServiceException {
1632     try {
1633       checkOpen();
1634       requestCount.increment();
1635       Region regionA = getRegion(request.getRegionA());
1636       Region regionB = getRegion(request.getRegionB());
1637       boolean forcible = request.getForcible();
1638       long masterSystemTime = request.hasMasterSystemTime() ? request.getMasterSystemTime() : -1;
1639       regionA.startRegionOperation(Operation.MERGE_REGION);
1640       regionB.startRegionOperation(Operation.MERGE_REGION);
1641       if (regionA.getRegionInfo().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID ||
1642           regionB.getRegionInfo().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
1643         throw new ServiceException(new MergeRegionException("Can't merge non-default replicas"));
1644       }
1645       LOG.info("Receiving merging request for  " + regionA + ", " + regionB
1646           + ",forcible=" + forcible);
1647       regionA.flush(true);
1648       regionB.flush(true);
1649       regionServer.compactSplitThread.requestRegionsMerge(regionA, regionB, forcible,
1650           masterSystemTime, RpcServer.getRequestUser());
1651       return MergeRegionsResponse.newBuilder().build();
1652     } catch (DroppedSnapshotException ex) {
1653       regionServer.abort("Replay of WAL required. Forcing server shutdown", ex);
1654       throw new ServiceException(ex);
1655     } catch (IOException ie) {
1656       throw new ServiceException(ie);
1657     }
1658   }
1659 
1660   /**
1661    * Open asynchronously a region or a set of regions on the region server.
1662    *
1663    * The opening is coordinated by ZooKeeper, and this method requires the znode to be created
1664    *  before being called. As a consequence, this method should be called only from the master.
1665    * <p>
1666    * Different manages states for the region are:
1667    * </p><ul>
1668    *  <li>region not opened: the region opening will start asynchronously.</li>
1669    *  <li>a close is already in progress: this is considered as an error.</li>
1670    *  <li>an open is already in progress: this new open request will be ignored. This is important
1671    *  because the Master can do multiple requests if it crashes.</li>
1672    *  <li>the region is already opened:  this new open request will be ignored.</li>
1673    *  </ul>
1674    * <p>
1675    * Bulk assign: If there are more than 1 region to open, it will be considered as a bulk assign.
1676    * For a single region opening, errors are sent through a ServiceException. For bulk assign,
1677    * errors are put in the response as FAILED_OPENING.
1678    * </p>
1679    * @param controller the RPC controller
1680    * @param request the request
1681    * @throws ServiceException
1682    */
1683   @Override
1684   @QosPriority(priority=HConstants.ADMIN_QOS)
1685   @edu.umd.cs.findbugs.annotations.SuppressWarnings(
1686     value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
1687     justification="We double up use of an atomic both as monitor and condition variable")
1688   public OpenRegionResponse openRegion(final RpcController controller,
1689       final OpenRegionRequest request) throws ServiceException {
1690     requestCount.increment();
1691     if (request.hasServerStartCode()) {
1692       // check that we are the same server that this RPC is intended for.
1693       long serverStartCode = request.getServerStartCode();
1694       if (regionServer.serverName.getStartcode() !=  serverStartCode) {
1695         throw new ServiceException(new DoNotRetryIOException("This RPC was intended for a " +
1696             "different server with startCode: " + serverStartCode + ", this server is: "
1697             + regionServer.serverName));
1698       }
1699     }
1700 
1701     OpenRegionResponse.Builder builder = OpenRegionResponse.newBuilder();
1702     final int regionCount = request.getOpenInfoCount();
1703     final Map<TableName, HTableDescriptor> htds =
1704         new HashMap<TableName, HTableDescriptor>(regionCount);
1705     final boolean isBulkAssign = regionCount > 1;
1706     try {
1707       checkOpen();
1708     } catch (IOException ie) {
1709       TableName tableName = null;
1710       if (regionCount == 1) {
1711         RegionInfo ri = request.getOpenInfo(0).getRegion();
1712         if (ri != null) {
1713           tableName = ProtobufUtil.toTableName(ri.getTableName());
1714         }
1715       }
1716       if (!TableName.META_TABLE_NAME.equals(tableName)) {
1717         throw new ServiceException(ie);
1718       }
1719       // We are assigning meta, wait a little for regionserver to finish initialization.
1720       int timeout = regionServer.conf.getInt(HConstants.HBASE_RPC_TIMEOUT_KEY,
1721         HConstants.DEFAULT_HBASE_RPC_TIMEOUT) >> 2; // Quarter of RPC timeout
1722       long endTime = System.currentTimeMillis() + timeout;
1723       synchronized (regionServer.online) {
1724         try {
1725           while (System.currentTimeMillis() <= endTime
1726               && !regionServer.isStopped() && !regionServer.isOnline()) {
1727             regionServer.online.wait(regionServer.msgInterval);
1728           }
1729           checkOpen();
1730         } catch (InterruptedException t) {
1731           Thread.currentThread().interrupt();
1732           throw new ServiceException(t);
1733         } catch (IOException e) {
1734           throw new ServiceException(e);
1735         }
1736       }
1737     }
1738 
1739     long masterSystemTime = request.hasMasterSystemTime() ? request.getMasterSystemTime() : -1;
1740 
1741     for (RegionOpenInfo regionOpenInfo : request.getOpenInfoList()) {
1742       final HRegionInfo region = HRegionInfo.convert(regionOpenInfo.getRegion());
1743       OpenRegionCoordination coordination = regionServer.getCoordinatedStateManager().
1744         getOpenRegionCoordination();
1745       OpenRegionCoordination.OpenRegionDetails ord =
1746         coordination.parseFromProtoRequest(regionOpenInfo);
1747 
1748       HTableDescriptor htd;
1749       try {
1750         final Region onlineRegion = regionServer.getFromOnlineRegions(region.getEncodedName());
1751         if (onlineRegion != null) {
1752           //Check if the region can actually be opened.
1753           if (onlineRegion.getCoprocessorHost() != null) {
1754             onlineRegion.getCoprocessorHost().preOpen();
1755           }
1756           // See HBASE-5094. Cross check with hbase:meta if still this RS is owning
1757           // the region.
1758           Pair<HRegionInfo, ServerName> p = MetaTableAccessor.getRegion(
1759             regionServer.getConnection(), region.getRegionName());
1760           if (regionServer.serverName.equals(p.getSecond())) {
1761             Boolean closing = regionServer.regionsInTransitionInRS.get(region.getEncodedNameAsBytes());
1762             // Map regionsInTransitionInRSOnly has an entry for a region only if the region
1763             // is in transition on this RS, so here closing can be null. If not null, it can
1764             // be true or false. True means the region is opening on this RS; while false
1765             // means the region is closing. Only return ALREADY_OPENED if not closing (i.e.
1766             // not in transition any more, or still transition to open.
1767             if (!Boolean.FALSE.equals(closing)
1768                 && regionServer.getFromOnlineRegions(region.getEncodedName()) != null) {
1769               LOG.warn("Attempted open of " + region.getEncodedName()
1770                 + " but already online on this server");
1771               builder.addOpeningState(RegionOpeningState.ALREADY_OPENED);
1772               continue;
1773             }
1774           } else {
1775             LOG.warn("The region " + region.getEncodedName() + " is online on this server"
1776               + " but hbase:meta does not have this server - continue opening.");
1777             regionServer.removeFromOnlineRegions(onlineRegion, null);
1778           }
1779         }
1780         LOG.info("Open " + region.getRegionNameAsString());
1781         htd = htds.get(region.getTable());
1782         if (htd == null) {
1783           htd = regionServer.tableDescriptors.get(region.getTable());
1784           htds.put(region.getTable(), htd);
1785         }
1786 
1787         final Boolean previous = regionServer.regionsInTransitionInRS.putIfAbsent(
1788           region.getEncodedNameAsBytes(), Boolean.TRUE);
1789 
1790         if (Boolean.FALSE.equals(previous)) {
1791           // There is a close in progress. We need to mark this open as failed in ZK.
1792 
1793           coordination.tryTransitionFromOfflineToFailedOpen(regionServer, region, ord);
1794 
1795           throw new RegionAlreadyInTransitionException("Received OPEN for the region:"
1796             + region.getRegionNameAsString() + " , which we are already trying to CLOSE ");
1797         }
1798 
1799         if (Boolean.TRUE.equals(previous)) {
1800           // An open is in progress. This is supported, but let's log this.
1801           LOG.info("Receiving OPEN for the region:" +
1802             region.getRegionNameAsString() + " , which we are already trying to OPEN"
1803               + " - ignoring this new request for this region.");
1804         }
1805 
1806         // We are opening this region. If it moves back and forth for whatever reason, we don't
1807         // want to keep returning the stale moved record while we are opening/if we close again.
1808         regionServer.removeFromMovedRegions(region.getEncodedName());
1809 
1810         if (previous == null) {
1811           // check if the region to be opened is marked in recovering state in ZK
1812           if (ZKSplitLog.isRegionMarkedRecoveringInZK(regionServer.getZooKeeper(),
1813               region.getEncodedName())) {
1814             // Check if current region open is for distributedLogReplay. This check is to support
1815             // rolling restart/upgrade where we want to Master/RS see same configuration
1816             if (!regionOpenInfo.hasOpenForDistributedLogReplay()
1817                   || regionOpenInfo.getOpenForDistributedLogReplay()) {
1818               regionServer.recoveringRegions.put(region.getEncodedName(), null);
1819             } else {
1820               // Remove stale recovery region from ZK when we open region not for recovering which
1821               // could happen when turn distributedLogReplay off from on.
1822               List<String> tmpRegions = new ArrayList<String>();
1823               tmpRegions.add(region.getEncodedName());
1824               ZKSplitLog.deleteRecoveringRegionZNodes(regionServer.getZooKeeper(),
1825                 tmpRegions);
1826             }
1827           }
1828           if (htd == null) {
1829             throw new IOException("Missing table descriptor for " + region.getEncodedName());
1830           }
1831           // If there is no action in progress, we can submit a specific handler.
1832           // Need to pass the expected version in the constructor.
1833           if (region.isMetaRegion()) {
1834             regionServer.service.submit(new OpenMetaHandler(
1835               regionServer, regionServer, region, htd, masterSystemTime, coordination, ord));
1836           } else {
1837             regionServer.updateRegionFavoredNodesMapping(region.getEncodedName(),
1838               regionOpenInfo.getFavoredNodesList());
1839             if (htd.getPriority() >= HConstants.ADMIN_QOS || region.getTable().isSystemTable()) {
1840               regionServer.service.submit(new OpenPriorityRegionHandler(
1841                 regionServer, regionServer, region, htd, masterSystemTime, coordination, ord));
1842             } else {
1843               regionServer.service.submit(new OpenRegionHandler(
1844                 regionServer, regionServer, region, htd, masterSystemTime, coordination, ord));
1845             }
1846           }
1847         }
1848 
1849         builder.addOpeningState(RegionOpeningState.OPENED);
1850 
1851       } catch (KeeperException zooKeeperEx) {
1852         LOG.error("Can't retrieve recovering state from zookeeper", zooKeeperEx);
1853         throw new ServiceException(zooKeeperEx);
1854       } catch (IOException ie) {
1855         LOG.warn("Failed opening region " + region.getRegionNameAsString(), ie);
1856         if (isBulkAssign) {
1857           builder.addOpeningState(RegionOpeningState.FAILED_OPENING);
1858         } else {
1859           throw new ServiceException(ie);
1860         }
1861       }
1862     }
1863     return builder.build();
1864   }
1865 
1866   /**
1867    *  Wamrmup a region on this server.
1868    *
1869    * This method should only be called by Master. It synchrnously opens the region and
1870    * closes the region bringing the most important pages in cache.
1871    * <p>
1872    *
1873    * @param controller the RPC controller
1874    * @param request the request
1875    * @throws ServiceException
1876    */
1877   @Override
1878   public WarmupRegionResponse warmupRegion(final RpcController controller,
1879       final WarmupRegionRequest request) throws ServiceException {
1880 
1881     RegionInfo regionInfo = request.getRegionInfo();
1882     final HRegionInfo region = HRegionInfo.convert(regionInfo);
1883     HTableDescriptor htd;
1884     WarmupRegionResponse response = WarmupRegionResponse.getDefaultInstance();
1885 
1886     try {
1887       checkOpen();
1888       String encodedName = region.getEncodedName();
1889       byte[] encodedNameBytes = region.getEncodedNameAsBytes();
1890       final Region onlineRegion = regionServer.getFromOnlineRegions(encodedName);
1891 
1892       if (onlineRegion != null) {
1893         LOG.info("Region already online. Skipping warming up " + region);
1894         return response;
1895       }
1896 
1897       if (LOG.isDebugEnabled()) {
1898         LOG.debug("Warming up Region " + region.getRegionNameAsString());
1899       }
1900 
1901       htd = regionServer.tableDescriptors.get(region.getTable());
1902 
1903       if (regionServer.getRegionsInTransitionInRS().containsKey(encodedNameBytes)) {
1904         LOG.info("Region is in transition. Skipping warmup " + region);
1905         return response;
1906       }
1907 
1908       HRegion.warmupHRegion(region, htd, regionServer.getWAL(region),
1909           regionServer.getConfiguration(), regionServer, null);
1910 
1911     } catch (IOException ie) {
1912       LOG.error("Failed warming up region " + region.getRegionNameAsString(), ie);
1913       throw new ServiceException(ie);
1914     }
1915 
1916     return response;
1917   }
1918 
1919   /**
1920    * Replay the given changes when distributedLogReplay WAL edits from a failed RS. The guarantee is
1921    * that the given mutations will be durable on the receiving RS if this method returns without any
1922    * exception.
1923    * @param controller the RPC controller
1924    * @param request the request
1925    * @throws ServiceException
1926    */
1927   @Override
1928   @QosPriority(priority = HConstants.REPLAY_QOS)
1929   public ReplicateWALEntryResponse replay(final RpcController controller,
1930       final ReplicateWALEntryRequest request) throws ServiceException {
1931     long before = EnvironmentEdgeManager.currentTime();
1932     CellScanner cells = ((HBaseRpcController) controller).cellScanner();
1933     try {
1934       checkOpen();
1935       List<WALEntry> entries = request.getEntryList();
1936       if (entries == null || entries.isEmpty()) {
1937         // empty input
1938         return ReplicateWALEntryResponse.newBuilder().build();
1939       }
1940       ByteString regionName = entries.get(0).getKey().getEncodedRegionName();
1941       Region region = regionServer.getRegionByEncodedName(regionName.toStringUtf8());
1942       RegionCoprocessorHost coprocessorHost =
1943           ServerRegionReplicaUtil.isDefaultReplica(region.getRegionInfo())
1944             ? region.getCoprocessorHost()
1945             : null; // do not invoke coprocessors if this is a secondary region replica
1946       List<Pair<WALKey, WALEdit>> walEntries = new ArrayList<Pair<WALKey, WALEdit>>();
1947 
1948       // Skip adding the edits to WAL if this is a secondary region replica
1949       boolean isPrimary = RegionReplicaUtil.isDefaultReplica(region.getRegionInfo());
1950       Durability durability = isPrimary ? Durability.USE_DEFAULT : Durability.SKIP_WAL;
1951 
1952       for (WALEntry entry : entries) {
1953         if (!regionName.equals(entry.getKey().getEncodedRegionName())) {
1954           throw new NotServingRegionException("Replay request contains entries from multiple " +
1955               "regions. First region:" + regionName.toStringUtf8() + " , other region:"
1956               + entry.getKey().getEncodedRegionName());
1957         }
1958         if (regionServer.nonceManager != null && isPrimary) {
1959           long nonceGroup = entry.getKey().hasNonceGroup()
1960             ? entry.getKey().getNonceGroup() : HConstants.NO_NONCE;
1961           long nonce = entry.getKey().hasNonce() ? entry.getKey().getNonce() : HConstants.NO_NONCE;
1962           regionServer.nonceManager.reportOperationFromWal(
1963               nonceGroup,
1964               nonce,
1965               entry.getKey().getWriteTime());
1966         }
1967         Pair<WALKey, WALEdit> walEntry = (coprocessorHost == null) ? null :
1968           new Pair<WALKey, WALEdit>();
1969         List<WALSplitter.MutationReplay> edits = WALSplitter.getMutationsFromWALEntry(entry,
1970           cells, walEntry, durability);
1971         if (coprocessorHost != null) {
1972           // Start coprocessor replay here. The coprocessor is for each WALEdit instead of a
1973           // KeyValue.
1974           if (coprocessorHost.preWALRestore(region.getRegionInfo(), walEntry.getFirst(),
1975             walEntry.getSecond())) {
1976             // if bypass this log entry, ignore it ...
1977             continue;
1978           }
1979           walEntries.add(walEntry);
1980         }
1981         if(edits!=null && !edits.isEmpty()) {
1982           // HBASE-17924
1983           // sort to improve lock efficiency
1984           Collections.sort(edits);
1985           long replaySeqId = (entry.getKey().hasOrigSequenceNumber()) ?
1986             entry.getKey().getOrigSequenceNumber() : entry.getKey().getLogSequenceNumber();
1987           OperationStatus[] result = doReplayBatchOp(region, edits, replaySeqId);
1988           // check if it's a partial success
1989           for (int i = 0; result != null && i < result.length; i++) {
1990             if (result[i] != OperationStatus.SUCCESS) {
1991               throw new IOException(result[i].getExceptionMsg());
1992             }
1993           }
1994         }
1995       }
1996 
1997       //sync wal at the end because ASYNC_WAL is used above
1998       WAL wal = getWAL(region);
1999       if (wal != null) {
2000         wal.sync();
2001       }
2002 
2003       if (coprocessorHost != null) {
2004         for (Pair<WALKey, WALEdit> entry : walEntries) {
2005           coprocessorHost.postWALRestore(region.getRegionInfo(), entry.getFirst(),
2006             entry.getSecond());
2007         }
2008       }
2009       return ReplicateWALEntryResponse.newBuilder().build();
2010     } catch (IOException ie) {
2011       throw new ServiceException(ie);
2012     } finally {
2013       if (regionServer.metricsRegionServer != null) {
2014         regionServer.metricsRegionServer.updateReplay(
2015           EnvironmentEdgeManager.currentTime() - before);
2016       }
2017     }
2018   }
2019 
2020   WAL getWAL(Region region) {
2021     return ((HRegion)region).getWAL();
2022   }
2023 
2024   /**
2025    * Replicate WAL entries on the region server.
2026    *
2027    * @param controller the RPC controller
2028    * @param request the request
2029    * @throws ServiceException
2030    */
2031   @Override
2032   @QosPriority(priority=HConstants.REPLICATION_QOS)
2033   public ReplicateWALEntryResponse replicateWALEntry(final RpcController controller,
2034       final ReplicateWALEntryRequest request) throws ServiceException {
2035     try {
2036       checkOpen();
2037       if (regionServer.replicationSinkHandler != null) {
2038         requestCount.increment();
2039         List<WALEntry> entries = request.getEntryList();
2040         CellScanner cellScanner = ((HBaseRpcController)controller).cellScanner();
2041         regionServer.getRegionServerCoprocessorHost().preReplicateLogEntries(entries, cellScanner);
2042         regionServer.replicationSinkHandler.replicateLogEntries(entries, cellScanner,
2043           request.getReplicationClusterId(), request.getSourceBaseNamespaceDirPath(),
2044           request.getSourceHFileArchiveDirPath());
2045         regionServer.getRegionServerCoprocessorHost().postReplicateLogEntries(entries, cellScanner);
2046         return ReplicateWALEntryResponse.newBuilder().build();
2047       } else {
2048         throw new ServiceException("Replication services are not initialized yet");
2049       }
2050     } catch (IOException ie) {
2051       throw new ServiceException(ie);
2052     }
2053   }
2054 
2055   /**
2056    * Roll the WAL writer of the region server.
2057    * @param controller the RPC controller
2058    * @param request the request
2059    * @throws ServiceException
2060    */
2061   @Override
2062   public RollWALWriterResponse rollWALWriter(final RpcController controller,
2063       final RollWALWriterRequest request) throws ServiceException {
2064     try {
2065       checkOpen();
2066       requestCount.increment();
2067       regionServer.getRegionServerCoprocessorHost().preRollWALWriterRequest();
2068       regionServer.walRoller.requestRollAll();
2069       regionServer.getRegionServerCoprocessorHost().postRollWALWriterRequest();
2070       RollWALWriterResponse.Builder builder = RollWALWriterResponse.newBuilder();
2071       return builder.build();
2072     } catch (IOException ie) {
2073       throw new ServiceException(ie);
2074     }
2075   }
2076 
2077   /**
2078    * Split a region on the region server.
2079    *
2080    * @param controller the RPC controller
2081    * @param request the request
2082    * @throws ServiceException
2083    */
2084   @Override
2085   @QosPriority(priority=HConstants.ADMIN_QOS)
2086   public SplitRegionResponse splitRegion(final RpcController controller,
2087       final SplitRegionRequest request) throws ServiceException {
2088     try {
2089       checkOpen();
2090       requestCount.increment();
2091       Region region = getRegion(request.getRegion());
2092       region.startRegionOperation(Operation.SPLIT_REGION);
2093       if (region.getRegionInfo().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) {
2094         throw new IOException("Can't split replicas directly. "
2095             + "Replicas are auto-split when their primary is split.");
2096       }
2097       LOG.info("Splitting " + region.getRegionInfo().getRegionNameAsString());
2098       region.flush(true);
2099       byte[] splitPoint = null;
2100       if (request.hasSplitPoint()) {
2101         splitPoint = request.getSplitPoint().toByteArray();
2102       }
2103       ((HRegion)region).forceSplit(splitPoint);
2104       regionServer.compactSplitThread.requestSplit(region, ((HRegion)region).checkSplit(),
2105         RpcServer.getRequestUser());
2106       return SplitRegionResponse.newBuilder().build();
2107     } catch (DroppedSnapshotException ex) {
2108       regionServer.abort("Replay of WAL required. Forcing server shutdown", ex);
2109       throw new ServiceException(ex);
2110     } catch (IOException ie) {
2111       throw new ServiceException(ie);
2112     }
2113   }
2114 
2115   /**
2116    * Stop the region server.
2117    *
2118    * @param controller the RPC controller
2119    * @param request the request
2120    * @throws ServiceException
2121    */
2122   @Override
2123   @QosPriority(priority=HConstants.ADMIN_QOS)
2124   public StopServerResponse stopServer(final RpcController controller,
2125       final StopServerRequest request) throws ServiceException {
2126     requestCount.increment();
2127     String reason = request.getReason();
2128     regionServer.stop(reason);
2129     return StopServerResponse.newBuilder().build();
2130   }
2131 
2132   @Override
2133   public UpdateFavoredNodesResponse updateFavoredNodes(RpcController controller,
2134       UpdateFavoredNodesRequest request) throws ServiceException {
2135     List<UpdateFavoredNodesRequest.RegionUpdateInfo> openInfoList = request.getUpdateInfoList();
2136     UpdateFavoredNodesResponse.Builder respBuilder = UpdateFavoredNodesResponse.newBuilder();
2137     for (UpdateFavoredNodesRequest.RegionUpdateInfo regionUpdateInfo : openInfoList) {
2138       HRegionInfo hri = HRegionInfo.convert(regionUpdateInfo.getRegion());
2139       regionServer.updateRegionFavoredNodesMapping(hri.getEncodedName(),
2140         regionUpdateInfo.getFavoredNodesList());
2141     }
2142     respBuilder.setResponse(openInfoList.size());
2143     return respBuilder.build();
2144   }
2145 
2146   /**
2147    * Atomically bulk load several HFiles into an open region
2148    * @return true if successful, false is failed but recoverably (no action)
2149    * @throws ServiceException if failed unrecoverably
2150    */
2151   @Override
2152   public BulkLoadHFileResponse bulkLoadHFile(final RpcController controller,
2153       final BulkLoadHFileRequest request) throws ServiceException {
2154     long start = EnvironmentEdgeManager.currentTime();
2155     try {
2156       checkOpen();
2157       requestCount.increment();
2158       Region region = getRegion(request.getRegion());
2159       List<Pair<byte[], String>> familyPaths = new ArrayList<Pair<byte[], String>>();
2160       for (FamilyPath familyPath: request.getFamilyPathList()) {
2161         familyPaths.add(new Pair<byte[], String>(familyPath.getFamily().toByteArray(),
2162           familyPath.getPath()));
2163       }
2164       boolean bypass = false;
2165       if (region.getCoprocessorHost() != null) {
2166         bypass = region.getCoprocessorHost().preBulkLoadHFile(familyPaths);
2167       }
2168       boolean loaded = false;
2169       try {
2170         if (!bypass) {
2171           loaded = region.bulkLoadHFiles(familyPaths, request.getAssignSeqNum(), null);
2172         }
2173       } finally {
2174         if (region.getCoprocessorHost() != null) {
2175           loaded = region.getCoprocessorHost().postBulkLoadHFile(familyPaths, loaded);
2176         }
2177       }
2178       BulkLoadHFileResponse.Builder builder = BulkLoadHFileResponse.newBuilder();
2179       builder.setLoaded(loaded);
2180       return builder.build();
2181     } catch (IOException ie) {
2182       throw new ServiceException(ie);
2183     } finally {
2184       if (regionServer.metricsRegionServer != null) {
2185         regionServer.metricsRegionServer.updateBulkLoad(
2186             EnvironmentEdgeManager.currentTime() - start);
2187       }
2188     }
2189   }
2190 
2191   @Override
2192   public CoprocessorServiceResponse execService(final RpcController controller,
2193       final CoprocessorServiceRequest request) throws ServiceException {
2194     try {
2195       checkOpen();
2196       requestCount.increment();
2197       Region region = getRegion(request.getRegion());
2198       Message result = execServiceOnRegion(region, request.getCall());
2199       CoprocessorServiceResponse.Builder builder =
2200         CoprocessorServiceResponse.newBuilder();
2201       builder.setRegion(RequestConverter.buildRegionSpecifier(
2202         RegionSpecifierType.REGION_NAME, region.getRegionInfo().getRegionName()));
2203       builder.setValue(
2204         builder.getValueBuilder().setName(result.getClass().getName())
2205           .setValue(result.toByteString()));
2206       return builder.build();
2207     } catch (IOException ie) {
2208       throw new ServiceException(ie);
2209     }
2210   }
2211 
2212   private Message execServiceOnRegion(Region region,
2213       final ClientProtos.CoprocessorServiceCall serviceCall) throws IOException {
2214     // ignore the passed in controller (from the serialized call)
2215     ServerRpcController execController = new ServerRpcController();
2216     return region.execService(execController, serviceCall);
2217   }
2218 
2219   /**
2220    * Get data from a table.
2221    *
2222    * @param controller the RPC controller
2223    * @param request the get request
2224    * @throws ServiceException
2225    */
2226   @Override
2227   public GetResponse get(final RpcController controller,
2228       final GetRequest request) throws ServiceException {
2229     long before = EnvironmentEdgeManager.currentTime();
2230     OperationQuota quota = null;
2231     Region region = null;
2232     try {
2233       checkOpen();
2234       requestCount.increment();
2235       rpcGetRequestCount.increment();
2236       region = getRegion(request.getRegion());
2237 
2238       GetResponse.Builder builder = GetResponse.newBuilder();
2239       ClientProtos.Get get = request.getGet();
2240       Boolean existence = null;
2241       Result r = null;
2242       quota = getQuotaManager().checkQuota(region, OperationQuota.OperationType.GET);
2243 
2244       if (get.hasClosestRowBefore() && get.getClosestRowBefore()) {
2245         if (get.getColumnCount() != 1) {
2246           throw new DoNotRetryIOException(
2247             "get ClosestRowBefore supports one and only one family now, not "
2248               + get.getColumnCount() + " families");
2249         }
2250         byte[] row = get.getRow().toByteArray();
2251         byte[] family = get.getColumn(0).getFamily().toByteArray();
2252         r = region.getClosestRowBefore(row, family);
2253       } else {
2254         Get clientGet = ProtobufUtil.toGet(get);
2255         if (get.getExistenceOnly() && region.getCoprocessorHost() != null) {
2256           existence = region.getCoprocessorHost().preExists(clientGet);
2257         }
2258         if (existence == null) {
2259           r = region.get(clientGet);
2260           if (get.getExistenceOnly()) {
2261             boolean exists = r.getExists();
2262             if (region.getCoprocessorHost() != null) {
2263               exists = region.getCoprocessorHost().postExists(clientGet, exists);
2264             }
2265             existence = exists;
2266           }
2267         }
2268       }
2269       if (existence != null){
2270         ClientProtos.Result pbr =
2271             ProtobufUtil.toResult(existence, region.getRegionInfo().getReplicaId() != 0);
2272         builder.setResult(pbr);
2273       } else  if (r != null) {
2274         ClientProtos.Result pbr;
2275         RpcCallContext call = RpcServer.getCurrentCall();
2276         if (isClientCellBlockSupport(call) && controller instanceof HBaseRpcController
2277             && VersionInfoUtil.hasMinimumVersion(call.getClientVersionInfo(), 1, 3)) {
2278           pbr = ProtobufUtil.toResultNoData(r);
2279           ((HBaseRpcController) controller)
2280               .setCellScanner(CellUtil.createCellScanner(r.rawCells()));
2281           addSize(call, r, null);
2282         } else {
2283           pbr = ProtobufUtil.toResult(r);
2284         }
2285         builder.setResult(pbr);
2286       }
2287       if (r != null) {
2288         quota.addGetResult(r);
2289       }
2290       return builder.build();
2291     } catch (IOException ie) {
2292       throw new ServiceException(ie);
2293     } finally {
2294       MetricsRegionServer mrs = regionServer.metricsRegionServer;
2295       if (mrs != null) {
2296         HTableDescriptor td = region != null ? region.getTableDesc() : null;
2297         if (td != null) {
2298           mrs.updateGet(td.getTableName(), EnvironmentEdgeManager.currentTime() - before);
2299         }
2300       }
2301       if (quota != null) {
2302         quota.close();
2303       }
2304     }
2305   }
2306 
2307   private void checkBatchSizeAndLogLargeSize(MultiRequest request) {
2308     int sum = 0;
2309     String firstRegionName = null;
2310     for (RegionAction regionAction : request.getRegionActionList()) {
2311       if (sum == 0) {
2312         firstRegionName = Bytes.toStringBinary(regionAction.getRegion().getValue().toByteArray());
2313       }
2314       sum += regionAction.getActionCount();
2315     }
2316     if (sum > rowSizeWarnThreshold) {
2317       ld.logBatchWarning(firstRegionName, sum, rowSizeWarnThreshold);
2318     }
2319   }
2320 
2321   /**
2322    * Execute multiple actions on a table: get, mutate, and/or execCoprocessor
2323    *
2324    * @param rpcc the RPC controller
2325    * @param request the multi request
2326    * @throws ServiceException
2327    */
2328   @Override
2329   public MultiResponse multi(final RpcController rpcc, final MultiRequest request)
2330   throws ServiceException {
2331     try {
2332       checkOpen();
2333     } catch (IOException ie) {
2334       throw new ServiceException(ie);
2335     }
2336 
2337     checkBatchSizeAndLogLargeSize(request);
2338 
2339     // rpc controller is how we bring in data via the back door;  it is unprotobuf'ed data.
2340     // It is also the conduit via which we pass back data.
2341     HBaseRpcController controller = (HBaseRpcController)rpcc;
2342     CellScanner cellScanner = controller != null ? controller.cellScanner(): null;
2343     if (controller != null) {
2344       controller.setCellScanner(null);
2345     }
2346 
2347     long nonceGroup = request.hasNonceGroup() ? request.getNonceGroup() : HConstants.NO_NONCE;
2348 
2349     // this will contain all the cells that we need to return. It's created later, if needed.
2350     List<CellScannable> cellsToReturn = null;
2351     MultiResponse.Builder responseBuilder = MultiResponse.newBuilder();
2352     RegionActionResult.Builder regionActionResultBuilder = RegionActionResult.newBuilder();
2353     Boolean processed = null;
2354 
2355     this.rpcMultiRequestCount.increment();
2356     this.requestCount.increment();
2357     Map<RegionSpecifier, ClientProtos.RegionLoadStats> regionStats = new HashMap<>(request
2358       .getRegionActionCount());
2359     for (RegionAction regionAction : request.getRegionActionList()) {
2360       OperationQuota quota;
2361       Region region;
2362       regionActionResultBuilder.clear();
2363       RegionSpecifier regionSpecifier = regionAction.getRegion();
2364       try {
2365         region = getRegion(regionSpecifier);
2366         quota = getQuotaManager().checkQuota(region, regionAction.getActionList());
2367       } catch (IOException e) {
2368         rpcServer.getMetrics().exception(e);
2369         regionActionResultBuilder.setException(ResponseConverter.buildException(e));
2370         responseBuilder.addRegionActionResult(regionActionResultBuilder.build());
2371         // All Mutations in this RegionAction not executed as we can not see the Region online here
2372         // in this RS. Will be retried from Client. Skipping all the Cells in CellScanner
2373         // corresponding to these Mutations.
2374         skipCellsForMutations(regionAction.getActionList(), cellScanner);
2375         continue;  // For this region it's a failure.
2376       }
2377 
2378       if (regionAction.hasAtomic() && regionAction.getAtomic()) {
2379         // How does this call happen?  It may need some work to play well w/ the surroundings.
2380         // Need to return an item per Action along w/ Action index.  TODO.
2381         try {
2382           if (request.hasCondition()) {
2383             Condition condition = request.getCondition();
2384             byte[] row = condition.getRow().toByteArray();
2385             byte[] family = condition.getFamily().toByteArray();
2386             byte[] qualifier = condition.getQualifier().toByteArray();
2387             CompareOp compareOp = CompareOp.valueOf(condition.getCompareType().name());
2388             ByteArrayComparable comparator =
2389                 ProtobufUtil.toComparator(condition.getComparator());
2390             processed = checkAndRowMutate(region, regionAction.getActionList(),
2391                   cellScanner, row, family, qualifier, compareOp,
2392                   comparator, regionActionResultBuilder);
2393           } else {
2394             mutateRows(region, regionAction.getActionList(), cellScanner,
2395                 regionActionResultBuilder);
2396             processed = Boolean.TRUE;
2397           }
2398         } catch (IOException e) {
2399           rpcServer.getMetrics().exception(e);
2400           // As it's atomic, we may expect it's a global failure.
2401           regionActionResultBuilder.setException(ResponseConverter.buildException(e));
2402         }
2403       } else {
2404         // doNonAtomicRegionMutation manages the exception internally
2405         cellsToReturn = doNonAtomicRegionMutation(region, quota, regionAction, cellScanner,
2406             regionActionResultBuilder, cellsToReturn, nonceGroup);
2407       }
2408       responseBuilder.addRegionActionResult(regionActionResultBuilder.build());
2409       quota.close();
2410       ClientProtos.RegionLoadStats regionLoadStats = ((HRegion)region).getLoadStatistics();
2411       if(regionLoadStats != null) {
2412         regionStats.put(regionSpecifier, regionLoadStats);
2413       }
2414     }
2415     // Load the controller with the Cells to return.
2416     if (cellsToReturn != null && !cellsToReturn.isEmpty() && controller != null) {
2417       controller.setCellScanner(CellUtil.createCellScanner(cellsToReturn));
2418     }
2419 
2420     if (processed != null) {
2421       responseBuilder.setProcessed(processed);
2422     }
2423 
2424     MultiRegionLoadStats.Builder builder = MultiRegionLoadStats.newBuilder();
2425     for(Entry<RegionSpecifier, ClientProtos.RegionLoadStats> stat: regionStats.entrySet()){
2426       builder.addRegion(stat.getKey());
2427       builder.addStat(stat.getValue());
2428     }
2429     responseBuilder.setRegionStatistics(builder);
2430     return responseBuilder.build();
2431   }
2432 
2433   private void skipCellsForMutations(List<Action> actions, CellScanner cellScanner) {
2434     if (cellScanner == null) {
2435       return;
2436     }
2437     for (Action action : actions) {
2438       skipCellsForMutation(action, cellScanner);
2439     }
2440   }
2441 
2442   private void skipCellsForMutation(Action action, CellScanner cellScanner) {
2443     if (cellScanner == null) {
2444       return;
2445     }
2446     try {
2447       if (action.hasMutation()) {
2448         MutationProto m = action.getMutation();
2449         if (m.hasAssociatedCellCount()) {
2450           for (int i = 0; i < m.getAssociatedCellCount(); i++) {
2451             cellScanner.advance();
2452           }
2453         }
2454       }
2455     } catch (IOException e) {
2456       // No need to handle these Individual Muatation level issue. Any way this entire RegionAction
2457       // marked as failed as we could not see the Region here. At client side the top level
2458       // RegionAction exception will be considered first.
2459       LOG.error("Error while skipping Cells in CellScanner for invalid Region Mutations", e);
2460     }
2461   }
2462 
2463   /**
2464    * Mutate data in a table.
2465    *
2466    * @param rpcc the RPC controller
2467    * @param request the mutate request
2468    * @throws ServiceException
2469    */
2470   @Override
2471   public MutateResponse mutate(final RpcController rpcc,
2472       final MutateRequest request) throws ServiceException {
2473     // rpc controller is how we bring in data via the back door;  it is unprotobuf'ed data.
2474     // It is also the conduit via which we pass back data.
2475     HBaseRpcController controller = (HBaseRpcController)rpcc;
2476     CellScanner cellScanner = controller != null ? controller.cellScanner() : null;
2477     OperationQuota quota = null;
2478     RpcCallContext context = RpcServer.getCurrentCall();
2479     MutationType type = null;
2480     Region region = null;
2481     long before = EnvironmentEdgeManager.currentTime();
2482     // Clear scanner so we are not holding on to reference across call.
2483     if (controller != null) {
2484       controller.setCellScanner(null);
2485     }
2486     try {
2487       checkOpen();
2488       requestCount.increment();
2489       rpcMutateRequestCount.increment();
2490       region = getRegion(request.getRegion());
2491       MutateResponse.Builder builder = MutateResponse.newBuilder();
2492       MutationProto mutation = request.getMutation();
2493       if (!region.getRegionInfo().isMetaTable()) {
2494         regionServer.cacheFlusher.reclaimMemStoreMemory();
2495       }
2496       long nonceGroup = request.hasNonceGroup() ? request.getNonceGroup() : HConstants.NO_NONCE;
2497       Result r = null;
2498       Boolean processed = null;
2499       type = mutation.getMutateType();
2500       long mutationSize = 0;
2501       quota = getQuotaManager().checkQuota(region, OperationQuota.OperationType.MUTATE);
2502       switch (type) {
2503       case APPEND:
2504         // TODO: this doesn't actually check anything.
2505         r = append(region, quota, mutation, cellScanner, nonceGroup);
2506         break;
2507       case INCREMENT:
2508         // TODO: this doesn't actually check anything.
2509         r = increment(region, quota, mutation, cellScanner, nonceGroup);
2510         break;
2511       case PUT:
2512         Put put = ProtobufUtil.toPut(mutation, cellScanner);
2513         checkCellSizeLimit(region, put);
2514         quota.addMutation(put);
2515         if (request.hasCondition()) {
2516           Condition condition = request.getCondition();
2517           byte[] row = condition.getRow().toByteArray();
2518           byte[] family = condition.getFamily().toByteArray();
2519           byte[] qualifier = condition.getQualifier().toByteArray();
2520           CompareOp compareOp = CompareOp.valueOf(condition.getCompareType().name());
2521           ByteArrayComparable comparator =
2522             ProtobufUtil.toComparator(condition.getComparator());
2523           if (region.getCoprocessorHost() != null) {
2524             processed = region.getCoprocessorHost().preCheckAndPut(
2525               row, family, qualifier, compareOp, comparator, put);
2526           }
2527           if (processed == null) {
2528             boolean result = region.checkAndMutate(row, family,
2529               qualifier, compareOp, comparator, put, true);
2530             if (region.getCoprocessorHost() != null) {
2531               result = region.getCoprocessorHost().postCheckAndPut(row, family,
2532                 qualifier, compareOp, comparator, put, result);
2533             }
2534             processed = result;
2535           }
2536         } else {
2537           region.put(put);
2538           processed = Boolean.TRUE;
2539         }
2540         break;
2541       case DELETE:
2542         Delete delete = ProtobufUtil.toDelete(mutation, cellScanner);
2543         checkCellSizeLimit(region, delete);
2544         quota.addMutation(delete);
2545         if (request.hasCondition()) {
2546           Condition condition = request.getCondition();
2547           byte[] row = condition.getRow().toByteArray();
2548           byte[] family = condition.getFamily().toByteArray();
2549           byte[] qualifier = condition.getQualifier().toByteArray();
2550           CompareOp compareOp = CompareOp.valueOf(condition.getCompareType().name());
2551           ByteArrayComparable comparator =
2552             ProtobufUtil.toComparator(condition.getComparator());
2553           if (region.getCoprocessorHost() != null) {
2554             processed = region.getCoprocessorHost().preCheckAndDelete(
2555               row, family, qualifier, compareOp, comparator, delete);
2556           }
2557           if (processed == null) {
2558             boolean result = region.checkAndMutate(row, family,
2559               qualifier, compareOp, comparator, delete, true);
2560             if (region.getCoprocessorHost() != null) {
2561               result = region.getCoprocessorHost().postCheckAndDelete(row, family,
2562                 qualifier, compareOp, comparator, delete, result);
2563             }
2564             processed = result;
2565           }
2566         } else {
2567           region.delete(delete);
2568           processed = Boolean.TRUE;
2569         }
2570         break;
2571       default:
2572           throw new DoNotRetryIOException(
2573             "Unsupported mutate type: " + type.name());
2574       }
2575       if (processed != null) builder.setProcessed(processed.booleanValue());
2576       addResult(builder, r, controller);
2577       boolean clientCellBlockSupported = isClientCellBlockSupport(context);
2578       if (clientCellBlockSupported) {
2579         addSize(context, r, null);
2580       }
2581       return builder.build();
2582     } catch (IOException ie) {
2583       regionServer.checkFileSystem();
2584       throw new ServiceException(ie);
2585     } finally {
2586       if (quota != null) {
2587         quota.close();
2588       }
2589       // Update metrics
2590       if (regionServer.metricsRegionServer != null && type != null) {
2591         long after = EnvironmentEdgeManager.currentTime();
2592         switch (type) {
2593         case DELETE:
2594           if (request.hasCondition()) {
2595             regionServer.metricsRegionServer.updateCheckAndDelete(after - before);
2596           } else {
2597             regionServer.metricsRegionServer.updateDelete(
2598                 region == null ? null : region.getRegionInfo().getTable(), after - before);
2599           }
2600           break;
2601         case PUT:
2602           if (request.hasCondition()) {
2603             regionServer.metricsRegionServer.updateCheckAndPut(after - before);
2604           } else {
2605             regionServer.metricsRegionServer.updatePut(
2606                 region == null ? null : region.getRegionInfo().getTable(),after - before);
2607           }
2608           break;
2609         default:
2610           break;
2611 
2612         }
2613       }
2614     }
2615   }
2616 
2617   // This is used to keep compatible with the old client implementation. Consider remove it if we
2618   // decide to drop the support of the client that still sends close request to a region scanner
2619   // which has already been exhausted.
2620   @Deprecated
2621   private static final IOException SCANNER_ALREADY_CLOSED = new IOException() {
2622 
2623     private static final long serialVersionUID = -4305297078988180130L;
2624 
2625     @Override
2626     public synchronized Throwable fillInStackTrace() {
2627       return this;
2628     }
2629   };
2630 
2631   private RegionScannerHolder getRegionScanner(ScanRequest request) throws IOException {
2632     String scannerName = Long.toString(request.getScannerId());
2633     RegionScannerHolder rsh = scanners.get(scannerName);
2634     if (rsh == null) {
2635       // just ignore the next or close request if scanner does not exists.
2636       if (closedScanners.getIfPresent(scannerName) != null) {
2637         throw SCANNER_ALREADY_CLOSED;
2638       } else {
2639         LOG.warn("Client tried to access missing scanner " + scannerName);
2640         throw new UnknownScannerException(
2641             "Unknown scanner '" + scannerName + "'. This can happen due to any of the following " +
2642                 "reasons: a) Scanner id given is wrong, b) Scanner lease expired because of " +
2643                 "long wait between consecutive client checkins, c) Server may be closing down, " +
2644                 "d) RegionServer restart during upgrade.\nIf the issue is due to reason (b), a " +
2645                 "possible fix would be increasing the value of" +
2646                 "'hbase.client.scanner.timeout.period' configuration.");
2647       }
2648     }
2649     HRegionInfo hri = rsh.s.getRegionInfo();
2650     // Yes, should be the same instance
2651     if (regionServer.getOnlineRegion(hri.getRegionName()) != rsh.r) {
2652       String msg = "Region was re-opened after the scanner" + scannerName + " was created: "
2653           + hri.getRegionNameAsString();
2654       LOG.warn(msg + ", closing...");
2655       scanners.remove(scannerName);
2656       try {
2657         rsh.s.close();
2658       } catch (IOException e) {
2659         LOG.warn("Getting exception closing " + scannerName, e);
2660       } finally {
2661         try {
2662           regionServer.leases.cancelLease(scannerName);
2663         } catch (LeaseException e) {
2664           LOG.warn("Getting exception closing " + scannerName, e);
2665         }
2666       }
2667       throw new NotServingRegionException(msg);
2668     }
2669     return rsh;
2670   }
2671 
2672   private RegionScannerHolder newRegionScanner(ScanRequest request, ScanResponse.Builder builder)
2673       throws IOException {
2674     Region region = getRegion(request.getRegion());
2675     ClientProtos.Scan protoScan = request.getScan();
2676     boolean isLoadingCfsOnDemandSet = protoScan.hasLoadColumnFamiliesOnDemand();
2677     Scan scan = ProtobufUtil.toScan(protoScan);
2678     // if the request doesn't set this, get the default region setting.
2679     if (!isLoadingCfsOnDemandSet) {
2680       scan.setLoadColumnFamiliesOnDemand(region.isLoadingCfsOnDemandDefault());
2681     }
2682     if (!scan.hasFamilies()) {
2683       // Adding all families to scanner
2684       for (byte[] family : region.getTableDesc().getFamiliesKeys()) {
2685         scan.addFamily(family);
2686       }
2687     }
2688     RegionScanner scanner = null;
2689     if (region.getCoprocessorHost() != null) {
2690       scanner = region.getCoprocessorHost().preScannerOpen(scan);
2691     }
2692     if (scanner == null) {
2693       scanner = region.getScanner(scan);
2694     }
2695     if (region.getCoprocessorHost() != null) {
2696       scanner = region.getCoprocessorHost().postScannerOpen(scan, scanner);
2697     }
2698     long scannerId = scannerIdGenerator.generateNewScannerId();
2699     builder.setScannerId(scannerId);
2700     builder.setMvccReadPoint(scanner.getMvccReadPoint());
2701     builder.setTtl(scannerLeaseTimeoutPeriod);
2702     String scannerName = String.valueOf(scannerId);
2703     return addScanner(scannerName, scanner, region, scan.isNeedCursorResult());
2704   }
2705 
2706   private void checkScanNextCallSeq(ScanRequest request, RegionScannerHolder rsh)
2707       throws OutOfOrderScannerNextException {
2708     // if nextCallSeq does not match throw Exception straight away. This needs to be
2709     // performed even before checking of Lease.
2710     // See HBASE-5974
2711     if (request.hasNextCallSeq()) {
2712       long callSeq = request.getNextCallSeq();
2713       if (!rsh.incNextCallSeq(callSeq)) {
2714         throw new OutOfOrderScannerNextException("Expected nextCallSeq: " + rsh.getNextCallSeq()
2715             + " But the nextCallSeq got from client: " + request.getNextCallSeq() + "; request="
2716             + TextFormat.shortDebugString(request));
2717       }
2718     }
2719   }
2720 
2721   private void addScannerLeaseBack(Leases.Lease lease) {
2722     try {
2723       regionServer.leases.addLease(lease);
2724     } catch (LeaseStillHeldException e) {
2725       // should not happen as the scanner id is unique.
2726       throw new AssertionError(e);
2727     }
2728   }
2729 
2730   private long getTimeLimit(HBaseRpcController controller,
2731       boolean allowHeartbeatMessages) {
2732     // Set the time limit to be half of the more restrictive timeout value (one of the
2733     // timeout values must be positive). In the event that both values are positive, the
2734     // more restrictive of the two is used to calculate the limit.
2735     if (allowHeartbeatMessages && (scannerLeaseTimeoutPeriod > 0 || rpcTimeout > 0)) {
2736       long timeLimitDelta;
2737       if (scannerLeaseTimeoutPeriod > 0 && rpcTimeout > 0) {
2738         timeLimitDelta = Math.min(scannerLeaseTimeoutPeriod, rpcTimeout);
2739       } else {
2740         timeLimitDelta = scannerLeaseTimeoutPeriod > 0 ? scannerLeaseTimeoutPeriod : rpcTimeout;
2741       }
2742       if (controller != null && controller.getCallTimeout() > 0) {
2743         timeLimitDelta = Math.min(timeLimitDelta, controller.getCallTimeout());
2744       }
2745       // Use half of whichever timeout value was more restrictive... But don't allow
2746       // the time limit to be less than the allowable minimum (could cause an
2747       // immediatate timeout before scanning any data).
2748       timeLimitDelta = Math.max(timeLimitDelta / 2, minimumScanTimeLimitDelta);
2749       // XXX: Can not use EnvironmentEdge here because TestIncrementTimeRange use a
2750       // ManualEnvironmentEdge. Consider using System.nanoTime instead.
2751       return System.currentTimeMillis() + timeLimitDelta;
2752     }
2753     // Default value of timeLimit is negative to indicate no timeLimit should be
2754     // enforced.
2755     return -1L;
2756   }
2757 
2758   private void checkLimitOfRows(int numOfCompleteRows, int limitOfRows, boolean moreRows,
2759       ScannerContext scannerContext, ScanResponse.Builder builder) {
2760     if (numOfCompleteRows >= limitOfRows) {
2761       if (LOG.isTraceEnabled()) {
2762         LOG.trace("Done scanning, limit of rows reached, moreRows: " + moreRows +
2763             " scannerContext: " + scannerContext);
2764       }
2765       builder.setMoreResults(false);
2766     }
2767   }
2768 
2769   // return whether we have more results in region.
2770   private void scan(HBaseRpcController controller, ScanRequest request, RegionScannerHolder rsh,
2771       long maxQuotaResultSize, int maxResults, int limitOfRows, List<Result> results,
2772       ScanResponse.Builder builder, MutableObject lastBlock, RpcCallContext context)
2773       throws IOException {
2774     Region region = rsh.r;
2775     RegionScanner scanner = rsh.s;
2776     long maxResultSize;
2777     if (scanner.getMaxResultSize() > 0) {
2778       maxResultSize = Math.min(scanner.getMaxResultSize(), maxQuotaResultSize);
2779     } else {
2780       maxResultSize = maxQuotaResultSize;
2781     }
2782     // This is cells inside a row. Default size is 10 so if many versions or many cfs,
2783     // then we'll resize. Resizings show in profiler. Set it higher than 10. For now
2784     // arbitrary 32. TODO: keep record of general size of results being returned.
2785     List<Cell> values = new ArrayList<Cell>(32);
2786     region.startRegionOperation(Operation.SCAN);
2787     try {
2788       int numOfResults = 0;
2789       int numOfCompleteRows = 0;
2790       long before = EnvironmentEdgeManager.currentTime();
2791       synchronized (scanner) {
2792         boolean stale = (region.getRegionInfo().getReplicaId() != 0);
2793         boolean clientHandlesPartials =
2794             request.hasClientHandlesPartials() && request.getClientHandlesPartials();
2795         boolean clientHandlesHeartbeats =
2796             request.hasClientHandlesHeartbeats() && request.getClientHandlesHeartbeats();
2797 
2798         // On the server side we must ensure that the correct ordering of partial results is
2799         // returned to the client to allow them to properly reconstruct the partial results.
2800         // If the coprocessor host is adding to the result list, we cannot guarantee the
2801         // correct ordering of partial results and so we prevent partial results from being
2802         // formed.
2803         boolean serverGuaranteesOrderOfPartials = results.isEmpty();
2804         boolean allowPartialResults = clientHandlesPartials && serverGuaranteesOrderOfPartials;
2805         boolean moreRows = false;
2806 
2807         // Heartbeat messages occur when the processing of the ScanRequest is exceeds a
2808         // certain time threshold on the server. When the time threshold is exceeded, the
2809         // server stops the scan and sends back whatever Results it has accumulated within
2810         // that time period (may be empty). Since heartbeat messages have the potential to
2811         // create partial Results (in the event that the timeout occurs in the middle of a
2812         // row), we must only generate heartbeat messages when the client can handle both
2813         // heartbeats AND partials
2814         boolean allowHeartbeatMessages = clientHandlesHeartbeats && allowPartialResults;
2815 
2816         long timeLimit = getTimeLimit(controller, allowHeartbeatMessages);
2817 
2818         final LimitScope sizeScope =
2819             allowPartialResults ? LimitScope.BETWEEN_CELLS : LimitScope.BETWEEN_ROWS;
2820         final LimitScope timeScope =
2821             allowHeartbeatMessages ? LimitScope.BETWEEN_CELLS : LimitScope.BETWEEN_ROWS;
2822 
2823         boolean trackMetrics = request.hasTrackScanMetrics() && request.getTrackScanMetrics();
2824 
2825         // Configure with limits for this RPC. Set keep progress true since size progress
2826         // towards size limit should be kept between calls to nextRaw
2827         ScannerContext.Builder contextBuilder = ScannerContext.newBuilder(true);
2828         contextBuilder.setSizeLimit(sizeScope, maxResultSize);
2829         contextBuilder.setBatchLimit(scanner.getBatch());
2830         contextBuilder.setTimeLimit(timeScope, timeLimit);
2831         contextBuilder.setTrackMetrics(trackMetrics);
2832         ScannerContext scannerContext = contextBuilder.build();
2833         boolean limitReached = false;
2834         while (numOfResults < maxResults) {
2835           // Reset the batch progress to 0 before every call to RegionScanner#nextRaw. The
2836           // batch limit is a limit on the number of cells per Result. Thus, if progress is
2837           // being tracked (i.e. scannerContext.keepProgress() is true) then we need to
2838           // reset the batch progress between nextRaw invocations since we don't want the
2839           // batch progress from previous calls to affect future calls
2840           scannerContext.setBatchProgress(0);
2841 
2842           // Collect values to be returned here
2843           moreRows = scanner.nextRaw(values, scannerContext);
2844 
2845           if (!values.isEmpty()) {
2846             if (limitOfRows > 0) {
2847               // First we need to check if the last result is partial and we have a row change. If
2848               // so then we need to increase the numOfCompleteRows.
2849               if (results.isEmpty()) {
2850                 if (rsh.rowOfLastPartialResult != null &&
2851                     !CellUtil.matchingRow(values.get(0), rsh.rowOfLastPartialResult)) {
2852                   numOfCompleteRows++;
2853                   checkLimitOfRows(numOfCompleteRows, limitOfRows, moreRows, scannerContext,
2854                     builder);
2855                 }
2856               } else {
2857                 Result lastResult = results.get(results.size() - 1);
2858                 if (lastResult.mayHaveMoreCellsInRow() &&
2859                     !CellUtil.matchingRow(values.get(0), lastResult.getRow())) {
2860                   numOfCompleteRows++;
2861                   checkLimitOfRows(numOfCompleteRows, limitOfRows, moreRows, scannerContext,
2862                     builder);
2863                 }
2864               }
2865               if (builder.hasMoreResults() && !builder.getMoreResults()) {
2866                 break;
2867               }
2868             }
2869             boolean mayHaveMoreCellsInRow = scannerContext.mayHaveMoreCellsInRow();
2870             Result r = Result.create(values, null, stale, mayHaveMoreCellsInRow);
2871             lastBlock.setValue(addSize(context, r, lastBlock.getValue()));
2872             results.add(r);
2873             numOfResults++;
2874             if (!mayHaveMoreCellsInRow && limitOfRows > 0) {
2875               numOfCompleteRows++;
2876               checkLimitOfRows(numOfCompleteRows, limitOfRows, moreRows, scannerContext, builder);
2877               if (builder.hasMoreResults() && !builder.getMoreResults()) {
2878                 break;
2879               }
2880             }
2881           } else if (!moreRows && !results.isEmpty()) {
2882             // No more cells for the scan here, we need to ensure that the mayHaveMoreCellsInRow of
2883             // last result is false. Otherwise it's possible that: the first nextRaw returned
2884             // because BATCH_LIMIT_REACHED (BTW it happen to exhaust all cells of the scan),so the
2885             // last result's mayHaveMoreCellsInRow will be true. while the following nextRaw will
2886             // return with moreRows=false, which means moreResultsInRegion would be false, it will
2887             // be a contradictory state (HBASE-21206).
2888             int lastIdx = results.size() - 1;
2889             Result r = results.get(lastIdx);
2890             if (r.mayHaveMoreCellsInRow()) {
2891               results.set(lastIdx, Result.create(r.rawCells(), r.getExists(), r.isStale(), false));
2892             }
2893           }
2894           boolean sizeLimitReached = scannerContext.checkSizeLimit(LimitScope.BETWEEN_ROWS);
2895           boolean timeLimitReached = scannerContext.checkTimeLimit(LimitScope.BETWEEN_ROWS);
2896           boolean resultsLimitReached = numOfResults >= maxResults;
2897           limitReached = sizeLimitReached || timeLimitReached || resultsLimitReached;
2898 
2899           if (limitReached || !moreRows) {
2900             if (LOG.isTraceEnabled()) {
2901               LOG.trace("Done scanning. limitReached: " + limitReached + " moreRows: " + moreRows
2902                   + " scannerContext: " + scannerContext);
2903             }
2904             // We only want to mark a ScanResponse as a heartbeat message in the event that
2905             // there are more values to be read server side. If there aren't more values,
2906             // marking it as a heartbeat is wasteful because the client will need to issue
2907             // another ScanRequest only to realize that they already have all the values
2908             if (moreRows && timeLimitReached) {
2909               // Heartbeat messages occur when the time limit has been reached.
2910               builder.setHeartbeatMessage(true);
2911               if (rsh.needCursor) {
2912                 Cell cursorCell = scannerContext.getLastPeekedCell();
2913                 if (cursorCell != null) {
2914                   builder.setCursor(ProtobufUtil.toCursor(cursorCell));
2915                 }
2916               }
2917             }
2918             break;
2919           }
2920           values.clear();
2921         }
2922         builder.setMoreResultsInRegion(moreRows);
2923         // Check to see if the client requested that we track metrics server side. If the
2924         // client requested metrics, retrieve the metrics from the scanner context.
2925         if (trackMetrics) {
2926           Map<String, Long> metrics = scannerContext.getMetrics().getMetricsMap();
2927           ScanMetrics.Builder metricBuilder = ScanMetrics.newBuilder();
2928           NameInt64Pair.Builder pairBuilder = NameInt64Pair.newBuilder();
2929 
2930           for (Entry<String, Long> entry : metrics.entrySet()) {
2931             pairBuilder.setName(entry.getKey());
2932             pairBuilder.setValue(entry.getValue());
2933             metricBuilder.addMetrics(pairBuilder.build());
2934           }
2935 
2936           builder.setScanMetrics(metricBuilder.build());
2937         }
2938       }
2939       long end = EnvironmentEdgeManager.currentTime();
2940       long responseCellSize = context != null ? context.getResponseCellSize() : 0;
2941       region.getMetrics().updateScanTime(end - before);
2942       if (regionServer.metricsRegionServer != null) {
2943         regionServer.metricsRegionServer.updateScanSize(
2944             region.getTableDesc().getTableName(), responseCellSize);
2945         regionServer.metricsRegionServer.updateScanTime(
2946             region.getTableDesc().getTableName(), end - before);
2947       }
2948     } finally {
2949       region.closeRegionOperation();
2950     }
2951     // coprocessor postNext hook
2952     if (region.getCoprocessorHost() != null) {
2953       region.getCoprocessorHost().postScannerNext(scanner, results, maxResults, true);
2954     }
2955   }
2956 
2957   /**
2958    * Scan data in a table.
2959    *
2960    * @param controller the RPC controller
2961    * @param request the scan request
2962    * @throws ServiceException
2963    */
2964   @Override
2965   public ScanResponse scan(final RpcController controller, final ScanRequest request)
2966       throws ServiceException {
2967     if (controller != null && !(controller instanceof HBaseRpcController)) {
2968       throw new UnsupportedOperationException(
2969           "We only do HBaseRpcController! FIX IF A PROBLEM: " + controller);
2970     }
2971     if (!request.hasScannerId() && !request.hasScan()) {
2972       throw new ServiceException(
2973           new DoNotRetryIOException("Missing required input: scannerId or scan"));
2974     }
2975     try {
2976       checkOpen();
2977     } catch (IOException e) {
2978       if (request.hasScannerId()) {
2979         String scannerName = Long.toString(request.getScannerId());
2980         if (LOG.isDebugEnabled()) {
2981           LOG.debug(
2982             "Server shutting down and client tried to access missing scanner " + scannerName);
2983         }
2984         if (regionServer.leases != null) {
2985           try {
2986             regionServer.leases.cancelLease(scannerName);
2987           } catch (LeaseException le) {
2988             // No problem, ignore
2989             if (LOG.isTraceEnabled()) {
2990               LOG.trace("Un-able to cancel lease of scanner. It could already be closed.");
2991             }
2992           }
2993         }
2994       }
2995       throw new ServiceException(e);
2996     }
2997     requestCount.increment();
2998     rpcScanRequestCount.increment();
2999     RegionScannerHolder rsh;
3000     ScanResponse.Builder builder = ScanResponse.newBuilder();
3001     try {
3002       if (request.hasScannerId()) {
3003         // The downstream projects such as AsyncHBase in OpenTSDB need this value. See HBASE-18000
3004         // for more details.
3005         builder.setScannerId(request.getScannerId());
3006         rsh = getRegionScanner(request);
3007       } else {
3008         rsh = newRegionScanner(request, builder);
3009       }
3010     } catch (IOException e) {
3011       if (e == SCANNER_ALREADY_CLOSED) {
3012         // Now we will close scanner automatically if there are no more results for this region but
3013         // the old client will still send a close request to us. Just ignore it and return.
3014         return builder.build();
3015       }
3016       throw new ServiceException(e);
3017     }
3018     Region region = rsh.r;
3019     String scannerName = rsh.scannerName;
3020     Leases.Lease lease;
3021     try {
3022       // Remove lease while its being processed in server; protects against case
3023       // where processing of request takes > lease expiration time.
3024       lease = regionServer.leases.removeLease(scannerName);
3025     } catch (LeaseException e) {
3026       throw new ServiceException(e);
3027     }
3028     if (request.hasRenew() && request.getRenew()) {
3029       // add back and return
3030       addScannerLeaseBack(lease);
3031       try {
3032         checkScanNextCallSeq(request, rsh);
3033       } catch (OutOfOrderScannerNextException e) {
3034         throw new ServiceException(e);
3035       }
3036       return builder.build();
3037     }
3038     OperationQuota quota;
3039     try {
3040       quota = getQuotaManager().checkQuota(region, OperationQuota.OperationType.SCAN);
3041     } catch (IOException e) {
3042       addScannerLeaseBack(lease);
3043       throw new ServiceException(e);
3044     };
3045     try {
3046       checkScanNextCallSeq(request, rsh);
3047     } catch (OutOfOrderScannerNextException e) {
3048       addScannerLeaseBack(lease);
3049       throw new ServiceException(e);
3050     }
3051     // Now we have increased the next call sequence. If we give client an error, the retry will
3052     // never success. So we'd better close the scanner and return a DoNotRetryIOException to client
3053     // and then client will try to open a new scanner.
3054     boolean closeScanner = request.hasCloseScanner() ? request.getCloseScanner() : false;
3055     int rows; // this is scan.getCaching
3056     if (request.hasNumberOfRows()) {
3057       rows = request.getNumberOfRows();
3058     } else {
3059       rows = closeScanner ? 0 : 1;
3060     }
3061     RpcCallContext context = RpcServer.getCurrentCall();
3062     // now let's do the real scan.
3063     long maxQuotaResultSize = Math.min(maxScannerResultSize, quota.getReadAvailable());
3064     RegionScanner scanner = rsh.s;
3065     // this is the limit of rows for this scan, if we the number of rows reach this value, we will
3066     // close the scanner.
3067     int limitOfRows;
3068     if (request.hasLimitOfRows()) {
3069       limitOfRows = request.getLimitOfRows();
3070     } else {
3071       limitOfRows = -1;
3072     }
3073     MutableObject lastBlock = new MutableObject();
3074     boolean scannerClosed = false;
3075     try {
3076       List<Result> results = new ArrayList<>();
3077       if (rows > 0) {
3078         boolean done = false;
3079         // Call coprocessor. Get region info from scanner.
3080         if (region.getCoprocessorHost() != null) {
3081           Boolean bypass = region.getCoprocessorHost().preScannerNext(scanner, results, rows);
3082           if (!results.isEmpty()) {
3083             for (Result r : results) {
3084               lastBlock.setValue(addSize(context, r, lastBlock.getValue()));
3085             }
3086           }
3087           if (bypass != null && bypass.booleanValue()) {
3088             done = true;
3089           }
3090         }
3091         if (!done) {
3092           scan((HBaseRpcController) controller, request, rsh, maxQuotaResultSize, rows, limitOfRows,
3093             results, builder, lastBlock, context);
3094         } else {
3095           builder.setMoreResultsInRegion(!results.isEmpty());
3096         }
3097       } else {
3098         // This is a open scanner call with numberOfRow = 0, so set more results in region to true.
3099         builder.setMoreResultsInRegion(true);
3100       }
3101 
3102       quota.addScanResult(results);
3103       addResults(builder, results, (HBaseRpcController) controller,
3104         RegionReplicaUtil.isDefaultReplica(region.getRegionInfo()));
3105       if (scanner.isFilterDone() && results.isEmpty()) {
3106         // If the scanner's filter - if any - is done with the scan
3107         // only set moreResults to false if the results is empty. This is used to keep compatible
3108         // with the old scan implementation where we just ignore the returned results if moreResults
3109         // is false. Can remove the isEmpty check after we get rid of the old implementation.
3110         builder.setMoreResults(false);
3111       }
3112       // Later we may close the scanner depending on this flag so here we need to make sure that we
3113       // have already set this flag.
3114       assert builder.hasMoreResultsInRegion();
3115       // we only set moreResults to false in the above code, so set it to true if we haven't set it
3116       // yet.
3117       if (!builder.hasMoreResults()) {
3118         builder.setMoreResults(true);
3119       }
3120       if (builder.getMoreResults() && builder.getMoreResultsInRegion() && !results.isEmpty()) {
3121         // Record the last cell of the last result if it is a partial result
3122         // We need this to calculate the complete rows we have returned to client as the
3123         // mayHaveMoreCellsInRow is true does not mean that there will be extra cells for the
3124         // current row. We may filter out all the remaining cells for the current row and just
3125         // return the cells of the nextRow when calling RegionScanner.nextRaw. So here we need to
3126         // check for row change.
3127         Result lastResult = results.get(results.size() - 1);
3128         if (lastResult.mayHaveMoreCellsInRow()) {
3129           rsh.rowOfLastPartialResult = lastResult.getRow();
3130         } else {
3131           rsh.rowOfLastPartialResult = null;
3132         }
3133       }
3134       if (!builder.getMoreResults() || !builder.getMoreResultsInRegion() || closeScanner) {
3135         scannerClosed = true;
3136         closeScanner(region, scanner, scannerName, context);
3137       }
3138       return builder.build();
3139     } catch (IOException e) {
3140       try {
3141         // scanner is closed here
3142         scannerClosed = true;
3143         // The scanner state might be left in a dirty state, so we will tell the Client to
3144         // fail this RPC and close the scanner while opening up another one from the start of
3145         // row that the client has last seen.
3146         closeScanner(region, scanner, scannerName, context);
3147 
3148         // If it is a DoNotRetryIOException already, throw as it is. Unfortunately, DNRIOE is
3149         // used in two different semantics.
3150         // (1) The first is to close the client scanner and bubble up the exception all the way
3151         // to the application. This is preferred when the exception is really un-recoverable
3152         // (like CorruptHFileException, etc). Plain DoNotRetryIOException also falls into this
3153         // bucket usually.
3154         // (2) Second semantics is to close the current region scanner only, but continue the
3155         // client scanner by overriding the exception. This is usually UnknownScannerException,
3156         // OutOfOrderScannerNextException, etc where the region scanner has to be closed, but the
3157         // application-level ClientScanner has to continue without bubbling up the exception to
3158         // the client. See ClientScanner code to see how it deals with these special exceptions.
3159         if (e instanceof DoNotRetryIOException) {
3160           throw e;
3161         }
3162 
3163         // If it is a FileNotFoundException, wrap as a
3164         // DoNotRetryIOException. This can avoid the retry in ClientScanner.
3165         if (e instanceof FileNotFoundException) {
3166           throw new DoNotRetryIOException(e);
3167         }
3168 
3169         // We closed the scanner already. Instead of throwing the IOException, and client
3170         // retrying with the same scannerId only to get USE on the next RPC, we directly throw
3171         // a special exception to save an RPC.
3172         if (VersionInfoUtil.hasMinimumVersion(context.getClientVersionInfo(), 1, 4)) {
3173           // 1.4.0+ clients know how to handle
3174           throw new ScannerResetException("Scanner is closed on the server-side", e);
3175         } else {
3176           // older clients do not know about SRE. Just throw USE, which they will handle
3177           throw new UnknownScannerException("Throwing UnknownScannerException to reset the client"
3178               + " scanner state for clients older than 1.4.", e);
3179         }
3180       } catch (IOException ioe) {
3181         throw new ServiceException(ioe);
3182       }
3183     } finally {
3184       if (!scannerClosed) {
3185         // Adding resets expiration time on lease.
3186         addScannerLeaseBack(lease);
3187       }
3188       quota.close();
3189     }
3190   }
3191 
3192   private void closeScanner(Region region, RegionScanner scanner, String scannerName,
3193       RpcCallContext context) throws IOException {
3194     if (region.getCoprocessorHost() != null) {
3195       if (region.getCoprocessorHost().preScannerClose(scanner)) {
3196         // bypass the actual close.
3197         return;
3198       }
3199     }
3200     RegionScannerHolder rsh = scanners.remove(scannerName);
3201     if (rsh != null) {
3202       rsh.s.close();
3203       if (region.getCoprocessorHost() != null) {
3204         region.getCoprocessorHost().postScannerClose(scanner);
3205       }
3206       closedScanners.put(scannerName, scannerName);
3207     }
3208   }
3209 
3210   @Override
3211   public CoprocessorServiceResponse execRegionServerService(RpcController controller,
3212       CoprocessorServiceRequest request) throws ServiceException {
3213     return regionServer.execRegionServerService(controller, request);
3214   }
3215 
3216   @Override
3217   public UpdateConfigurationResponse updateConfiguration(
3218       RpcController controller, UpdateConfigurationRequest request)
3219       throws ServiceException {
3220     try {
3221       this.regionServer.updateConfiguration();
3222     } catch (Exception e) {
3223       throw new ServiceException(e);
3224     }
3225     return UpdateConfigurationResponse.getDefaultInstance();
3226   }
3227 }