001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import static org.apache.hadoop.hbase.HConstants.REPLICATION_SCOPE_LOCAL;
021import static org.apache.hadoop.hbase.regionserver.HStoreFile.MAJOR_COMPACTION_KEY;
022import static org.apache.hadoop.hbase.util.CollectionUtils.computeIfAbsent;
023
024import edu.umd.cs.findbugs.annotations.Nullable;
025import java.io.EOFException;
026import java.io.FileNotFoundException;
027import java.io.IOException;
028import java.io.InterruptedIOException;
029import java.lang.reflect.Constructor;
030import java.nio.ByteBuffer;
031import java.nio.charset.StandardCharsets;
032import java.text.ParseException;
033import java.util.AbstractList;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.Collection;
037import java.util.Collections;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Map;
043import java.util.Map.Entry;
044import java.util.NavigableMap;
045import java.util.NavigableSet;
046import java.util.Optional;
047import java.util.RandomAccess;
048import java.util.Set;
049import java.util.TreeMap;
050import java.util.UUID;
051import java.util.concurrent.Callable;
052import java.util.concurrent.CompletionService;
053import java.util.concurrent.ConcurrentHashMap;
054import java.util.concurrent.ConcurrentMap;
055import java.util.concurrent.ConcurrentSkipListMap;
056import java.util.concurrent.ExecutionException;
057import java.util.concurrent.ExecutorCompletionService;
058import java.util.concurrent.ExecutorService;
059import java.util.concurrent.Executors;
060import java.util.concurrent.Future;
061import java.util.concurrent.FutureTask;
062import java.util.concurrent.ThreadFactory;
063import java.util.concurrent.ThreadPoolExecutor;
064import java.util.concurrent.TimeUnit;
065import java.util.concurrent.TimeoutException;
066import java.util.concurrent.atomic.AtomicBoolean;
067import java.util.concurrent.atomic.AtomicInteger;
068import java.util.concurrent.atomic.LongAdder;
069import java.util.concurrent.locks.Lock;
070import java.util.concurrent.locks.ReadWriteLock;
071import java.util.concurrent.locks.ReentrantReadWriteLock;
072import java.util.function.Function;
073import org.apache.hadoop.conf.Configuration;
074import org.apache.hadoop.fs.FileStatus;
075import org.apache.hadoop.fs.FileSystem;
076import org.apache.hadoop.fs.LocatedFileStatus;
077import org.apache.hadoop.fs.Path;
078import org.apache.hadoop.hbase.Cell;
079import org.apache.hadoop.hbase.CellBuilderType;
080import org.apache.hadoop.hbase.CellComparator;
081import org.apache.hadoop.hbase.CellComparatorImpl;
082import org.apache.hadoop.hbase.CellScanner;
083import org.apache.hadoop.hbase.CellUtil;
084import org.apache.hadoop.hbase.CompareOperator;
085import org.apache.hadoop.hbase.CompoundConfiguration;
086import org.apache.hadoop.hbase.DoNotRetryIOException;
087import org.apache.hadoop.hbase.DroppedSnapshotException;
088import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
089import org.apache.hadoop.hbase.HConstants;
090import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
091import org.apache.hadoop.hbase.HDFSBlocksDistribution;
092import org.apache.hadoop.hbase.KeyValue;
093import org.apache.hadoop.hbase.KeyValueUtil;
094import org.apache.hadoop.hbase.NamespaceDescriptor;
095import org.apache.hadoop.hbase.NotServingRegionException;
096import org.apache.hadoop.hbase.PrivateCellUtil;
097import org.apache.hadoop.hbase.RegionTooBusyException;
098import org.apache.hadoop.hbase.TableName;
099import org.apache.hadoop.hbase.Tag;
100import org.apache.hadoop.hbase.TagUtil;
101import org.apache.hadoop.hbase.UnknownScannerException;
102import org.apache.hadoop.hbase.client.Append;
103import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
104import org.apache.hadoop.hbase.client.CompactionState;
105import org.apache.hadoop.hbase.client.Delete;
106import org.apache.hadoop.hbase.client.Durability;
107import org.apache.hadoop.hbase.client.Get;
108import org.apache.hadoop.hbase.client.Increment;
109import org.apache.hadoop.hbase.client.IsolationLevel;
110import org.apache.hadoop.hbase.client.Mutation;
111import org.apache.hadoop.hbase.client.PackagePrivateFieldAccessor;
112import org.apache.hadoop.hbase.client.Put;
113import org.apache.hadoop.hbase.client.RegionInfo;
114import org.apache.hadoop.hbase.client.RegionInfoBuilder;
115import org.apache.hadoop.hbase.client.RegionReplicaUtil;
116import org.apache.hadoop.hbase.client.Result;
117import org.apache.hadoop.hbase.client.RowMutations;
118import org.apache.hadoop.hbase.client.Scan;
119import org.apache.hadoop.hbase.client.TableDescriptor;
120import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
121import org.apache.hadoop.hbase.conf.ConfigurationManager;
122import org.apache.hadoop.hbase.conf.PropagatingConfigurationObserver;
123import org.apache.hadoop.hbase.coprocessor.RegionObserver.MutationType;
124import org.apache.hadoop.hbase.errorhandling.ForeignExceptionSnare;
125import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException;
126import org.apache.hadoop.hbase.exceptions.TimeoutIOException;
127import org.apache.hadoop.hbase.exceptions.UnknownProtocolException;
128import org.apache.hadoop.hbase.filter.ByteArrayComparable;
129import org.apache.hadoop.hbase.filter.FilterWrapper;
130import org.apache.hadoop.hbase.filter.IncompatibleFilterException;
131import org.apache.hadoop.hbase.io.HFileLink;
132import org.apache.hadoop.hbase.io.HeapSize;
133import org.apache.hadoop.hbase.io.TimeRange;
134import org.apache.hadoop.hbase.io.hfile.HFile;
135import org.apache.hadoop.hbase.ipc.CallerDisconnectedException;
136import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
137import org.apache.hadoop.hbase.ipc.RpcCall;
138import org.apache.hadoop.hbase.ipc.RpcServer;
139import org.apache.hadoop.hbase.monitoring.MonitoredTask;
140import org.apache.hadoop.hbase.monitoring.TaskMonitor;
141import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl.WriteEntry;
142import org.apache.hadoop.hbase.regionserver.ScannerContext.LimitScope;
143import org.apache.hadoop.hbase.regionserver.ScannerContext.NextState;
144import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
145import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
146import org.apache.hadoop.hbase.regionserver.throttle.CompactionThroughputControllerFactory;
147import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
148import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController;
149import org.apache.hadoop.hbase.regionserver.wal.WALUtil;
150import org.apache.hadoop.hbase.security.User;
151import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
152import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
153import org.apache.hadoop.hbase.trace.TraceUtil;
154import org.apache.hadoop.hbase.util.Bytes;
155import org.apache.hadoop.hbase.util.CancelableProgressable;
156import org.apache.hadoop.hbase.util.ClassSize;
157import org.apache.hadoop.hbase.util.CollectionUtils;
158import org.apache.hadoop.hbase.util.CompressionTest;
159import org.apache.hadoop.hbase.util.EncryptionTest;
160import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
161import org.apache.hadoop.hbase.util.FSUtils;
162import org.apache.hadoop.hbase.util.HashedBytes;
163import org.apache.hadoop.hbase.util.NonceKey;
164import org.apache.hadoop.hbase.util.Pair;
165import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
166import org.apache.hadoop.hbase.util.Threads;
167import org.apache.hadoop.hbase.wal.WAL;
168import org.apache.hadoop.hbase.wal.WALEdit;
169import org.apache.hadoop.hbase.wal.WALFactory;
170import org.apache.hadoop.hbase.wal.WALKey;
171import org.apache.hadoop.hbase.wal.WALKeyImpl;
172import org.apache.hadoop.hbase.wal.WALSplitter;
173import org.apache.hadoop.hbase.wal.WALSplitter.MutationReplay;
174import org.apache.hadoop.io.MultipleIOException;
175import org.apache.hadoop.util.StringUtils;
176import org.apache.htrace.core.TraceScope;
177import org.apache.yetus.audience.InterfaceAudience;
178import org.slf4j.Logger;
179import org.slf4j.LoggerFactory;
180
181import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
182import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
183import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
184import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
185import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
186import org.apache.hbase.thirdparty.com.google.protobuf.Service;
187import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat;
188import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
189
190import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
191import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
192import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceCall;
193import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionLoad;
194import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.StoreSequenceId;
195import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
196import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos;
197import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.CompactionDescriptor;
198import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor;
199import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor.FlushAction;
200import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor.StoreFlushDescriptor;
201import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor;
202import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor.EventType;
203import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.StoreDescriptor;
204
205/**
206 * Regions store data for a certain region of a table.  It stores all columns
207 * for each row. A given table consists of one or more Regions.
208 *
209 * <p>An Region is defined by its table and its key extent.
210 *
211 * <p>Locking at the Region level serves only one purpose: preventing the
212 * region from being closed (and consequently split) while other operations
213 * are ongoing. Each row level operation obtains both a row lock and a region
214 * read lock for the duration of the operation. While a scanner is being
215 * constructed, getScanner holds a read lock. If the scanner is successfully
216 * constructed, it holds a read lock until it is closed. A close takes out a
217 * write lock and consequently will block for ongoing operations and will block
218 * new operations from starting while the close is in progress.
219 */
220@SuppressWarnings("deprecation")
221@InterfaceAudience.Private
222public class HRegion implements HeapSize, PropagatingConfigurationObserver, Region {
223  private static final Logger LOG = LoggerFactory.getLogger(HRegion.class);
224
225  public static final String LOAD_CFS_ON_DEMAND_CONFIG_KEY =
226    "hbase.hregion.scan.loadColumnFamiliesOnDemand";
227
228  public static final String HBASE_MAX_CELL_SIZE_KEY = "hbase.server.keyvalue.maxsize";
229  public static final int DEFAULT_MAX_CELL_SIZE = 10485760;
230
231  public static final String HBASE_REGIONSERVER_MINIBATCH_SIZE =
232      "hbase.regionserver.minibatch.size";
233  public static final int DEFAULT_HBASE_REGIONSERVER_MINIBATCH_SIZE = 20000;
234
235  /**
236   * This is the global default value for durability. All tables/mutations not
237   * defining a durability or using USE_DEFAULT will default to this value.
238   */
239  private static final Durability DEFAULT_DURABILITY = Durability.SYNC_WAL;
240
241  final AtomicBoolean closed = new AtomicBoolean(false);
242
243  /* Closing can take some time; use the closing flag if there is stuff we don't
244   * want to do while in closing state; e.g. like offer this region up to the
245   * master as a region to close if the carrying regionserver is overloaded.
246   * Once set, it is never cleared.
247   */
248  final AtomicBoolean closing = new AtomicBoolean(false);
249
250  /**
251   * The max sequence id of flushed data on this region. There is no edit in memory that is
252   * less that this sequence id.
253   */
254  private volatile long maxFlushedSeqId = HConstants.NO_SEQNUM;
255
256  /**
257   * Record the sequence id of last flush operation. Can be in advance of
258   * {@link #maxFlushedSeqId} when flushing a single column family. In this case,
259   * {@link #maxFlushedSeqId} will be older than the oldest edit in memory.
260   */
261  private volatile long lastFlushOpSeqId = HConstants.NO_SEQNUM;
262
263  /**
264   * The sequence id of the last replayed open region event from the primary region. This is used
265   * to skip entries before this due to the possibility of replay edits coming out of order from
266   * replication.
267   */
268  protected volatile long lastReplayedOpenRegionSeqId = -1L;
269  protected volatile long lastReplayedCompactionSeqId = -1L;
270
271  //////////////////////////////////////////////////////////////////////////////
272  // Members
273  //////////////////////////////////////////////////////////////////////////////
274
275  // map from a locked row to the context for that lock including:
276  // - CountDownLatch for threads waiting on that row
277  // - the thread that owns the lock (allow reentrancy)
278  // - reference count of (reentrant) locks held by the thread
279  // - the row itself
280  private final ConcurrentHashMap<HashedBytes, RowLockContext> lockedRows =
281      new ConcurrentHashMap<>();
282
283  protected final Map<byte[], HStore> stores =
284      new ConcurrentSkipListMap<>(Bytes.BYTES_RAWCOMPARATOR);
285
286  // TODO: account for each registered handler in HeapSize computation
287  private Map<String, com.google.protobuf.Service> coprocessorServiceHandlers = Maps.newHashMap();
288
289  // Track data size in all memstores
290  private final MemStoreSizing memStoreSizing = new ThreadSafeMemStoreSizing();
291  private final RegionServicesForStores regionServicesForStores = new RegionServicesForStores(this);
292
293  // Debug possible data loss due to WAL off
294  final LongAdder numMutationsWithoutWAL = new LongAdder();
295  final LongAdder dataInMemoryWithoutWAL = new LongAdder();
296
297  // Debug why CAS operations are taking a while.
298  final LongAdder checkAndMutateChecksPassed = new LongAdder();
299  final LongAdder checkAndMutateChecksFailed = new LongAdder();
300
301  // Number of requests
302  // Count rows for scan
303  final LongAdder readRequestsCount = new LongAdder();
304  final LongAdder filteredReadRequestsCount = new LongAdder();
305  // Count rows for multi row mutations
306  final LongAdder writeRequestsCount = new LongAdder();
307
308  // Number of requests blocked by memstore size.
309  private final LongAdder blockedRequestsCount = new LongAdder();
310
311  // Compaction LongAdders
312  final LongAdder compactionsFinished = new LongAdder();
313  final LongAdder compactionsFailed = new LongAdder();
314  final LongAdder compactionNumFilesCompacted = new LongAdder();
315  final LongAdder compactionNumBytesCompacted = new LongAdder();
316  final LongAdder compactionsQueued = new LongAdder();
317  final LongAdder flushesQueued = new LongAdder();
318
319  private final WAL wal;
320  private final HRegionFileSystem fs;
321  protected final Configuration conf;
322  private final Configuration baseConf;
323  private final int rowLockWaitDuration;
324  static final int DEFAULT_ROWLOCK_WAIT_DURATION = 30000;
325
326  // The internal wait duration to acquire a lock before read/update
327  // from the region. It is not per row. The purpose of this wait time
328  // is to avoid waiting a long time while the region is busy, so that
329  // we can release the IPC handler soon enough to improve the
330  // availability of the region server. It can be adjusted by
331  // tuning configuration "hbase.busy.wait.duration".
332  final long busyWaitDuration;
333  static final long DEFAULT_BUSY_WAIT_DURATION = HConstants.DEFAULT_HBASE_RPC_TIMEOUT;
334
335  // If updating multiple rows in one call, wait longer,
336  // i.e. waiting for busyWaitDuration * # of rows. However,
337  // we can limit the max multiplier.
338  final int maxBusyWaitMultiplier;
339
340  // Max busy wait duration. There is no point to wait longer than the RPC
341  // purge timeout, when a RPC call will be terminated by the RPC engine.
342  final long maxBusyWaitDuration;
343
344  // Max cell size. If nonzero, the maximum allowed size for any given cell
345  // in bytes
346  final long maxCellSize;
347
348  // Number of mutations for minibatch processing.
349  private final int miniBatchSize;
350
351  // negative number indicates infinite timeout
352  static final long DEFAULT_ROW_PROCESSOR_TIMEOUT = 60 * 1000L;
353  final ExecutorService rowProcessorExecutor = Executors.newCachedThreadPool();
354
355  private final ConcurrentHashMap<RegionScanner, Long> scannerReadPoints;
356
357  /**
358   * The sequence ID that was enLongAddered when this region was opened.
359   */
360  private long openSeqNum = HConstants.NO_SEQNUM;
361
362  /**
363   * The default setting for whether to enable on-demand CF loading for
364   * scan requests to this region. Requests can override it.
365   */
366  private boolean isLoadingCfsOnDemandDefault = false;
367
368  private final AtomicInteger majorInProgress = new AtomicInteger(0);
369  private final AtomicInteger minorInProgress = new AtomicInteger(0);
370
371  //
372  // Context: During replay we want to ensure that we do not lose any data. So, we
373  // have to be conservative in how we replay wals. For each store, we calculate
374  // the maxSeqId up to which the store was flushed. And, skip the edits which
375  // are equal to or lower than maxSeqId for each store.
376  // The following map is populated when opening the region
377  Map<byte[], Long> maxSeqIdInStores = new TreeMap<>(Bytes.BYTES_COMPARATOR);
378
379  /** Saved state from replaying prepare flush cache */
380  private PrepareFlushResult prepareFlushResult = null;
381
382  private volatile Optional<ConfigurationManager> configurationManager;
383
384  // Used for testing.
385  private volatile Long timeoutForWriteLock = null;
386
387  /**
388   * @return The smallest mvcc readPoint across all the scanners in this
389   * region. Writes older than this readPoint, are included in every
390   * read operation.
391   */
392  public long getSmallestReadPoint() {
393    long minimumReadPoint;
394    // We need to ensure that while we are calculating the smallestReadPoint
395    // no new RegionScanners can grab a readPoint that we are unaware of.
396    // We achieve this by synchronizing on the scannerReadPoints object.
397    synchronized (scannerReadPoints) {
398      minimumReadPoint = mvcc.getReadPoint();
399      for (Long readPoint : this.scannerReadPoints.values()) {
400        if (readPoint < minimumReadPoint) {
401          minimumReadPoint = readPoint;
402        }
403      }
404    }
405    return minimumReadPoint;
406  }
407
408  /*
409   * Data structure of write state flags used coordinating flushes,
410   * compactions and closes.
411   */
412  static class WriteState {
413    // Set while a memstore flush is happening.
414    volatile boolean flushing = false;
415    // Set when a flush has been requested.
416    volatile boolean flushRequested = false;
417    // Number of compactions running.
418    AtomicInteger compacting = new AtomicInteger(0);
419    // Gets set in close. If set, cannot compact or flush again.
420    volatile boolean writesEnabled = true;
421    // Set if region is read-only
422    volatile boolean readOnly = false;
423    // whether the reads are enabled. This is different than readOnly, because readOnly is
424    // static in the lifetime of the region, while readsEnabled is dynamic
425    volatile boolean readsEnabled = true;
426
427    /**
428     * Set flags that make this region read-only.
429     *
430     * @param onOff flip value for region r/o setting
431     */
432    synchronized void setReadOnly(final boolean onOff) {
433      this.writesEnabled = !onOff;
434      this.readOnly = onOff;
435    }
436
437    boolean isReadOnly() {
438      return this.readOnly;
439    }
440
441    boolean isFlushRequested() {
442      return this.flushRequested;
443    }
444
445    void setReadsEnabled(boolean readsEnabled) {
446      this.readsEnabled = readsEnabled;
447    }
448
449    static final long HEAP_SIZE = ClassSize.align(
450        ClassSize.OBJECT + 5 * Bytes.SIZEOF_BOOLEAN);
451  }
452
453  /**
454   * Objects from this class are created when flushing to describe all the different states that
455   * that method ends up in. The Result enum describes those states. The sequence id should only
456   * be specified if the flush was successful, and the failure message should only be specified
457   * if it didn't flush.
458   */
459  public static class FlushResultImpl implements FlushResult {
460    final Result result;
461    final String failureReason;
462    final long flushSequenceId;
463    final boolean wroteFlushWalMarker;
464
465    /**
466     * Convenience constructor to use when the flush is successful, the failure message is set to
467     * null.
468     * @param result Expecting FLUSHED_NO_COMPACTION_NEEDED or FLUSHED_COMPACTION_NEEDED.
469     * @param flushSequenceId Generated sequence id that comes right after the edits in the
470     *                        memstores.
471     */
472    FlushResultImpl(Result result, long flushSequenceId) {
473      this(result, flushSequenceId, null, false);
474      assert result == Result.FLUSHED_NO_COMPACTION_NEEDED || result == Result
475          .FLUSHED_COMPACTION_NEEDED;
476    }
477
478    /**
479     * Convenience constructor to use when we cannot flush.
480     * @param result Expecting CANNOT_FLUSH_MEMSTORE_EMPTY or CANNOT_FLUSH.
481     * @param failureReason Reason why we couldn't flush.
482     */
483    FlushResultImpl(Result result, String failureReason, boolean wroteFlushMarker) {
484      this(result, -1, failureReason, wroteFlushMarker);
485      assert result == Result.CANNOT_FLUSH_MEMSTORE_EMPTY || result == Result.CANNOT_FLUSH;
486    }
487
488    /**
489     * Constructor with all the parameters.
490     * @param result Any of the Result.
491     * @param flushSequenceId Generated sequence id if the memstores were flushed else -1.
492     * @param failureReason Reason why we couldn't flush, or null.
493     */
494    FlushResultImpl(Result result, long flushSequenceId, String failureReason,
495      boolean wroteFlushMarker) {
496      this.result = result;
497      this.flushSequenceId = flushSequenceId;
498      this.failureReason = failureReason;
499      this.wroteFlushWalMarker = wroteFlushMarker;
500    }
501
502    /**
503     * Convenience method, the equivalent of checking if result is
504     * FLUSHED_NO_COMPACTION_NEEDED or FLUSHED_NO_COMPACTION_NEEDED.
505     * @return true if the memstores were flushed, else false.
506     */
507    @Override
508    public boolean isFlushSucceeded() {
509      return result == Result.FLUSHED_NO_COMPACTION_NEEDED || result == Result
510          .FLUSHED_COMPACTION_NEEDED;
511    }
512
513    /**
514     * Convenience method, the equivalent of checking if result is FLUSHED_COMPACTION_NEEDED.
515     * @return True if the flush requested a compaction, else false (doesn't even mean it flushed).
516     */
517    @Override
518    public boolean isCompactionNeeded() {
519      return result == Result.FLUSHED_COMPACTION_NEEDED;
520    }
521
522    @Override
523    public String toString() {
524      return new StringBuilder()
525        .append("flush result:").append(result).append(", ")
526        .append("failureReason:").append(failureReason).append(",")
527        .append("flush seq id").append(flushSequenceId).toString();
528    }
529
530    @Override
531    public Result getResult() {
532      return result;
533    }
534  }
535
536  /** A result object from prepare flush cache stage */
537  @VisibleForTesting
538  static class PrepareFlushResult {
539    final FlushResultImpl result; // indicating a failure result from prepare
540    final TreeMap<byte[], StoreFlushContext> storeFlushCtxs;
541    final TreeMap<byte[], List<Path>> committedFiles;
542    final TreeMap<byte[], MemStoreSize> storeFlushableSize;
543    final long startTime;
544    final long flushOpSeqId;
545    final long flushedSeqId;
546    final MemStoreSizing totalFlushableSize;
547
548    /** Constructs an early exit case */
549    PrepareFlushResult(FlushResultImpl result, long flushSeqId) {
550      this(result, null, null, null, Math.max(0, flushSeqId), 0, 0, MemStoreSizing.DUD);
551    }
552
553    /** Constructs a successful prepare flush result */
554    PrepareFlushResult(
555      TreeMap<byte[], StoreFlushContext> storeFlushCtxs,
556      TreeMap<byte[], List<Path>> committedFiles,
557      TreeMap<byte[], MemStoreSize> storeFlushableSize, long startTime, long flushSeqId,
558      long flushedSeqId, MemStoreSizing totalFlushableSize) {
559      this(null, storeFlushCtxs, committedFiles, storeFlushableSize, startTime,
560        flushSeqId, flushedSeqId, totalFlushableSize);
561    }
562
563    private PrepareFlushResult(
564        FlushResultImpl result,
565      TreeMap<byte[], StoreFlushContext> storeFlushCtxs,
566      TreeMap<byte[], List<Path>> committedFiles,
567      TreeMap<byte[], MemStoreSize> storeFlushableSize, long startTime, long flushSeqId,
568      long flushedSeqId, MemStoreSizing totalFlushableSize) {
569      this.result = result;
570      this.storeFlushCtxs = storeFlushCtxs;
571      this.committedFiles = committedFiles;
572      this.storeFlushableSize = storeFlushableSize;
573      this.startTime = startTime;
574      this.flushOpSeqId = flushSeqId;
575      this.flushedSeqId = flushedSeqId;
576      this.totalFlushableSize = totalFlushableSize;
577    }
578
579    public FlushResult getResult() {
580      return this.result;
581    }
582  }
583
584  /**
585   * A class that tracks exceptions that have been observed in one batch. Not thread safe.
586   */
587  static class ObservedExceptionsInBatch {
588    private boolean wrongRegion = false;
589    private boolean failedSanityCheck = false;
590    private boolean wrongFamily = false;
591
592    /**
593     * @return If a {@link WrongRegionException} has been observed.
594     */
595    boolean hasSeenWrongRegion() {
596      return wrongRegion;
597    }
598
599    /**
600     * Records that a {@link WrongRegionException} has been observed.
601     */
602    void sawWrongRegion() {
603      wrongRegion = true;
604    }
605
606    /**
607     * @return If a {@link FailedSanityCheckException} has been observed.
608     */
609    boolean hasSeenFailedSanityCheck() {
610      return failedSanityCheck;
611    }
612
613    /**
614     * Records that a {@link FailedSanityCheckException} has been observed.
615     */
616    void sawFailedSanityCheck() {
617      failedSanityCheck = true;
618    }
619
620    /**
621     * @return If a {@link NoSuchColumnFamilyException} has been observed.
622     */
623    boolean hasSeenNoSuchFamily() {
624      return wrongFamily;
625    }
626
627    /**
628     * Records that a {@link NoSuchColumnFamilyException} has been observed.
629     */
630    void sawNoSuchFamily() {
631      wrongFamily = true;
632    }
633  }
634
635  final WriteState writestate = new WriteState();
636
637  long memstoreFlushSize;
638  final long timestampSlop;
639  final long rowProcessorTimeout;
640
641  // Last flush time for each Store. Useful when we are flushing for each column
642  private final ConcurrentMap<HStore, Long> lastStoreFlushTimeMap = new ConcurrentHashMap<>();
643
644  final RegionServerServices rsServices;
645  private RegionServerAccounting rsAccounting;
646  private long flushCheckInterval;
647  // flushPerChanges is to prevent too many changes in memstore
648  private long flushPerChanges;
649  private long blockingMemStoreSize;
650  // Used to guard closes
651  final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
652
653  // Stop updates lock
654  private final ReentrantReadWriteLock updatesLock = new ReentrantReadWriteLock();
655  private boolean splitRequest;
656  private byte[] explicitSplitPoint = null;
657
658  private final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl();
659
660  // Coprocessor host
661  private RegionCoprocessorHost coprocessorHost;
662
663  private TableDescriptor htableDescriptor = null;
664  private RegionSplitPolicy splitPolicy;
665  private FlushPolicy flushPolicy;
666
667  private final MetricsRegion metricsRegion;
668  private final MetricsRegionWrapperImpl metricsRegionWrapper;
669  private final Durability regionDurability;
670  private final boolean regionStatsEnabled;
671  // Stores the replication scope of the various column families of the table
672  // that has non-default scope
673  private final NavigableMap<byte[], Integer> replicationScope = new TreeMap<>(
674      Bytes.BYTES_COMPARATOR);
675
676  /**
677   * HRegion constructor. This constructor should only be used for testing and
678   * extensions.  Instances of HRegion should be instantiated with the
679   * {@link HRegion#createHRegion} or {@link HRegion#openHRegion} method.
680   *
681   * @param tableDir qualified path of directory where region should be located,
682   * usually the table directory.
683   * @param wal The WAL is the outbound log for any updates to the HRegion
684   * The wal file is a logfile from the previous execution that's
685   * custom-computed for this HRegion. The HRegionServer computes and sorts the
686   * appropriate wal info for this HRegion. If there is a previous wal file
687   * (implying that the HRegion has been written-to before), then read it from
688   * the supplied path.
689   * @param fs is the filesystem.
690   * @param confParam is global configuration settings.
691   * @param regionInfo - RegionInfo that describes the region
692   * is new), then read them from the supplied path.
693   * @param htd the table descriptor
694   * @param rsServices reference to {@link RegionServerServices} or null
695   * @deprecated Use other constructors.
696   */
697  @Deprecated
698  @VisibleForTesting
699  public HRegion(final Path tableDir, final WAL wal, final FileSystem fs,
700      final Configuration confParam, final RegionInfo regionInfo,
701      final TableDescriptor htd, final RegionServerServices rsServices) {
702    this(new HRegionFileSystem(confParam, fs, tableDir, regionInfo),
703      wal, confParam, htd, rsServices);
704  }
705
706  /**
707   * HRegion constructor. This constructor should only be used for testing and
708   * extensions.  Instances of HRegion should be instantiated with the
709   * {@link HRegion#createHRegion} or {@link HRegion#openHRegion} method.
710   *
711   * @param fs is the filesystem.
712   * @param wal The WAL is the outbound log for any updates to the HRegion
713   * The wal file is a logfile from the previous execution that's
714   * custom-computed for this HRegion. The HRegionServer computes and sorts the
715   * appropriate wal info for this HRegion. If there is a previous wal file
716   * (implying that the HRegion has been written-to before), then read it from
717   * the supplied path.
718   * @param confParam is global configuration settings.
719   * @param htd the table descriptor
720   * @param rsServices reference to {@link RegionServerServices} or null
721   */
722  public HRegion(final HRegionFileSystem fs, final WAL wal, final Configuration confParam,
723      final TableDescriptor htd, final RegionServerServices rsServices) {
724    if (htd == null) {
725      throw new IllegalArgumentException("Need table descriptor");
726    }
727
728    if (confParam instanceof CompoundConfiguration) {
729      throw new IllegalArgumentException("Need original base configuration");
730    }
731
732    this.wal = wal;
733    this.fs = fs;
734
735    // 'conf' renamed to 'confParam' b/c we use this.conf in the constructor
736    this.baseConf = confParam;
737    this.conf = new CompoundConfiguration()
738      .add(confParam)
739      .addBytesMap(htd.getValues());
740    this.flushCheckInterval = conf.getInt(MEMSTORE_PERIODIC_FLUSH_INTERVAL,
741        DEFAULT_CACHE_FLUSH_INTERVAL);
742    this.flushPerChanges = conf.getLong(MEMSTORE_FLUSH_PER_CHANGES, DEFAULT_FLUSH_PER_CHANGES);
743    if (this.flushPerChanges > MAX_FLUSH_PER_CHANGES) {
744      throw new IllegalArgumentException(MEMSTORE_FLUSH_PER_CHANGES + " can not exceed "
745          + MAX_FLUSH_PER_CHANGES);
746    }
747    this.rowLockWaitDuration = conf.getInt("hbase.rowlock.wait.duration",
748                    DEFAULT_ROWLOCK_WAIT_DURATION);
749
750    this.isLoadingCfsOnDemandDefault = conf.getBoolean(LOAD_CFS_ON_DEMAND_CONFIG_KEY, true);
751    this.htableDescriptor = htd;
752    Set<byte[]> families = this.htableDescriptor.getColumnFamilyNames();
753    for (byte[] family : families) {
754      if (!replicationScope.containsKey(family)) {
755        int scope = htd.getColumnFamily(family).getScope();
756        // Only store those families that has NON-DEFAULT scope
757        if (scope != REPLICATION_SCOPE_LOCAL) {
758          // Do a copy before storing it here.
759          replicationScope.put(Bytes.copy(family), scope);
760        }
761      }
762    }
763    this.rsServices = rsServices;
764    setHTableSpecificConf();
765    this.scannerReadPoints = new ConcurrentHashMap<>();
766
767    this.busyWaitDuration = conf.getLong(
768      "hbase.busy.wait.duration", DEFAULT_BUSY_WAIT_DURATION);
769    this.maxBusyWaitMultiplier = conf.getInt("hbase.busy.wait.multiplier.max", 2);
770    if (busyWaitDuration * maxBusyWaitMultiplier <= 0L) {
771      throw new IllegalArgumentException("Invalid hbase.busy.wait.duration ("
772        + busyWaitDuration + ") or hbase.busy.wait.multiplier.max ("
773        + maxBusyWaitMultiplier + "). Their product should be positive");
774    }
775    this.maxBusyWaitDuration = conf.getLong("hbase.ipc.client.call.purge.timeout",
776      2 * HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
777
778    /*
779     * timestamp.slop provides a server-side constraint on the timestamp. This
780     * assumes that you base your TS around currentTimeMillis(). In this case,
781     * throw an error to the user if the user-specified TS is newer than now +
782     * slop. LATEST_TIMESTAMP == don't use this functionality
783     */
784    this.timestampSlop = conf.getLong(
785        "hbase.hregion.keyvalue.timestamp.slop.millisecs",
786        HConstants.LATEST_TIMESTAMP);
787
788    /**
789     * Timeout for the process time in processRowsWithLocks().
790     * Use -1 to switch off time bound.
791     */
792    this.rowProcessorTimeout = conf.getLong(
793        "hbase.hregion.row.processor.timeout", DEFAULT_ROW_PROCESSOR_TIMEOUT);
794    this.regionDurability = htd.getDurability() == Durability.USE_DEFAULT ?
795        DEFAULT_DURABILITY : htd.getDurability();
796    if (rsServices != null) {
797      this.rsAccounting = this.rsServices.getRegionServerAccounting();
798      // don't initialize coprocessors if not running within a regionserver
799      // TODO: revisit if coprocessors should load in other cases
800      this.coprocessorHost = new RegionCoprocessorHost(this, rsServices, conf);
801      this.metricsRegionWrapper = new MetricsRegionWrapperImpl(this);
802      this.metricsRegion = new MetricsRegion(this.metricsRegionWrapper);
803    } else {
804      this.metricsRegionWrapper = null;
805      this.metricsRegion = null;
806    }
807    if (LOG.isDebugEnabled()) {
808      // Write out region name as string and its encoded name.
809      LOG.debug("Instantiated " + this);
810    }
811
812    configurationManager = Optional.empty();
813
814    // disable stats tracking system tables, but check the config for everything else
815    this.regionStatsEnabled = htd.getTableName().getNamespaceAsString().equals(
816        NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR) ?
817          false :
818          conf.getBoolean(HConstants.ENABLE_CLIENT_BACKPRESSURE,
819              HConstants.DEFAULT_ENABLE_CLIENT_BACKPRESSURE);
820
821    this.maxCellSize = conf.getLong(HBASE_MAX_CELL_SIZE_KEY, DEFAULT_MAX_CELL_SIZE);
822    this.miniBatchSize = conf.getInt(HBASE_REGIONSERVER_MINIBATCH_SIZE,
823        DEFAULT_HBASE_REGIONSERVER_MINIBATCH_SIZE);
824  }
825
826  void setHTableSpecificConf() {
827    if (this.htableDescriptor == null) return;
828    long flushSize = this.htableDescriptor.getMemStoreFlushSize();
829
830    if (flushSize <= 0) {
831      flushSize = conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE,
832          TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE);
833    }
834    this.memstoreFlushSize = flushSize;
835    long mult = conf.getLong(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER,
836        HConstants.DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER);
837    this.blockingMemStoreSize = this.memstoreFlushSize * mult;
838  }
839
840  /**
841   * Initialize this region.
842   * Used only by tests and SplitTransaction to reopen the region.
843   * You should use createHRegion() or openHRegion()
844   * @return What the next sequence (edit) id should be.
845   * @throws IOException e
846   * @deprecated use HRegion.createHRegion() or HRegion.openHRegion()
847   */
848  @Deprecated
849  public long initialize() throws IOException {
850    return initialize(null);
851  }
852
853  /**
854   * Initialize this region.
855   *
856   * @param reporter Tickle every so often if initialize is taking a while.
857   * @return What the next sequence (edit) id should be.
858   * @throws IOException e
859   */
860  @VisibleForTesting
861  long initialize(final CancelableProgressable reporter) throws IOException {
862
863    //Refuse to open the region if there is no column family in the table
864    if (htableDescriptor.getColumnFamilyCount() == 0) {
865      throw new DoNotRetryIOException("Table " + htableDescriptor.getTableName().getNameAsString()+
866          " should have at least one column family.");
867    }
868
869    MonitoredTask status = TaskMonitor.get().createStatus("Initializing region " + this);
870    long nextSeqId = -1;
871    try {
872      nextSeqId = initializeRegionInternals(reporter, status);
873      return nextSeqId;
874    } catch (IOException e) {
875      LOG.warn("Failed initialize of region= {}, starting to roll back memstore",
876          getRegionInfo().getRegionNameAsString(), e);
877      // global memstore size will be decreased when dropping memstore
878      try {
879        //drop the memory used by memstore if open region fails
880        dropMemStoreContents();
881      } catch (IOException ioE) {
882        if (conf.getBoolean(MemStoreLAB.USEMSLAB_KEY, MemStoreLAB.USEMSLAB_DEFAULT)) {
883          LOG.warn("Failed drop memstore of region= {}, "
884                  + "some chunks may not released forever since MSLAB is enabled",
885              getRegionInfo().getRegionNameAsString());
886        }
887
888      }
889      throw e;
890    } finally {
891      // nextSeqid will be -1 if the initialization fails.
892      // At least it will be 0 otherwise.
893      if (nextSeqId == -1) {
894        status.abort("Exception during region " + getRegionInfo().getRegionNameAsString() +
895          " initialization.");
896      }
897    }
898  }
899
900  private long initializeRegionInternals(final CancelableProgressable reporter,
901      final MonitoredTask status) throws IOException {
902    if (coprocessorHost != null) {
903      status.setStatus("Running coprocessor pre-open hook");
904      coprocessorHost.preOpen();
905    }
906
907    // Write HRI to a file in case we need to recover hbase:meta
908    // Only the primary replica should write .regioninfo
909    if (this.getRegionInfo().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID) {
910      status.setStatus("Writing region info on filesystem");
911      fs.checkRegionInfoOnFilesystem();
912    }
913
914    // Initialize all the HStores
915    status.setStatus("Initializing all the Stores");
916    long maxSeqId = initializeStores(reporter, status);
917    this.mvcc.advanceTo(maxSeqId);
918    if (ServerRegionReplicaUtil.shouldReplayRecoveredEdits(this)) {
919      Collection<HStore> stores = this.stores.values();
920      try {
921        // update the stores that we are replaying
922        stores.forEach(HStore::startReplayingFromWAL);
923        // Recover any edits if available.
924        maxSeqId = Math.max(maxSeqId,
925          replayRecoveredEditsIfAny(this.fs.getRegionDir(), maxSeqIdInStores, reporter, status));
926        // Make sure mvcc is up to max.
927        this.mvcc.advanceTo(maxSeqId);
928      } finally {
929        // update the stores that we are done replaying
930        stores.forEach(HStore::stopReplayingFromWAL);
931      }
932    }
933    this.lastReplayedOpenRegionSeqId = maxSeqId;
934
935    this.writestate.setReadOnly(ServerRegionReplicaUtil.isReadOnly(this));
936    this.writestate.flushRequested = false;
937    this.writestate.compacting.set(0);
938
939    if (this.writestate.writesEnabled) {
940      // Remove temporary data left over from old regions
941      status.setStatus("Cleaning up temporary data from old regions");
942      fs.cleanupTempDir();
943    }
944
945    if (this.writestate.writesEnabled) {
946      status.setStatus("Cleaning up detritus from prior splits");
947      // Get rid of any splits or merges that were lost in-progress.  Clean out
948      // these directories here on open.  We may be opening a region that was
949      // being split but we crashed in the middle of it all.
950      fs.cleanupAnySplitDetritus();
951      fs.cleanupMergesDir();
952    }
953
954    // Initialize split policy
955    this.splitPolicy = RegionSplitPolicy.create(this, conf);
956
957    // Initialize flush policy
958    this.flushPolicy = FlushPolicyFactory.create(this, conf);
959
960    long lastFlushTime = EnvironmentEdgeManager.currentTime();
961    for (HStore store: stores.values()) {
962      this.lastStoreFlushTimeMap.put(store, lastFlushTime);
963    }
964
965    // Use maximum of log sequenceid or that which was found in stores
966    // (particularly if no recovered edits, seqid will be -1).
967    long maxSeqIdFromFile =
968      WALSplitter.getMaxRegionSequenceId(fs.getFileSystem(), fs.getRegionDir());
969    long nextSeqId = Math.max(maxSeqId, maxSeqIdFromFile) + 1;
970    // The openSeqNum will always be increase even for read only region, as we rely on it to
971    // determine whether a region has been successfully reopend, so here we always need to update
972    // the max sequence id file.
973    if (RegionReplicaUtil.isDefaultReplica(getRegionInfo())) {
974      LOG.debug("writing seq id for {}", this.getRegionInfo().getEncodedName());
975      WALSplitter.writeRegionSequenceIdFile(fs.getFileSystem(), fs.getRegionDir(), nextSeqId);
976    }
977
978    LOG.info("Opened {}; next sequenceid={}", this.getRegionInfo().getShortNameToLog(), nextSeqId);
979
980    // A region can be reopened if failed a split; reset flags
981    this.closing.set(false);
982    this.closed.set(false);
983
984    if (coprocessorHost != null) {
985      status.setStatus("Running coprocessor post-open hooks");
986      coprocessorHost.postOpen();
987    }
988
989    status.markComplete("Region opened successfully");
990    return nextSeqId;
991  }
992
993  /**
994   * Open all Stores.
995   * @param reporter
996   * @param status
997   * @return Highest sequenceId found out in a Store.
998   * @throws IOException
999   */
1000  private long initializeStores(CancelableProgressable reporter, MonitoredTask status)
1001      throws IOException {
1002    // Load in all the HStores.
1003    long maxSeqId = -1;
1004    // initialized to -1 so that we pick up MemstoreTS from column families
1005    long maxMemstoreTS = -1;
1006
1007    if (htableDescriptor.getColumnFamilyCount() != 0) {
1008      // initialize the thread pool for opening stores in parallel.
1009      ThreadPoolExecutor storeOpenerThreadPool =
1010        getStoreOpenAndCloseThreadPool("StoreOpener-" + this.getRegionInfo().getShortNameToLog());
1011      CompletionService<HStore> completionService = new ExecutorCompletionService<>(storeOpenerThreadPool);
1012
1013      // initialize each store in parallel
1014      for (final ColumnFamilyDescriptor family : htableDescriptor.getColumnFamilies()) {
1015        status.setStatus("Instantiating store for column family " + family);
1016        completionService.submit(new Callable<HStore>() {
1017          @Override
1018          public HStore call() throws IOException {
1019            return instantiateHStore(family);
1020          }
1021        });
1022      }
1023      boolean allStoresOpened = false;
1024      boolean hasSloppyStores = false;
1025      try {
1026        for (int i = 0; i < htableDescriptor.getColumnFamilyCount(); i++) {
1027          Future<HStore> future = completionService.take();
1028          HStore store = future.get();
1029          this.stores.put(store.getColumnFamilyDescriptor().getName(), store);
1030          if (store.isSloppyMemStore()) {
1031            hasSloppyStores = true;
1032          }
1033
1034          long storeMaxSequenceId = store.getMaxSequenceId().orElse(0L);
1035          maxSeqIdInStores.put(Bytes.toBytes(store.getColumnFamilyName()),
1036              storeMaxSequenceId);
1037          if (maxSeqId == -1 || storeMaxSequenceId > maxSeqId) {
1038            maxSeqId = storeMaxSequenceId;
1039          }
1040          long maxStoreMemstoreTS = store.getMaxMemStoreTS().orElse(0L);
1041          if (maxStoreMemstoreTS > maxMemstoreTS) {
1042            maxMemstoreTS = maxStoreMemstoreTS;
1043          }
1044        }
1045        allStoresOpened = true;
1046        if(hasSloppyStores) {
1047          htableDescriptor = TableDescriptorBuilder.newBuilder(htableDescriptor)
1048                  .setFlushPolicyClassName(FlushNonSloppyStoresFirstPolicy.class.getName())
1049                  .build();
1050          LOG.info("Setting FlushNonSloppyStoresFirstPolicy for the region=" + this);
1051        }
1052      } catch (InterruptedException e) {
1053        throw (InterruptedIOException)new InterruptedIOException().initCause(e);
1054      } catch (ExecutionException e) {
1055        throw new IOException(e.getCause());
1056      } finally {
1057        storeOpenerThreadPool.shutdownNow();
1058        if (!allStoresOpened) {
1059          // something went wrong, close all opened stores
1060          LOG.error("Could not initialize all stores for the region=" + this);
1061          for (HStore store : this.stores.values()) {
1062            try {
1063              store.close();
1064            } catch (IOException e) {
1065              LOG.warn("close store failed", e);
1066            }
1067          }
1068        }
1069      }
1070    }
1071    return Math.max(maxSeqId, maxMemstoreTS + 1);
1072  }
1073
1074  private void initializeWarmup(final CancelableProgressable reporter) throws IOException {
1075    MonitoredTask status = TaskMonitor.get().createStatus("Initializing region " + this);
1076    // Initialize all the HStores
1077    status.setStatus("Warming up all the Stores");
1078    try {
1079      initializeStores(reporter, status);
1080    } finally {
1081      status.markComplete("Done warming up.");
1082    }
1083  }
1084
1085  /**
1086   * @return Map of StoreFiles by column family
1087   */
1088  private NavigableMap<byte[], List<Path>> getStoreFiles() {
1089    NavigableMap<byte[], List<Path>> allStoreFiles = new TreeMap<>(Bytes.BYTES_COMPARATOR);
1090    for (HStore store : stores.values()) {
1091      Collection<HStoreFile> storeFiles = store.getStorefiles();
1092      if (storeFiles == null) {
1093        continue;
1094      }
1095      List<Path> storeFileNames = new ArrayList<>();
1096      for (HStoreFile storeFile : storeFiles) {
1097        storeFileNames.add(storeFile.getPath());
1098      }
1099      allStoreFiles.put(store.getColumnFamilyDescriptor().getName(), storeFileNames);
1100    }
1101    return allStoreFiles;
1102  }
1103
1104  private void writeRegionOpenMarker(WAL wal, long openSeqId) throws IOException {
1105    Map<byte[], List<Path>> storeFiles = getStoreFiles();
1106    RegionEventDescriptor regionOpenDesc = ProtobufUtil.toRegionEventDescriptor(
1107      RegionEventDescriptor.EventType.REGION_OPEN, getRegionInfo(), openSeqId,
1108      getRegionServerServices().getServerName(), storeFiles);
1109    WALUtil.writeRegionEventMarker(wal, getReplicationScope(), getRegionInfo(), regionOpenDesc,
1110        mvcc);
1111  }
1112
1113  private void writeRegionCloseMarker(WAL wal) throws IOException {
1114    Map<byte[], List<Path>> storeFiles = getStoreFiles();
1115    RegionEventDescriptor regionEventDesc = ProtobufUtil.toRegionEventDescriptor(
1116      RegionEventDescriptor.EventType.REGION_CLOSE, getRegionInfo(), mvcc.getReadPoint(),
1117      getRegionServerServices().getServerName(), storeFiles);
1118    WALUtil.writeRegionEventMarker(wal, getReplicationScope(), getRegionInfo(), regionEventDesc,
1119        mvcc);
1120
1121    // Store SeqId in HDFS when a region closes
1122    // checking region folder exists is due to many tests which delete the table folder while a
1123    // table is still online
1124    if (this.fs.getFileSystem().exists(this.fs.getRegionDir())) {
1125      WALSplitter.writeRegionSequenceIdFile(this.fs.getFileSystem(), this.fs.getRegionDir(),
1126        mvcc.getReadPoint());
1127    }
1128  }
1129
1130  /**
1131   * @return True if this region has references.
1132   */
1133  public boolean hasReferences() {
1134    return stores.values().stream().anyMatch(HStore::hasReferences);
1135  }
1136
1137  public void blockUpdates() {
1138    this.updatesLock.writeLock().lock();
1139  }
1140
1141  public void unblockUpdates() {
1142    this.updatesLock.writeLock().unlock();
1143  }
1144
1145  public HDFSBlocksDistribution getHDFSBlocksDistribution() {
1146    HDFSBlocksDistribution hdfsBlocksDistribution = new HDFSBlocksDistribution();
1147    stores.values().stream().filter(s -> s.getStorefiles() != null)
1148        .flatMap(s -> s.getStorefiles().stream()).map(HStoreFile::getHDFSBlockDistribution)
1149        .forEachOrdered(hdfsBlocksDistribution::add);
1150    return hdfsBlocksDistribution;
1151  }
1152
1153  /**
1154   * This is a helper function to compute HDFS block distribution on demand
1155   * @param conf configuration
1156   * @param tableDescriptor TableDescriptor of the table
1157   * @param regionInfo encoded name of the region
1158   * @return The HDFS blocks distribution for the given region.
1159   * @throws IOException
1160   */
1161  public static HDFSBlocksDistribution computeHDFSBlocksDistribution(Configuration conf,
1162      TableDescriptor tableDescriptor, RegionInfo regionInfo) throws IOException {
1163    Path tablePath = FSUtils.getTableDir(FSUtils.getRootDir(conf), tableDescriptor.getTableName());
1164    return computeHDFSBlocksDistribution(conf, tableDescriptor, regionInfo, tablePath);
1165  }
1166
1167  /**
1168   * This is a helper function to compute HDFS block distribution on demand
1169   * @param conf configuration
1170   * @param tableDescriptor TableDescriptor of the table
1171   * @param regionInfo encoded name of the region
1172   * @param tablePath the table directory
1173   * @return The HDFS blocks distribution for the given region.
1174   * @throws IOException
1175   */
1176  public static HDFSBlocksDistribution computeHDFSBlocksDistribution(Configuration conf,
1177      TableDescriptor tableDescriptor, RegionInfo regionInfo, Path tablePath) throws IOException {
1178    HDFSBlocksDistribution hdfsBlocksDistribution = new HDFSBlocksDistribution();
1179    FileSystem fs = tablePath.getFileSystem(conf);
1180
1181    HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tablePath, regionInfo);
1182    for (ColumnFamilyDescriptor family : tableDescriptor.getColumnFamilies()) {
1183      List<LocatedFileStatus> locatedFileStatusList = HRegionFileSystem
1184          .getStoreFilesLocatedStatus(regionFs, family.getNameAsString(), true);
1185      if (locatedFileStatusList == null) {
1186        continue;
1187      }
1188
1189      for (LocatedFileStatus status : locatedFileStatusList) {
1190        Path p = status.getPath();
1191        if (StoreFileInfo.isReference(p) || HFileLink.isHFileLink(p)) {
1192          // Only construct StoreFileInfo object if its not a hfile, save obj
1193          // creation
1194          StoreFileInfo storeFileInfo = new StoreFileInfo(conf, fs, status);
1195          hdfsBlocksDistribution.add(storeFileInfo
1196              .computeHDFSBlocksDistribution(fs));
1197        } else if (StoreFileInfo.isHFile(p)) {
1198          // If its a HFile, then lets just add to the block distribution
1199          // lets not create more objects here, not even another HDFSBlocksDistribution
1200          FSUtils.addToHDFSBlocksDistribution(hdfsBlocksDistribution,
1201              status.getBlockLocations());
1202        } else {
1203          throw new IOException("path=" + p
1204              + " doesn't look like a valid StoreFile");
1205        }
1206      }
1207    }
1208    return hdfsBlocksDistribution;
1209  }
1210
1211  /**
1212   * Increase the size of mem store in this region and the size of global mem
1213   * store
1214   */
1215  void incMemStoreSize(MemStoreSize mss) {
1216    incMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize());
1217  }
1218
1219  void incMemStoreSize(long dataSizeDelta, long heapSizeDelta, long offHeapSizeDelta) {
1220    if (this.rsAccounting != null) {
1221      rsAccounting.incGlobalMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta);
1222    }
1223    long dataSize =
1224        this.memStoreSizing.incMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta);
1225    checkNegativeMemStoreDataSize(dataSize, dataSizeDelta);
1226  }
1227
1228  void decrMemStoreSize(MemStoreSize mss) {
1229    decrMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize());
1230  }
1231
1232  void decrMemStoreSize(long dataSizeDelta, long heapSizeDelta, long offHeapSizeDelta) {
1233    if (this.rsAccounting != null) {
1234      rsAccounting.decGlobalMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta);
1235    }
1236    long dataSize =
1237        this.memStoreSizing.decMemStoreSize(dataSizeDelta, heapSizeDelta, offHeapSizeDelta);
1238    checkNegativeMemStoreDataSize(dataSize, -dataSizeDelta);
1239  }
1240
1241  private void checkNegativeMemStoreDataSize(long memStoreDataSize, long delta) {
1242    // This is extremely bad if we make memStoreSizing negative. Log as much info on the offending
1243    // caller as possible. (memStoreSizing might be a negative value already -- freeing memory)
1244    if (memStoreDataSize < 0) {
1245      LOG.error("Asked to modify this region's (" + this.toString()
1246          + ") memStoreSizing to a negative value which is incorrect. Current memStoreSizing="
1247          + (memStoreDataSize - delta) + ", delta=" + delta, new Exception());
1248    }
1249  }
1250
1251  @Override
1252  public RegionInfo getRegionInfo() {
1253    return this.fs.getRegionInfo();
1254  }
1255
1256  /**
1257   * @return Instance of {@link RegionServerServices} used by this HRegion.
1258   * Can be null.
1259   */
1260  RegionServerServices getRegionServerServices() {
1261    return this.rsServices;
1262  }
1263
1264  @Override
1265  public long getReadRequestsCount() {
1266    return readRequestsCount.sum();
1267  }
1268
1269  @Override
1270  public long getFilteredReadRequestsCount() {
1271    return filteredReadRequestsCount.sum();
1272  }
1273
1274  @Override
1275  public long getWriteRequestsCount() {
1276    return writeRequestsCount.sum();
1277  }
1278
1279  @Override
1280  public long getMemStoreDataSize() {
1281    return memStoreSizing.getDataSize();
1282  }
1283
1284  @Override
1285  public long getMemStoreHeapSize() {
1286    return memStoreSizing.getHeapSize();
1287  }
1288
1289  @Override
1290  public long getMemStoreOffHeapSize() {
1291    return memStoreSizing.getOffHeapSize();
1292  }
1293
1294  /** @return store services for this region, to access services required by store level needs */
1295  public RegionServicesForStores getRegionServicesForStores() {
1296    return regionServicesForStores;
1297  }
1298
1299  @Override
1300  public long getNumMutationsWithoutWAL() {
1301    return numMutationsWithoutWAL.sum();
1302  }
1303
1304  @Override
1305  public long getDataInMemoryWithoutWAL() {
1306    return dataInMemoryWithoutWAL.sum();
1307  }
1308
1309  @Override
1310  public long getBlockedRequestsCount() {
1311    return blockedRequestsCount.sum();
1312  }
1313
1314  @Override
1315  public long getCheckAndMutateChecksPassed() {
1316    return checkAndMutateChecksPassed.sum();
1317  }
1318
1319  @Override
1320  public long getCheckAndMutateChecksFailed() {
1321    return checkAndMutateChecksFailed.sum();
1322  }
1323
1324  // TODO Needs to check whether we should expose our metrics system to CPs. If CPs themselves doing
1325  // the op and bypassing the core, this might be needed? Should be stop supporting the bypass
1326  // feature?
1327  public MetricsRegion getMetrics() {
1328    return metricsRegion;
1329  }
1330
1331  @Override
1332  public boolean isClosed() {
1333    return this.closed.get();
1334  }
1335
1336  @Override
1337  public boolean isClosing() {
1338    return this.closing.get();
1339  }
1340
1341  @Override
1342  public boolean isReadOnly() {
1343    return this.writestate.isReadOnly();
1344  }
1345
1346  @Override
1347  public boolean isAvailable() {
1348    return !isClosed() && !isClosing();
1349  }
1350
1351  @Override
1352  public boolean isSplittable() {
1353    return isAvailable() && !hasReferences();
1354  }
1355
1356  @Override
1357  public boolean isMergeable() {
1358    if (!isAvailable()) {
1359      LOG.debug("Region " + this
1360          + " is not mergeable because it is closing or closed");
1361      return false;
1362    }
1363    if (hasReferences()) {
1364      LOG.debug("Region " + this
1365          + " is not mergeable because it has references");
1366      return false;
1367    }
1368
1369    return true;
1370  }
1371
1372  public boolean areWritesEnabled() {
1373    synchronized(this.writestate) {
1374      return this.writestate.writesEnabled;
1375    }
1376  }
1377
1378  @VisibleForTesting
1379  public MultiVersionConcurrencyControl getMVCC() {
1380    return mvcc;
1381  }
1382
1383  @Override
1384  public long getMaxFlushedSeqId() {
1385    return maxFlushedSeqId;
1386  }
1387
1388  /**
1389   * @return readpoint considering given IsolationLevel. Pass {@code null} for default
1390   */
1391  public long getReadPoint(IsolationLevel isolationLevel) {
1392    if (isolationLevel != null && isolationLevel == IsolationLevel.READ_UNCOMMITTED) {
1393      // This scan can read even uncommitted transactions
1394      return Long.MAX_VALUE;
1395    }
1396    return mvcc.getReadPoint();
1397  }
1398
1399  public boolean isLoadingCfsOnDemandDefault() {
1400    return this.isLoadingCfsOnDemandDefault;
1401  }
1402
1403  /**
1404   * Close down this HRegion.  Flush the cache, shut down each HStore, don't
1405   * service any more calls.
1406   *
1407   * <p>This method could take some time to execute, so don't call it from a
1408   * time-sensitive thread.
1409   *
1410   * @return Vector of all the storage files that the HRegion's component
1411   * HStores make use of.  It's a list of all StoreFile objects. Returns empty
1412   * vector if already closed and null if judged that it should not close.
1413   *
1414   * @throws IOException e
1415   * @throws DroppedSnapshotException Thrown when replay of wal is required
1416   * because a Snapshot was not properly persisted. The region is put in closing mode, and the
1417   * caller MUST abort after this.
1418   */
1419  public Map<byte[], List<HStoreFile>> close() throws IOException {
1420    return close(false);
1421  }
1422
1423  private final Object closeLock = new Object();
1424
1425  /** Conf key for the periodic flush interval */
1426  public static final String MEMSTORE_PERIODIC_FLUSH_INTERVAL =
1427      "hbase.regionserver.optionalcacheflushinterval";
1428  /** Default interval for the memstore flush */
1429  public static final int DEFAULT_CACHE_FLUSH_INTERVAL = 3600000;
1430  /** Default interval for System tables memstore flush */
1431  public static final int SYSTEM_CACHE_FLUSH_INTERVAL = 300000; // 5 minutes
1432
1433  /** Conf key to force a flush if there are already enough changes for one region in memstore */
1434  public static final String MEMSTORE_FLUSH_PER_CHANGES =
1435      "hbase.regionserver.flush.per.changes";
1436  public static final long DEFAULT_FLUSH_PER_CHANGES = 30000000; // 30 millions
1437  /**
1438   * The following MAX_FLUSH_PER_CHANGES is large enough because each KeyValue has 20+ bytes
1439   * overhead. Therefore, even 1G empty KVs occupy at least 20GB memstore size for a single region
1440   */
1441  public static final long MAX_FLUSH_PER_CHANGES = 1000000000; // 1G
1442
1443  /**
1444   * Close down this HRegion.  Flush the cache unless abort parameter is true,
1445   * Shut down each HStore, don't service any more calls.
1446   *
1447   * This method could take some time to execute, so don't call it from a
1448   * time-sensitive thread.
1449   *
1450   * @param abort true if server is aborting (only during testing)
1451   * @return Vector of all the storage files that the HRegion's component
1452   * HStores make use of.  It's a list of StoreFile objects.  Can be null if
1453   * we are not to close at this time or we are already closed.
1454   *
1455   * @throws IOException e
1456   * @throws DroppedSnapshotException Thrown when replay of wal is required
1457   * because a Snapshot was not properly persisted. The region is put in closing mode, and the
1458   * caller MUST abort after this.
1459   */
1460  public Map<byte[], List<HStoreFile>> close(boolean abort) throws IOException {
1461    // Only allow one thread to close at a time. Serialize them so dual
1462    // threads attempting to close will run up against each other.
1463    MonitoredTask status = TaskMonitor.get().createStatus(
1464        "Closing region " + this.getRegionInfo().getEncodedName() +
1465        (abort ? " due to abort" : ""));
1466
1467    status.setStatus("Waiting for close lock");
1468    try {
1469      synchronized (closeLock) {
1470        return doClose(abort, status);
1471      }
1472    } finally {
1473      status.cleanup();
1474    }
1475  }
1476
1477  /**
1478   * Exposed for some very specific unit tests.
1479   */
1480  @VisibleForTesting
1481  public void setClosing(boolean closing) {
1482    this.closing.set(closing);
1483  }
1484
1485  /**
1486   * The {@link HRegion#doClose} will block forever if someone tries proving the dead lock via the unit test.
1487   * Instead of blocking, the {@link HRegion#doClose} will throw exception if you set the timeout.
1488   * @param timeoutForWriteLock the second time to wait for the write lock in {@link HRegion#doClose}
1489   */
1490  @VisibleForTesting
1491  public void setTimeoutForWriteLock(long timeoutForWriteLock) {
1492    assert timeoutForWriteLock >= 0;
1493    this.timeoutForWriteLock = timeoutForWriteLock;
1494  }
1495
1496  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UL_UNRELEASED_LOCK_EXCEPTION_PATH",
1497      justification="I think FindBugs is confused")
1498  private Map<byte[], List<HStoreFile>> doClose(boolean abort, MonitoredTask status)
1499      throws IOException {
1500    if (isClosed()) {
1501      LOG.warn("Region " + this + " already closed");
1502      return null;
1503    }
1504
1505    if (coprocessorHost != null) {
1506      status.setStatus("Running coprocessor pre-close hooks");
1507      this.coprocessorHost.preClose(abort);
1508    }
1509    status.setStatus("Disabling compacts and flushes for region");
1510    boolean canFlush = true;
1511    synchronized (writestate) {
1512      // Disable compacting and flushing by background threads for this
1513      // region.
1514      canFlush = !writestate.readOnly;
1515      writestate.writesEnabled = false;
1516      LOG.debug("Closing {}, disabling compactions & flushes",
1517          this.getRegionInfo().getEncodedName());
1518      waitForFlushesAndCompactions();
1519    }
1520    // If we were not just flushing, is it worth doing a preflush...one
1521    // that will clear out of the bulk of the memstore before we put up
1522    // the close flag?
1523    if (!abort && worthPreFlushing() && canFlush) {
1524      status.setStatus("Pre-flushing region before close");
1525      LOG.info("Running close preflush of {}", this.getRegionInfo().getEncodedName());
1526      try {
1527        internalFlushcache(status);
1528      } catch (IOException ioe) {
1529        // Failed to flush the region. Keep going.
1530        status.setStatus("Failed pre-flush " + this + "; " + ioe.getMessage());
1531      }
1532    }
1533
1534    if (timeoutForWriteLock == null
1535        || timeoutForWriteLock == Long.MAX_VALUE) {
1536      // block waiting for the lock for closing
1537      lock.writeLock().lock(); // FindBugs: Complains UL_UNRELEASED_LOCK_EXCEPTION_PATH but seems fine
1538    } else {
1539      try {
1540        boolean succeed = lock.writeLock().tryLock(timeoutForWriteLock, TimeUnit.SECONDS);
1541        if (!succeed) {
1542          throw new IOException("Failed to get write lock when closing region");
1543        }
1544      } catch (InterruptedException e) {
1545        throw (InterruptedIOException) new InterruptedIOException().initCause(e);
1546      }
1547    }
1548    this.closing.set(true);
1549    status.setStatus("Disabling writes for close");
1550    try {
1551      if (this.isClosed()) {
1552        status.abort("Already got closed by another process");
1553        // SplitTransaction handles the null
1554        return null;
1555      }
1556      LOG.debug("Updates disabled for region " + this);
1557      // Don't flush the cache if we are aborting
1558      if (!abort && canFlush) {
1559        int failedfFlushCount = 0;
1560        int flushCount = 0;
1561        long tmp = 0;
1562        long remainingSize = this.memStoreSizing.getDataSize();
1563        while (remainingSize > 0) {
1564          try {
1565            internalFlushcache(status);
1566            if(flushCount >0) {
1567              LOG.info("Running extra flush, " + flushCount +
1568                  " (carrying snapshot?) " + this);
1569            }
1570            flushCount++;
1571            tmp = this.memStoreSizing.getDataSize();
1572            if (tmp >= remainingSize) {
1573              failedfFlushCount++;
1574            }
1575            remainingSize = tmp;
1576            if (failedfFlushCount > 5) {
1577              // If we failed 5 times and are unable to clear memory, abort
1578              // so we do not lose data
1579              throw new DroppedSnapshotException("Failed clearing memory after " +
1580                  flushCount + " attempts on region: " +
1581                  Bytes.toStringBinary(getRegionInfo().getRegionName()));
1582            }
1583          } catch (IOException ioe) {
1584            status.setStatus("Failed flush " + this + ", putting online again");
1585            synchronized (writestate) {
1586              writestate.writesEnabled = true;
1587            }
1588            // Have to throw to upper layers.  I can't abort server from here.
1589            throw ioe;
1590          }
1591        }
1592      }
1593
1594      Map<byte[], List<HStoreFile>> result = new TreeMap<>(Bytes.BYTES_COMPARATOR);
1595      if (!stores.isEmpty()) {
1596        // initialize the thread pool for closing stores in parallel.
1597        ThreadPoolExecutor storeCloserThreadPool =
1598          getStoreOpenAndCloseThreadPool("StoreCloserThread-" +
1599            getRegionInfo().getRegionNameAsString());
1600        CompletionService<Pair<byte[], Collection<HStoreFile>>> completionService =
1601          new ExecutorCompletionService<>(storeCloserThreadPool);
1602
1603        // close each store in parallel
1604        for (HStore store : stores.values()) {
1605          MemStoreSize mss = store.getFlushableSize();
1606          if (!(abort || mss.getDataSize() == 0 || writestate.readOnly)) {
1607            if (getRegionServerServices() != null) {
1608              getRegionServerServices().abort("Assertion failed while closing store "
1609                + getRegionInfo().getRegionNameAsString() + " " + store
1610                + ". flushableSize expected=0, actual={" + mss
1611                + "}. Current memStoreSize=" + this.memStoreSizing.getMemStoreSize() +
1612                  ". Maybe a coprocessor "
1613                + "operation failed and left the memstore in a partially updated state.", null);
1614            }
1615          }
1616          completionService
1617              .submit(new Callable<Pair<byte[], Collection<HStoreFile>>>() {
1618                @Override
1619                public Pair<byte[], Collection<HStoreFile>> call() throws IOException {
1620                  return new Pair<>(store.getColumnFamilyDescriptor().getName(), store.close());
1621                }
1622              });
1623        }
1624        try {
1625          for (int i = 0; i < stores.size(); i++) {
1626            Future<Pair<byte[], Collection<HStoreFile>>> future = completionService.take();
1627            Pair<byte[], Collection<HStoreFile>> storeFiles = future.get();
1628            List<HStoreFile> familyFiles = result.get(storeFiles.getFirst());
1629            if (familyFiles == null) {
1630              familyFiles = new ArrayList<>();
1631              result.put(storeFiles.getFirst(), familyFiles);
1632            }
1633            familyFiles.addAll(storeFiles.getSecond());
1634          }
1635        } catch (InterruptedException e) {
1636          throw (InterruptedIOException)new InterruptedIOException().initCause(e);
1637        } catch (ExecutionException e) {
1638          Throwable cause = e.getCause();
1639          if (cause instanceof IOException) {
1640            throw (IOException) cause;
1641          }
1642          throw new IOException(cause);
1643        } finally {
1644          storeCloserThreadPool.shutdownNow();
1645        }
1646      }
1647
1648      status.setStatus("Writing region close event to WAL");
1649      // Always write close marker to wal even for read only table. This is not a big problem as we
1650      // do not write any data into the region.
1651      if (!abort && wal != null && getRegionServerServices() != null &&
1652        RegionReplicaUtil.isDefaultReplica(getRegionInfo())) {
1653        writeRegionCloseMarker(wal);
1654      }
1655
1656      this.closed.set(true);
1657      if (!canFlush) {
1658        decrMemStoreSize(this.memStoreSizing.getMemStoreSize());
1659      } else if (this.memStoreSizing.getDataSize() != 0) {
1660        LOG.error("Memstore data size is {}", this.memStoreSizing.getDataSize());
1661      }
1662      if (coprocessorHost != null) {
1663        status.setStatus("Running coprocessor post-close hooks");
1664        this.coprocessorHost.postClose(abort);
1665      }
1666      if (this.metricsRegion != null) {
1667        this.metricsRegion.close();
1668      }
1669      if (this.metricsRegionWrapper != null) {
1670        Closeables.close(this.metricsRegionWrapper, true);
1671      }
1672      status.markComplete("Closed");
1673      LOG.info("Closed " + this);
1674      return result;
1675    } finally {
1676      lock.writeLock().unlock();
1677    }
1678  }
1679
1680  /** Wait for all current flushes and compactions of the region to complete */
1681  // TODO HBASE-18906. Check the usage (if any) in Phoenix and expose this or give alternate way for
1682  // Phoenix needs.
1683  public void waitForFlushesAndCompactions() {
1684    synchronized (writestate) {
1685      if (this.writestate.readOnly) {
1686        // we should not wait for replayed flushed if we are read only (for example in case the
1687        // region is a secondary replica).
1688        return;
1689      }
1690      boolean interrupted = false;
1691      try {
1692        while (writestate.compacting.get() > 0 || writestate.flushing) {
1693          LOG.debug("waiting for " + writestate.compacting + " compactions"
1694            + (writestate.flushing ? " & cache flush" : "") + " to complete for region " + this);
1695          try {
1696            writestate.wait();
1697          } catch (InterruptedException iex) {
1698            // essentially ignore and propagate the interrupt back up
1699            LOG.warn("Interrupted while waiting");
1700            interrupted = true;
1701            break;
1702          }
1703        }
1704      } finally {
1705        if (interrupted) {
1706          Thread.currentThread().interrupt();
1707        }
1708      }
1709    }
1710  }
1711
1712  /**
1713   * Wait for all current flushes of the region to complete
1714   */
1715  public void waitForFlushes() {
1716    waitForFlushes(0);// Unbound wait
1717  }
1718
1719  @Override
1720  public boolean waitForFlushes(long timeout) {
1721    synchronized (writestate) {
1722      if (this.writestate.readOnly) {
1723        // we should not wait for replayed flushed if we are read only (for example in case the
1724        // region is a secondary replica).
1725        return true;
1726      }
1727      if (!writestate.flushing) return true;
1728      long start = System.currentTimeMillis();
1729      long duration = 0;
1730      boolean interrupted = false;
1731      LOG.debug("waiting for cache flush to complete for region " + this);
1732      try {
1733        while (writestate.flushing) {
1734          if (timeout > 0 && duration >= timeout) break;
1735          try {
1736            long toWait = timeout == 0 ? 0 : (timeout - duration);
1737            writestate.wait(toWait);
1738          } catch (InterruptedException iex) {
1739            // essentially ignore and propagate the interrupt back up
1740            LOG.warn("Interrupted while waiting");
1741            interrupted = true;
1742            break;
1743          } finally {
1744            duration = System.currentTimeMillis() - start;
1745          }
1746        }
1747      } finally {
1748        if (interrupted) {
1749          Thread.currentThread().interrupt();
1750        }
1751      }
1752      LOG.debug("Waited " + duration + " ms for flush to complete");
1753      return !(writestate.flushing);
1754    }
1755  }
1756
1757  protected ThreadPoolExecutor getStoreOpenAndCloseThreadPool(
1758      final String threadNamePrefix) {
1759    int numStores = Math.max(1, this.htableDescriptor.getColumnFamilyCount());
1760    int maxThreads = Math.min(numStores,
1761        conf.getInt(HConstants.HSTORE_OPEN_AND_CLOSE_THREADS_MAX,
1762            HConstants.DEFAULT_HSTORE_OPEN_AND_CLOSE_THREADS_MAX));
1763    return getOpenAndCloseThreadPool(maxThreads, threadNamePrefix);
1764  }
1765
1766  protected ThreadPoolExecutor getStoreFileOpenAndCloseThreadPool(
1767      final String threadNamePrefix) {
1768    int numStores = Math.max(1, this.htableDescriptor.getColumnFamilyCount());
1769    int maxThreads = Math.max(1,
1770        conf.getInt(HConstants.HSTORE_OPEN_AND_CLOSE_THREADS_MAX,
1771            HConstants.DEFAULT_HSTORE_OPEN_AND_CLOSE_THREADS_MAX)
1772            / numStores);
1773    return getOpenAndCloseThreadPool(maxThreads, threadNamePrefix);
1774  }
1775
1776  static ThreadPoolExecutor getOpenAndCloseThreadPool(int maxThreads,
1777      final String threadNamePrefix) {
1778    return Threads.getBoundedCachedThreadPool(maxThreads, 30L, TimeUnit.SECONDS,
1779      new ThreadFactory() {
1780        private int count = 1;
1781
1782        @Override
1783        public Thread newThread(Runnable r) {
1784          return new Thread(r, threadNamePrefix + "-" + count++);
1785        }
1786      });
1787  }
1788
1789   /**
1790    * @return True if its worth doing a flush before we put up the close flag.
1791    */
1792  private boolean worthPreFlushing() {
1793    return this.memStoreSizing.getDataSize() >
1794      this.conf.getLong("hbase.hregion.preclose.flush.size", 1024 * 1024 * 5);
1795  }
1796
1797  //////////////////////////////////////////////////////////////////////////////
1798  // HRegion accessors
1799  //////////////////////////////////////////////////////////////////////////////
1800
1801  @Override
1802  public TableDescriptor getTableDescriptor() {
1803    return this.htableDescriptor;
1804  }
1805
1806  @VisibleForTesting
1807  void setTableDescriptor(TableDescriptor desc) {
1808    htableDescriptor = desc;
1809  }
1810
1811  /** @return WAL in use for this region */
1812  public WAL getWAL() {
1813    return this.wal;
1814  }
1815
1816  /**
1817   * @return split policy for this region.
1818   */
1819  public RegionSplitPolicy getSplitPolicy() {
1820    return this.splitPolicy;
1821  }
1822
1823  /**
1824   * A split takes the config from the parent region & passes it to the daughter
1825   * region's constructor. If 'conf' was passed, you would end up using the HTD
1826   * of the parent region in addition to the new daughter HTD. Pass 'baseConf'
1827   * to the daughter regions to avoid this tricky dedupe problem.
1828   * @return Configuration object
1829   */
1830  Configuration getBaseConf() {
1831    return this.baseConf;
1832  }
1833
1834  /** @return {@link FileSystem} being used by this region */
1835  public FileSystem getFilesystem() {
1836    return fs.getFileSystem();
1837  }
1838
1839  /** @return the {@link HRegionFileSystem} used by this region */
1840  public HRegionFileSystem getRegionFileSystem() {
1841    return this.fs;
1842  }
1843
1844  @Override
1845  public long getEarliestFlushTimeForAllStores() {
1846    return Collections.min(lastStoreFlushTimeMap.values());
1847  }
1848
1849  @Override
1850  public long getOldestHfileTs(boolean majorCompactionOnly) throws IOException {
1851    long result = Long.MAX_VALUE;
1852    for (HStore store : stores.values()) {
1853      Collection<HStoreFile> storeFiles = store.getStorefiles();
1854      if (storeFiles == null) {
1855        continue;
1856      }
1857      for (HStoreFile file : storeFiles) {
1858        StoreFileReader sfReader = file.getReader();
1859        if (sfReader == null) {
1860          continue;
1861        }
1862        HFile.Reader reader = sfReader.getHFileReader();
1863        if (reader == null) {
1864          continue;
1865        }
1866        if (majorCompactionOnly) {
1867          byte[] val = reader.loadFileInfo().get(MAJOR_COMPACTION_KEY);
1868          if (val == null || !Bytes.toBoolean(val)) {
1869            continue;
1870          }
1871        }
1872        result = Math.min(result, reader.getFileContext().getFileCreateTime());
1873      }
1874    }
1875    return result == Long.MAX_VALUE ? 0 : result;
1876  }
1877
1878  RegionLoad.Builder setCompleteSequenceId(RegionLoad.Builder regionLoadBldr) {
1879    long lastFlushOpSeqIdLocal = this.lastFlushOpSeqId;
1880    byte[] encodedRegionName = this.getRegionInfo().getEncodedNameAsBytes();
1881    regionLoadBldr.clearStoreCompleteSequenceId();
1882    for (byte[] familyName : this.stores.keySet()) {
1883      long earliest = this.wal.getEarliestMemStoreSeqNum(encodedRegionName, familyName);
1884      // Subtract - 1 to go earlier than the current oldest, unflushed edit in memstore; this will
1885      // give us a sequence id that is for sure flushed. We want edit replay to start after this
1886      // sequence id in this region. If NO_SEQNUM, use the regions maximum flush id.
1887      long csid = (earliest == HConstants.NO_SEQNUM)? lastFlushOpSeqIdLocal: earliest - 1;
1888      regionLoadBldr.addStoreCompleteSequenceId(StoreSequenceId.newBuilder()
1889          .setFamilyName(UnsafeByteOperations.unsafeWrap(familyName)).setSequenceId(csid).build());
1890    }
1891    return regionLoadBldr.setCompleteSequenceId(getMaxFlushedSeqId());
1892  }
1893
1894  //////////////////////////////////////////////////////////////////////////////
1895  // HRegion maintenance.
1896  //
1897  // These methods are meant to be called periodically by the HRegionServer for
1898  // upkeep.
1899  //////////////////////////////////////////////////////////////////////////////
1900  /**
1901   * Do preparation for pending compaction.
1902   * @throws IOException
1903   */
1904  protected void doRegionCompactionPrep() throws IOException {
1905  }
1906
1907  /**
1908   * Synchronously compact all stores in the region.
1909   * <p>This operation could block for a long time, so don't call it from a
1910   * time-sensitive thread.
1911   * <p>Note that no locks are taken to prevent possible conflicts between
1912   * compaction and splitting activities. The regionserver does not normally compact
1913   * and split in parallel. However by calling this method you may introduce
1914   * unexpected and unhandled concurrency. Don't do this unless you know what
1915   * you are doing.
1916   *
1917   * @param majorCompaction True to force a major compaction regardless of thresholds
1918   * @throws IOException
1919   */
1920  public void compact(boolean majorCompaction) throws IOException {
1921    if (majorCompaction) {
1922      stores.values().forEach(HStore::triggerMajorCompaction);
1923    }
1924    for (HStore s : stores.values()) {
1925      Optional<CompactionContext> compaction = s.requestCompaction();
1926      if (compaction.isPresent()) {
1927        ThroughputController controller = null;
1928        if (rsServices != null) {
1929          controller = CompactionThroughputControllerFactory.create(rsServices, conf);
1930        }
1931        if (controller == null) {
1932          controller = NoLimitThroughputController.INSTANCE;
1933        }
1934        compact(compaction.get(), s, controller, null);
1935      }
1936    }
1937  }
1938
1939  /**
1940   * This is a helper function that compact all the stores synchronously.
1941   * <p>
1942   * It is used by utilities and testing
1943   */
1944  @VisibleForTesting
1945  public void compactStores() throws IOException {
1946    for (HStore s : stores.values()) {
1947      Optional<CompactionContext> compaction = s.requestCompaction();
1948      if (compaction.isPresent()) {
1949        compact(compaction.get(), s, NoLimitThroughputController.INSTANCE, null);
1950      }
1951    }
1952  }
1953
1954  /**
1955   * This is a helper function that compact the given store.
1956   * <p>
1957   * It is used by utilities and testing
1958   */
1959  @VisibleForTesting
1960  void compactStore(byte[] family, ThroughputController throughputController) throws IOException {
1961    HStore s = getStore(family);
1962    Optional<CompactionContext> compaction = s.requestCompaction();
1963    if (compaction.isPresent()) {
1964      compact(compaction.get(), s, throughputController, null);
1965    }
1966  }
1967
1968  /**
1969   * Called by compaction thread and after region is opened to compact the
1970   * HStores if necessary.
1971   *
1972   * <p>This operation could block for a long time, so don't call it from a
1973   * time-sensitive thread.
1974   *
1975   * Note that no locking is necessary at this level because compaction only
1976   * conflicts with a region split, and that cannot happen because the region
1977   * server does them sequentially and not in parallel.
1978   *
1979   * @param compaction Compaction details, obtained by requestCompaction()
1980   * @param throughputController
1981   * @return whether the compaction completed
1982   */
1983  public boolean compact(CompactionContext compaction, HStore store,
1984      ThroughputController throughputController) throws IOException {
1985    return compact(compaction, store, throughputController, null);
1986  }
1987
1988  public boolean compact(CompactionContext compaction, HStore store,
1989      ThroughputController throughputController, User user) throws IOException {
1990    assert compaction != null && compaction.hasSelection();
1991    assert !compaction.getRequest().getFiles().isEmpty();
1992    if (this.closing.get() || this.closed.get()) {
1993      LOG.debug("Skipping compaction on " + this + " because closing/closed");
1994      store.cancelRequestedCompaction(compaction);
1995      return false;
1996    }
1997    MonitoredTask status = null;
1998    boolean requestNeedsCancellation = true;
1999    /*
2000     * We are trying to remove / relax the region read lock for compaction.
2001     * Let's see what are the potential race conditions among the operations (user scan,
2002     * region split, region close and region bulk load).
2003     *
2004     *  user scan ---> region read lock
2005     *  region split --> region close first --> region write lock
2006     *  region close --> region write lock
2007     *  region bulk load --> region write lock
2008     *
2009     * read lock is compatible with read lock. ---> no problem with user scan/read
2010     * region bulk load does not cause problem for compaction (no consistency problem, store lock
2011     *  will help the store file accounting).
2012     * They can run almost concurrently at the region level.
2013     *
2014     * The only remaining race condition is between the region close and compaction.
2015     * So we will evaluate, below, how region close intervenes with compaction if compaction does
2016     * not acquire region read lock.
2017     *
2018     * Here are the steps for compaction:
2019     * 1. obtain list of StoreFile's
2020     * 2. create StoreFileScanner's based on list from #1
2021     * 3. perform compaction and save resulting files under tmp dir
2022     * 4. swap in compacted files
2023     *
2024     * #1 is guarded by store lock. This patch does not change this --> no worse or better
2025     * For #2, we obtain smallest read point (for region) across all the Scanners (for both default
2026     * compactor and stripe compactor).
2027     * The read points are for user scans. Region keeps the read points for all currently open
2028     * user scanners.
2029     * Compaction needs to know the smallest read point so that during re-write of the hfiles,
2030     * it can remove the mvcc points for the cells if their mvccs are older than the smallest
2031     * since they are not needed anymore.
2032     * This will not conflict with compaction.
2033     * For #3, it can be performed in parallel to other operations.
2034     * For #4 bulk load and compaction don't conflict with each other on the region level
2035     *   (for multi-family atomicy).
2036     * Region close and compaction are guarded pretty well by the 'writestate'.
2037     * In HRegion#doClose(), we have :
2038     * synchronized (writestate) {
2039     *   // Disable compacting and flushing by background threads for this
2040     *   // region.
2041     *   canFlush = !writestate.readOnly;
2042     *   writestate.writesEnabled = false;
2043     *   LOG.debug("Closing " + this + ": disabling compactions & flushes");
2044     *   waitForFlushesAndCompactions();
2045     * }
2046     * waitForFlushesAndCompactions() would wait for writestate.compacting to come down to 0.
2047     * and in HRegion.compact()
2048     *  try {
2049     *    synchronized (writestate) {
2050     *    if (writestate.writesEnabled) {
2051     *      wasStateSet = true;
2052     *      ++writestate.compacting;
2053     *    } else {
2054     *      String msg = "NOT compacting region " + this + ". Writes disabled.";
2055     *      LOG.info(msg);
2056     *      status.abort(msg);
2057     *      return false;
2058     *    }
2059     *  }
2060     * Also in compactor.performCompaction():
2061     * check periodically to see if a system stop is requested
2062     * if (closeCheckInterval > 0) {
2063     *   bytesWritten += len;
2064     *   if (bytesWritten > closeCheckInterval) {
2065     *     bytesWritten = 0;
2066     *     if (!store.areWritesEnabled()) {
2067     *       progress.cancel();
2068     *       return false;
2069     *     }
2070     *   }
2071     * }
2072     */
2073    try {
2074      byte[] cf = Bytes.toBytes(store.getColumnFamilyName());
2075      if (stores.get(cf) != store) {
2076        LOG.warn("Store " + store.getColumnFamilyName() + " on region " + this
2077            + " has been re-instantiated, cancel this compaction request. "
2078            + " It may be caused by the roll back of split transaction");
2079        return false;
2080      }
2081
2082      status = TaskMonitor.get().createStatus("Compacting " + store + " in " + this);
2083      status.enableStatusJournal(false);
2084      if (this.closed.get()) {
2085        String msg = "Skipping compaction on " + this + " because closed";
2086        LOG.debug(msg);
2087        status.abort(msg);
2088        return false;
2089      }
2090      boolean wasStateSet = false;
2091      try {
2092        synchronized (writestate) {
2093          if (writestate.writesEnabled) {
2094            wasStateSet = true;
2095            writestate.compacting.incrementAndGet();
2096          } else {
2097            String msg = "NOT compacting region " + this + ". Writes disabled.";
2098            LOG.info(msg);
2099            status.abort(msg);
2100            return false;
2101          }
2102        }
2103        LOG.info("Starting compaction of {} in {}{}", store, this,
2104            (compaction.getRequest().isOffPeak()?" as an off-peak compaction":""));
2105        doRegionCompactionPrep();
2106        try {
2107          status.setStatus("Compacting store " + store);
2108          // We no longer need to cancel the request on the way out of this
2109          // method because Store#compact will clean up unconditionally
2110          requestNeedsCancellation = false;
2111          store.compact(compaction, throughputController, user);
2112        } catch (InterruptedIOException iioe) {
2113          String msg = "compaction interrupted";
2114          LOG.info(msg, iioe);
2115          status.abort(msg);
2116          return false;
2117        }
2118      } finally {
2119        if (wasStateSet) {
2120          synchronized (writestate) {
2121            writestate.compacting.decrementAndGet();
2122            if (writestate.compacting.get() <= 0) {
2123              writestate.notifyAll();
2124            }
2125          }
2126        }
2127      }
2128      status.markComplete("Compaction complete");
2129      return true;
2130    } finally {
2131      if (requestNeedsCancellation) store.cancelRequestedCompaction(compaction);
2132      if (status != null) {
2133        LOG.debug("Compaction status journal:\n\t" + status.prettyPrintJournal());
2134        status.cleanup();
2135      }
2136    }
2137  }
2138
2139  /**
2140   * Flush the cache.
2141   *
2142   * <p>When this method is called the cache will be flushed unless:
2143   * <ol>
2144   *   <li>the cache is empty</li>
2145   *   <li>the region is closed.</li>
2146   *   <li>a flush is already in progress</li>
2147   *   <li>writes are disabled</li>
2148   * </ol>
2149   *
2150   * <p>This method may block for some time, so it should not be called from a
2151   * time-sensitive thread.
2152   * @param force whether we want to force a flush of all stores
2153   * @return FlushResult indicating whether the flush was successful or not and if
2154   * the region needs compacting
2155   *
2156   * @throws IOException general io exceptions
2157   * because a snapshot was not properly persisted.
2158   */
2159  // TODO HBASE-18905. We might have to expose a requestFlush API for CPs
2160  public FlushResult flush(boolean force) throws IOException {
2161    return flushcache(force, false, FlushLifeCycleTracker.DUMMY);
2162  }
2163
2164  public interface FlushResult {
2165    enum Result {
2166      FLUSHED_NO_COMPACTION_NEEDED,
2167      FLUSHED_COMPACTION_NEEDED,
2168      // Special case where a flush didn't run because there's nothing in the memstores. Used when
2169      // bulk loading to know when we can still load even if a flush didn't happen.
2170      CANNOT_FLUSH_MEMSTORE_EMPTY,
2171      CANNOT_FLUSH
2172    }
2173
2174    /** @return the detailed result code */
2175    Result getResult();
2176
2177    /** @return true if the memstores were flushed, else false */
2178    boolean isFlushSucceeded();
2179
2180    /** @return True if the flush requested a compaction, else false */
2181    boolean isCompactionNeeded();
2182  }
2183
2184  /**
2185   * Flush the cache.
2186   *
2187   * When this method is called the cache will be flushed unless:
2188   * <ol>
2189   *   <li>the cache is empty</li>
2190   *   <li>the region is closed.</li>
2191   *   <li>a flush is already in progress</li>
2192   *   <li>writes are disabled</li>
2193   * </ol>
2194   *
2195   * <p>This method may block for some time, so it should not be called from a
2196   * time-sensitive thread.
2197   * @param forceFlushAllStores whether we want to flush all stores
2198   * @param writeFlushRequestWalMarker whether to write the flush request marker to WAL
2199   * @param tracker used to track the life cycle of this flush
2200   * @return whether the flush is success and whether the region needs compacting
2201   *
2202   * @throws IOException general io exceptions
2203   * @throws DroppedSnapshotException Thrown when replay of wal is required
2204   * because a Snapshot was not properly persisted. The region is put in closing mode, and the
2205   * caller MUST abort after this.
2206   */
2207  public FlushResultImpl flushcache(boolean forceFlushAllStores, boolean writeFlushRequestWalMarker,
2208      FlushLifeCycleTracker tracker) throws IOException {
2209    // fail-fast instead of waiting on the lock
2210    if (this.closing.get()) {
2211      String msg = "Skipping flush on " + this + " because closing";
2212      LOG.debug(msg);
2213      return new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false);
2214    }
2215    MonitoredTask status = TaskMonitor.get().createStatus("Flushing " + this);
2216    status.enableStatusJournal(false);
2217    status.setStatus("Acquiring readlock on region");
2218    // block waiting for the lock for flushing cache
2219    lock.readLock().lock();
2220    try {
2221      if (this.closed.get()) {
2222        String msg = "Skipping flush on " + this + " because closed";
2223        LOG.debug(msg);
2224        status.abort(msg);
2225        return new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false);
2226      }
2227      if (coprocessorHost != null) {
2228        status.setStatus("Running coprocessor pre-flush hooks");
2229        coprocessorHost.preFlush(tracker);
2230      }
2231      // TODO: this should be managed within memstore with the snapshot, updated only after flush
2232      // successful
2233      if (numMutationsWithoutWAL.sum() > 0) {
2234        numMutationsWithoutWAL.reset();
2235        dataInMemoryWithoutWAL.reset();
2236      }
2237      synchronized (writestate) {
2238        if (!writestate.flushing && writestate.writesEnabled) {
2239          this.writestate.flushing = true;
2240        } else {
2241          if (LOG.isDebugEnabled()) {
2242            LOG.debug("NOT flushing memstore for region " + this
2243                + ", flushing=" + writestate.flushing + ", writesEnabled="
2244                + writestate.writesEnabled);
2245          }
2246          String msg = "Not flushing since "
2247              + (writestate.flushing ? "already flushing"
2248              : "writes not enabled");
2249          status.abort(msg);
2250          return new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false);
2251        }
2252      }
2253
2254      try {
2255        Collection<HStore> specificStoresToFlush =
2256            forceFlushAllStores ? stores.values() : flushPolicy.selectStoresToFlush();
2257        FlushResultImpl fs =
2258            internalFlushcache(specificStoresToFlush, status, writeFlushRequestWalMarker, tracker);
2259
2260        if (coprocessorHost != null) {
2261          status.setStatus("Running post-flush coprocessor hooks");
2262          coprocessorHost.postFlush(tracker);
2263        }
2264
2265        if(fs.isFlushSucceeded()) {
2266          flushesQueued.reset();
2267        }
2268
2269        status.markComplete("Flush successful");
2270        return fs;
2271      } finally {
2272        synchronized (writestate) {
2273          writestate.flushing = false;
2274          this.writestate.flushRequested = false;
2275          writestate.notifyAll();
2276        }
2277      }
2278    } finally {
2279      lock.readLock().unlock();
2280      LOG.debug("Flush status journal:\n\t" + status.prettyPrintJournal());
2281      status.cleanup();
2282    }
2283  }
2284
2285  /**
2286   * Should the store be flushed because it is old enough.
2287   * <p>
2288   * Every FlushPolicy should call this to determine whether a store is old enough to flush (except
2289   * that you always flush all stores). Otherwise the method will always
2290   * returns true which will make a lot of flush requests.
2291   */
2292  boolean shouldFlushStore(HStore store) {
2293    long earliest = this.wal.getEarliestMemStoreSeqNum(getRegionInfo().getEncodedNameAsBytes(),
2294      store.getColumnFamilyDescriptor().getName()) - 1;
2295    if (earliest > 0 && earliest + flushPerChanges < mvcc.getReadPoint()) {
2296      if (LOG.isDebugEnabled()) {
2297        LOG.debug("Flush column family " + store.getColumnFamilyName() + " of " +
2298          getRegionInfo().getEncodedName() + " because unflushed sequenceid=" + earliest +
2299          " is > " + this.flushPerChanges + " from current=" + mvcc.getReadPoint());
2300      }
2301      return true;
2302    }
2303    if (this.flushCheckInterval <= 0) {
2304      return false;
2305    }
2306    long now = EnvironmentEdgeManager.currentTime();
2307    if (store.timeOfOldestEdit() < now - this.flushCheckInterval) {
2308      if (LOG.isDebugEnabled()) {
2309        LOG.debug("Flush column family: " + store.getColumnFamilyName() + " of " +
2310          getRegionInfo().getEncodedName() + " because time of oldest edit=" +
2311            store.timeOfOldestEdit() + " is > " + this.flushCheckInterval + " from now =" + now);
2312      }
2313      return true;
2314    }
2315    return false;
2316  }
2317
2318  /**
2319   * Should the memstore be flushed now
2320   */
2321  boolean shouldFlush(final StringBuilder whyFlush) {
2322    whyFlush.setLength(0);
2323    // This is a rough measure.
2324    if (this.maxFlushedSeqId > 0
2325          && (this.maxFlushedSeqId + this.flushPerChanges < this.mvcc.getReadPoint())) {
2326      whyFlush.append("more than max edits, " + this.flushPerChanges + ", since last flush");
2327      return true;
2328    }
2329    long modifiedFlushCheckInterval = flushCheckInterval;
2330    if (getRegionInfo().getTable().isSystemTable() &&
2331        getRegionInfo().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID) {
2332      modifiedFlushCheckInterval = SYSTEM_CACHE_FLUSH_INTERVAL;
2333    }
2334    if (modifiedFlushCheckInterval <= 0) { //disabled
2335      return false;
2336    }
2337    long now = EnvironmentEdgeManager.currentTime();
2338    //if we flushed in the recent past, we don't need to do again now
2339    if ((now - getEarliestFlushTimeForAllStores() < modifiedFlushCheckInterval)) {
2340      return false;
2341    }
2342    //since we didn't flush in the recent past, flush now if certain conditions
2343    //are met. Return true on first such memstore hit.
2344    for (HStore s : stores.values()) {
2345      if (s.timeOfOldestEdit() < now - modifiedFlushCheckInterval) {
2346        // we have an old enough edit in the memstore, flush
2347        whyFlush.append(s.toString() + " has an old edit so flush to free WALs");
2348        return true;
2349      }
2350    }
2351    return false;
2352  }
2353
2354  /**
2355   * Flushing all stores.
2356   * @see #internalFlushcache(Collection, MonitoredTask, boolean, FlushLifeCycleTracker)
2357   */
2358  private FlushResult internalFlushcache(MonitoredTask status) throws IOException {
2359    return internalFlushcache(stores.values(), status, false, FlushLifeCycleTracker.DUMMY);
2360  }
2361
2362  /**
2363   * Flushing given stores.
2364   * @see #internalFlushcache(WAL, long, Collection, MonitoredTask, boolean, FlushLifeCycleTracker)
2365   */
2366  private FlushResultImpl internalFlushcache(Collection<HStore> storesToFlush, MonitoredTask status,
2367      boolean writeFlushWalMarker, FlushLifeCycleTracker tracker) throws IOException {
2368    return internalFlushcache(this.wal, HConstants.NO_SEQNUM, storesToFlush, status,
2369      writeFlushWalMarker, tracker);
2370  }
2371
2372  /**
2373   * Flush the memstore. Flushing the memstore is a little tricky. We have a lot of updates in the
2374   * memstore, all of which have also been written to the wal. We need to write those updates in the
2375   * memstore out to disk, while being able to process reads/writes as much as possible during the
2376   * flush operation.
2377   * <p>
2378   * This method may block for some time. Every time you call it, we up the regions sequence id even
2379   * if we don't flush; i.e. the returned region id will be at least one larger than the last edit
2380   * applied to this region. The returned id does not refer to an actual edit. The returned id can
2381   * be used for say installing a bulk loaded file just ahead of the last hfile that was the result
2382   * of this flush, etc.
2383   * @param wal Null if we're NOT to go via wal.
2384   * @param myseqid The seqid to use if <code>wal</code> is null writing out flush file.
2385   * @param storesToFlush The list of stores to flush.
2386   * @return object describing the flush's state
2387   * @throws IOException general io exceptions
2388   * @throws DroppedSnapshotException Thrown when replay of WAL is required.
2389   */
2390  protected FlushResultImpl internalFlushcache(WAL wal, long myseqid,
2391      Collection<HStore> storesToFlush, MonitoredTask status, boolean writeFlushWalMarker,
2392      FlushLifeCycleTracker tracker) throws IOException {
2393    PrepareFlushResult result =
2394        internalPrepareFlushCache(wal, myseqid, storesToFlush, status, writeFlushWalMarker, tracker);
2395    if (result.result == null) {
2396      return internalFlushCacheAndCommit(wal, status, result, storesToFlush);
2397    } else {
2398      return result.result; // early exit due to failure from prepare stage
2399    }
2400  }
2401
2402  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="DLS_DEAD_LOCAL_STORE",
2403      justification="FindBugs seems confused about trxId")
2404  protected PrepareFlushResult internalPrepareFlushCache(WAL wal, long myseqid,
2405      Collection<HStore> storesToFlush, MonitoredTask status, boolean writeFlushWalMarker,
2406      FlushLifeCycleTracker tracker) throws IOException {
2407    if (this.rsServices != null && this.rsServices.isAborted()) {
2408      // Don't flush when server aborting, it's unsafe
2409      throw new IOException("Aborting flush because server is aborted...");
2410    }
2411    final long startTime = EnvironmentEdgeManager.currentTime();
2412    // If nothing to flush, return, but return with a valid unused sequenceId.
2413    // Its needed by bulk upload IIRC. It flushes until no edits in memory so it can insert a
2414    // bulk loaded file between memory and existing hfiles. It wants a good seqeunceId that belongs
2415    // to no other that it can use to associate with the bulk load. Hence this little dance below
2416    // to go get one.
2417    if (this.memStoreSizing.getDataSize() <= 0) {
2418      // Take an update lock so no edits can come into memory just yet.
2419      this.updatesLock.writeLock().lock();
2420      WriteEntry writeEntry = null;
2421      try {
2422        if (this.memStoreSizing.getDataSize() <= 0) {
2423          // Presume that if there are still no edits in the memstore, then there are no edits for
2424          // this region out in the WAL subsystem so no need to do any trickery clearing out
2425          // edits in the WAL sub-system. Up the sequence number so the resulting flush id is for
2426          // sure just beyond the last appended region edit and not associated with any edit
2427          // (useful as marker when bulk loading, etc.).
2428          if (wal != null) {
2429            writeEntry = mvcc.begin();
2430            long flushOpSeqId = writeEntry.getWriteNumber();
2431            FlushResultImpl flushResult =
2432                new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, flushOpSeqId,
2433                    "Nothing to flush", writeFlushRequestMarkerToWAL(wal, writeFlushWalMarker));
2434            mvcc.completeAndWait(writeEntry);
2435            // Set to null so we don't complete it again down in finally block.
2436            writeEntry = null;
2437            return new PrepareFlushResult(flushResult, myseqid);
2438          } else {
2439            return new PrepareFlushResult(new FlushResultImpl(
2440              FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, "Nothing to flush", false), myseqid);
2441          }
2442        }
2443      } finally {
2444        if (writeEntry != null) {
2445          // If writeEntry is non-null, this operation failed; the mvcc transaction failed...
2446          // but complete it anyways so it doesn't block the mvcc queue.
2447          mvcc.complete(writeEntry);
2448        }
2449        this.updatesLock.writeLock().unlock();
2450      }
2451    }
2452    logFatLineOnFlush(storesToFlush, myseqid);
2453    // Stop updates while we snapshot the memstore of all of these regions' stores. We only have
2454    // to do this for a moment.  It is quick. We also set the memstore size to zero here before we
2455    // allow updates again so its value will represent the size of the updates received
2456    // during flush
2457
2458    // We have to take an update lock during snapshot, or else a write could end up in both snapshot
2459    // and memstore (makes it difficult to do atomic rows then)
2460    status.setStatus("Obtaining lock to block concurrent updates");
2461    // block waiting for the lock for internal flush
2462    this.updatesLock.writeLock().lock();
2463    status.setStatus("Preparing flush snapshotting stores in " + getRegionInfo().getEncodedName());
2464    MemStoreSizing totalSizeOfFlushableStores = new NonThreadSafeMemStoreSizing();
2465
2466    Map<byte[], Long> flushedFamilyNamesToSeq = new HashMap<>();
2467    for (HStore store : storesToFlush) {
2468      flushedFamilyNamesToSeq.put(store.getColumnFamilyDescriptor().getName(),
2469        store.preFlushSeqIDEstimation());
2470    }
2471
2472    TreeMap<byte[], StoreFlushContext> storeFlushCtxs = new TreeMap<>(Bytes.BYTES_COMPARATOR);
2473    TreeMap<byte[], List<Path>> committedFiles = new TreeMap<>(Bytes.BYTES_COMPARATOR);
2474    TreeMap<byte[], MemStoreSize> storeFlushableSize = new TreeMap<>(Bytes.BYTES_COMPARATOR);
2475    // The sequence id of this flush operation which is used to log FlushMarker and pass to
2476    // createFlushContext to use as the store file's sequence id. It can be in advance of edits
2477    // still in the memstore, edits that are in other column families yet to be flushed.
2478    long flushOpSeqId = HConstants.NO_SEQNUM;
2479    // The max flushed sequence id after this flush operation completes. All edits in memstore
2480    // will be in advance of this sequence id.
2481    long flushedSeqId = HConstants.NO_SEQNUM;
2482    byte[] encodedRegionName = getRegionInfo().getEncodedNameAsBytes();
2483    try {
2484      if (wal != null) {
2485        Long earliestUnflushedSequenceIdForTheRegion =
2486            wal.startCacheFlush(encodedRegionName, flushedFamilyNamesToSeq);
2487        if (earliestUnflushedSequenceIdForTheRegion == null) {
2488          // This should never happen. This is how startCacheFlush signals flush cannot proceed.
2489          String msg = this.getRegionInfo().getEncodedName() + " flush aborted; WAL closing.";
2490          status.setStatus(msg);
2491          return new PrepareFlushResult(
2492              new FlushResultImpl(FlushResult.Result.CANNOT_FLUSH, msg, false),
2493              myseqid);
2494        }
2495        flushOpSeqId = getNextSequenceId(wal);
2496        // Back up 1, minus 1 from oldest sequence id in memstore to get last 'flushed' edit
2497        flushedSeqId =
2498            earliestUnflushedSequenceIdForTheRegion.longValue() == HConstants.NO_SEQNUM?
2499                flushOpSeqId: earliestUnflushedSequenceIdForTheRegion.longValue() - 1;
2500      } else {
2501        // use the provided sequence Id as WAL is not being used for this flush.
2502        flushedSeqId = flushOpSeqId = myseqid;
2503      }
2504
2505      for (HStore s : storesToFlush) {
2506        storeFlushCtxs.put(s.getColumnFamilyDescriptor().getName(),
2507          s.createFlushContext(flushOpSeqId, tracker));
2508        // for writing stores to WAL
2509        committedFiles.put(s.getColumnFamilyDescriptor().getName(), null);
2510      }
2511
2512      // write the snapshot start to WAL
2513      if (wal != null && !writestate.readOnly) {
2514        FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.START_FLUSH,
2515            getRegionInfo(), flushOpSeqId, committedFiles);
2516        // No sync. Sync is below where no updates lock and we do FlushAction.COMMIT_FLUSH
2517        WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, false,
2518            mvcc);
2519      }
2520
2521      // Prepare flush (take a snapshot)
2522      storeFlushCtxs.forEach((name, flush) -> {
2523        MemStoreSize snapshotSize = flush.prepare();
2524        totalSizeOfFlushableStores.incMemStoreSize(snapshotSize);
2525        storeFlushableSize.put(name, snapshotSize);
2526      });
2527    } catch (IOException ex) {
2528      doAbortFlushToWAL(wal, flushOpSeqId, committedFiles);
2529      throw ex;
2530    } finally {
2531      this.updatesLock.writeLock().unlock();
2532    }
2533    String s = "Finished memstore snapshotting " + this + ", syncing WAL and waiting on mvcc, " +
2534        "flushsize=" + totalSizeOfFlushableStores;
2535    status.setStatus(s);
2536    doSyncOfUnflushedWALChanges(wal, getRegionInfo());
2537    return new PrepareFlushResult(storeFlushCtxs, committedFiles, storeFlushableSize, startTime,
2538        flushOpSeqId, flushedSeqId, totalSizeOfFlushableStores);
2539  }
2540
2541  /**
2542   * Utility method broken out of internalPrepareFlushCache so that method is smaller.
2543   */
2544  private void logFatLineOnFlush(Collection<HStore> storesToFlush, long sequenceId) {
2545    if (!LOG.isInfoEnabled()) {
2546      return;
2547    }
2548    // Log a fat line detailing what is being flushed.
2549    StringBuilder perCfExtras = null;
2550    if (!isAllFamilies(storesToFlush)) {
2551      perCfExtras = new StringBuilder();
2552      for (HStore store: storesToFlush) {
2553        MemStoreSize mss = store.getFlushableSize();
2554        perCfExtras.append("; ").append(store.getColumnFamilyName());
2555        perCfExtras.append("={dataSize=")
2556            .append(StringUtils.byteDesc(mss.getDataSize()));
2557        perCfExtras.append(", heapSize=")
2558            .append(StringUtils.byteDesc(mss.getHeapSize()));
2559        perCfExtras.append(", offHeapSize=")
2560            .append(StringUtils.byteDesc(mss.getOffHeapSize()));
2561        perCfExtras.append("}");
2562      }
2563    }
2564    MemStoreSize mss = this.memStoreSizing.getMemStoreSize();
2565    LOG.info("Flushing " + + storesToFlush.size() + "/" + stores.size() + " column families," +
2566        " dataSize=" + StringUtils.byteDesc(mss.getDataSize()) +
2567        " heapSize=" + StringUtils.byteDesc(mss.getHeapSize()) +
2568        ((perCfExtras != null && perCfExtras.length() > 0)? perCfExtras.toString(): "") +
2569        ((wal != null) ? "" : "; WAL is null, using passed sequenceid=" + sequenceId));
2570  }
2571
2572  private void doAbortFlushToWAL(final WAL wal, final long flushOpSeqId,
2573      final Map<byte[], List<Path>> committedFiles) {
2574    if (wal == null) return;
2575    try {
2576      FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.ABORT_FLUSH,
2577          getRegionInfo(), flushOpSeqId, committedFiles);
2578      WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, false,
2579          mvcc);
2580    } catch (Throwable t) {
2581      LOG.warn("Received unexpected exception trying to write ABORT_FLUSH marker to WAL:" +
2582          StringUtils.stringifyException(t));
2583      // ignore this since we will be aborting the RS with DSE.
2584    }
2585    // we have called wal.startCacheFlush(), now we have to abort it
2586    wal.abortCacheFlush(this.getRegionInfo().getEncodedNameAsBytes());
2587  }
2588
2589  /**
2590   * Sync unflushed WAL changes. See HBASE-8208 for details
2591   */
2592  private static void doSyncOfUnflushedWALChanges(final WAL wal, final RegionInfo hri)
2593  throws IOException {
2594    if (wal == null) {
2595      return;
2596    }
2597    try {
2598      wal.sync(); // ensure that flush marker is sync'ed
2599    } catch (IOException ioe) {
2600      wal.abortCacheFlush(hri.getEncodedNameAsBytes());
2601      throw ioe;
2602    }
2603  }
2604
2605  /**
2606   * @return True if passed Set is all families in the region.
2607   */
2608  private boolean isAllFamilies(Collection<HStore> families) {
2609    return families == null || this.stores.size() == families.size();
2610  }
2611
2612  /**
2613   * Writes a marker to WAL indicating a flush is requested but cannot be complete due to various
2614   * reasons. Ignores exceptions from WAL. Returns whether the write succeeded.
2615   * @param wal
2616   * @return whether WAL write was successful
2617   */
2618  private boolean writeFlushRequestMarkerToWAL(WAL wal, boolean writeFlushWalMarker) {
2619    if (writeFlushWalMarker && wal != null && !writestate.readOnly) {
2620      FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.CANNOT_FLUSH,
2621        getRegionInfo(), -1, new TreeMap<>(Bytes.BYTES_COMPARATOR));
2622      try {
2623        WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, true,
2624            mvcc);
2625        return true;
2626      } catch (IOException e) {
2627        LOG.warn(getRegionInfo().getEncodedName() + " : "
2628            + "Received exception while trying to write the flush request to wal", e);
2629      }
2630    }
2631    return false;
2632  }
2633
2634  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY",
2635      justification="Intentional; notify is about completed flush")
2636  protected FlushResultImpl internalFlushCacheAndCommit(WAL wal, MonitoredTask status,
2637      PrepareFlushResult prepareResult, Collection<HStore> storesToFlush) throws IOException {
2638    // prepare flush context is carried via PrepareFlushResult
2639    TreeMap<byte[], StoreFlushContext> storeFlushCtxs = prepareResult.storeFlushCtxs;
2640    TreeMap<byte[], List<Path>> committedFiles = prepareResult.committedFiles;
2641    long startTime = prepareResult.startTime;
2642    long flushOpSeqId = prepareResult.flushOpSeqId;
2643    long flushedSeqId = prepareResult.flushedSeqId;
2644
2645    String s = "Flushing stores of " + this;
2646    status.setStatus(s);
2647    if (LOG.isTraceEnabled()) LOG.trace(s);
2648
2649    // Any failure from here on out will be catastrophic requiring server
2650    // restart so wal content can be replayed and put back into the memstore.
2651    // Otherwise, the snapshot content while backed up in the wal, it will not
2652    // be part of the current running servers state.
2653    boolean compactionRequested = false;
2654    long flushedOutputFileSize = 0;
2655    try {
2656      // A.  Flush memstore to all the HStores.
2657      // Keep running vector of all store files that includes both old and the
2658      // just-made new flush store file. The new flushed file is still in the
2659      // tmp directory.
2660
2661      for (StoreFlushContext flush : storeFlushCtxs.values()) {
2662        flush.flushCache(status);
2663      }
2664
2665      // Switch snapshot (in memstore) -> new hfile (thus causing
2666      // all the store scanners to reset/reseek).
2667      Iterator<HStore> it = storesToFlush.iterator();
2668      // stores.values() and storeFlushCtxs have same order
2669      for (StoreFlushContext flush : storeFlushCtxs.values()) {
2670        boolean needsCompaction = flush.commit(status);
2671        if (needsCompaction) {
2672          compactionRequested = true;
2673        }
2674        byte[] storeName = it.next().getColumnFamilyDescriptor().getName();
2675        List<Path> storeCommittedFiles = flush.getCommittedFiles();
2676        committedFiles.put(storeName, storeCommittedFiles);
2677        // Flush committed no files, indicating flush is empty or flush was canceled
2678        if (storeCommittedFiles == null || storeCommittedFiles.isEmpty()) {
2679          MemStoreSize storeFlushableSize = prepareResult.storeFlushableSize.get(storeName);
2680          prepareResult.totalFlushableSize.decMemStoreSize(storeFlushableSize);
2681        }
2682        flushedOutputFileSize += flush.getOutputFileSize();
2683      }
2684      storeFlushCtxs.clear();
2685
2686      // Set down the memstore size by amount of flush.
2687      MemStoreSize mss = prepareResult.totalFlushableSize.getMemStoreSize();
2688      this.decrMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize());
2689
2690      if (wal != null) {
2691        // write flush marker to WAL. If fail, we should throw DroppedSnapshotException
2692        FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.COMMIT_FLUSH,
2693          getRegionInfo(), flushOpSeqId, committedFiles);
2694        WALUtil.writeFlushMarker(wal, this.getReplicationScope(), getRegionInfo(), desc, true,
2695            mvcc);
2696      }
2697    } catch (Throwable t) {
2698      // An exception here means that the snapshot was not persisted.
2699      // The wal needs to be replayed so its content is restored to memstore.
2700      // Currently, only a server restart will do this.
2701      // We used to only catch IOEs but its possible that we'd get other
2702      // exceptions -- e.g. HBASE-659 was about an NPE -- so now we catch
2703      // all and sundry.
2704      if (wal != null) {
2705        try {
2706          FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.ABORT_FLUSH,
2707            getRegionInfo(), flushOpSeqId, committedFiles);
2708          WALUtil.writeFlushMarker(wal, this.replicationScope, getRegionInfo(), desc, false, mvcc);
2709        } catch (Throwable ex) {
2710          LOG.warn(getRegionInfo().getEncodedName() + " : "
2711              + "failed writing ABORT_FLUSH marker to WAL", ex);
2712          // ignore this since we will be aborting the RS with DSE.
2713        }
2714        wal.abortCacheFlush(this.getRegionInfo().getEncodedNameAsBytes());
2715      }
2716      DroppedSnapshotException dse = new DroppedSnapshotException("region: " +
2717          Bytes.toStringBinary(getRegionInfo().getRegionName()));
2718      dse.initCause(t);
2719      status.abort("Flush failed: " + StringUtils.stringifyException(t));
2720
2721      // Callers for flushcache() should catch DroppedSnapshotException and abort the region server.
2722      // However, since we may have the region read lock, we cannot call close(true) here since
2723      // we cannot promote to a write lock. Instead we are setting closing so that all other region
2724      // operations except for close will be rejected.
2725      this.closing.set(true);
2726
2727      if (rsServices != null) {
2728        // This is a safeguard against the case where the caller fails to explicitly handle aborting
2729        rsServices.abort("Replay of WAL required. Forcing server shutdown", dse);
2730      }
2731
2732      throw dse;
2733    }
2734
2735    // If we get to here, the HStores have been written.
2736    if (wal != null) {
2737      wal.completeCacheFlush(this.getRegionInfo().getEncodedNameAsBytes());
2738    }
2739
2740    // Record latest flush time
2741    for (HStore store: storesToFlush) {
2742      this.lastStoreFlushTimeMap.put(store, startTime);
2743    }
2744
2745    this.maxFlushedSeqId = flushedSeqId;
2746    this.lastFlushOpSeqId = flushOpSeqId;
2747
2748    // C. Finally notify anyone waiting on memstore to clear:
2749    // e.g. checkResources().
2750    synchronized (this) {
2751      notifyAll(); // FindBugs NN_NAKED_NOTIFY
2752    }
2753
2754    long time = EnvironmentEdgeManager.currentTime() - startTime;
2755    MemStoreSize mss = prepareResult.totalFlushableSize.getMemStoreSize();
2756    long memstoresize = this.memStoreSizing.getMemStoreSize().getDataSize();
2757    String msg = "Finished flush of"
2758        + " dataSize ~" + StringUtils.byteDesc(mss.getDataSize()) + "/" + mss.getDataSize()
2759        + ", heapSize ~" + StringUtils.byteDesc(mss.getHeapSize()) + "/" + mss.getHeapSize()
2760        + ", currentSize=" + StringUtils.byteDesc(memstoresize) + "/" + memstoresize
2761        + " for " + this.getRegionInfo().getEncodedName() + " in " + time + "ms, sequenceid="
2762        + flushOpSeqId +  ", compaction requested=" + compactionRequested
2763        + ((wal == null) ? "; wal=null" : "");
2764    LOG.info(msg);
2765    status.setStatus(msg);
2766
2767    if (rsServices != null && rsServices.getMetrics() != null) {
2768      rsServices.getMetrics().updateFlush(time - startTime,
2769          mss.getDataSize(), flushedOutputFileSize);
2770    }
2771
2772    return new FlushResultImpl(compactionRequested ?
2773        FlushResult.Result.FLUSHED_COMPACTION_NEEDED :
2774          FlushResult.Result.FLUSHED_NO_COMPACTION_NEEDED, flushOpSeqId);
2775  }
2776
2777  /**
2778   * Method to safely get the next sequence number.
2779   * @return Next sequence number unassociated with any actual edit.
2780   * @throws IOException
2781   */
2782  @VisibleForTesting
2783  protected long getNextSequenceId(final WAL wal) throws IOException {
2784    WriteEntry we = mvcc.begin();
2785    mvcc.completeAndWait(we);
2786    return we.getWriteNumber();
2787  }
2788
2789  //////////////////////////////////////////////////////////////////////////////
2790  // get() methods for client use.
2791  //////////////////////////////////////////////////////////////////////////////
2792
2793  @Override
2794  public RegionScannerImpl getScanner(Scan scan) throws IOException {
2795   return getScanner(scan, null);
2796  }
2797
2798  @Override
2799  public RegionScannerImpl getScanner(Scan scan, List<KeyValueScanner> additionalScanners)
2800      throws IOException {
2801    return getScanner(scan, additionalScanners, HConstants.NO_NONCE, HConstants.NO_NONCE);
2802  }
2803
2804  private RegionScannerImpl getScanner(Scan scan, List<KeyValueScanner> additionalScanners,
2805      long nonceGroup, long nonce) throws IOException {
2806    startRegionOperation(Operation.SCAN);
2807    try {
2808      // Verify families are all valid
2809      if (!scan.hasFamilies()) {
2810        // Adding all families to scanner
2811        for (byte[] family : this.htableDescriptor.getColumnFamilyNames()) {
2812          scan.addFamily(family);
2813        }
2814      } else {
2815        for (byte[] family : scan.getFamilyMap().keySet()) {
2816          checkFamily(family);
2817        }
2818      }
2819      return instantiateRegionScanner(scan, additionalScanners, nonceGroup, nonce);
2820    } finally {
2821      closeRegionOperation(Operation.SCAN);
2822    }
2823  }
2824
2825  protected RegionScanner instantiateRegionScanner(Scan scan,
2826      List<KeyValueScanner> additionalScanners) throws IOException {
2827    return instantiateRegionScanner(scan, additionalScanners, HConstants.NO_NONCE,
2828      HConstants.NO_NONCE);
2829  }
2830
2831  protected RegionScannerImpl instantiateRegionScanner(Scan scan,
2832      List<KeyValueScanner> additionalScanners, long nonceGroup, long nonce) throws IOException {
2833    if (scan.isReversed()) {
2834      if (scan.getFilter() != null) {
2835        scan.getFilter().setReversed(true);
2836      }
2837      return new ReversedRegionScannerImpl(scan, additionalScanners, this);
2838    }
2839    return new RegionScannerImpl(scan, additionalScanners, this, nonceGroup, nonce);
2840  }
2841
2842  /**
2843   * Prepare a delete for a row mutation processor
2844   * @param delete The passed delete is modified by this method. WARNING!
2845   * @throws IOException
2846   */
2847  public void prepareDelete(Delete delete) throws IOException {
2848    // Check to see if this is a deleteRow insert
2849    if(delete.getFamilyCellMap().isEmpty()){
2850      for(byte [] family : this.htableDescriptor.getColumnFamilyNames()){
2851        // Don't eat the timestamp
2852        delete.addFamily(family, delete.getTimestamp());
2853      }
2854    } else {
2855      for(byte [] family : delete.getFamilyCellMap().keySet()) {
2856        if(family == null) {
2857          throw new NoSuchColumnFamilyException("Empty family is invalid");
2858        }
2859        checkFamily(family);
2860      }
2861    }
2862  }
2863
2864  @Override
2865  public void delete(Delete delete) throws IOException {
2866    checkReadOnly();
2867    checkResources();
2868    startRegionOperation(Operation.DELETE);
2869    try {
2870      // All edits for the given row (across all column families) must happen atomically.
2871      doBatchMutate(delete);
2872    } finally {
2873      closeRegionOperation(Operation.DELETE);
2874    }
2875  }
2876
2877  /**
2878   * Row needed by below method.
2879   */
2880  private static final byte [] FOR_UNIT_TESTS_ONLY = Bytes.toBytes("ForUnitTestsOnly");
2881
2882  /**
2883   * This is used only by unit tests. Not required to be a public API.
2884   * @param familyMap map of family to edits for the given family.
2885   * @throws IOException
2886   */
2887  void delete(NavigableMap<byte[], List<Cell>> familyMap,
2888      Durability durability) throws IOException {
2889    Delete delete = new Delete(FOR_UNIT_TESTS_ONLY);
2890    delete.setFamilyCellMap(familyMap);
2891    delete.setDurability(durability);
2892    doBatchMutate(delete);
2893  }
2894
2895  /**
2896   * Set up correct timestamps in the KVs in Delete object.
2897   * <p>Caller should have the row and region locks.
2898   * @param mutation
2899   * @param familyMap
2900   * @param byteNow
2901   * @throws IOException
2902   */
2903  public void prepareDeleteTimestamps(Mutation mutation, Map<byte[], List<Cell>> familyMap,
2904      byte[] byteNow) throws IOException {
2905    for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) {
2906
2907      byte[] family = e.getKey();
2908      List<Cell> cells = e.getValue();
2909      assert cells instanceof RandomAccess;
2910
2911      Map<byte[], Integer> kvCount = new TreeMap<>(Bytes.BYTES_COMPARATOR);
2912      int listSize = cells.size();
2913      for (int i=0; i < listSize; i++) {
2914        Cell cell = cells.get(i);
2915        //  Check if time is LATEST, change to time of most recent addition if so
2916        //  This is expensive.
2917        if (cell.getTimestamp() == HConstants.LATEST_TIMESTAMP
2918            && PrivateCellUtil.isDeleteType(cell)) {
2919          byte[] qual = CellUtil.cloneQualifier(cell);
2920
2921          Integer count = kvCount.get(qual);
2922          if (count == null) {
2923            kvCount.put(qual, 1);
2924          } else {
2925            kvCount.put(qual, count + 1);
2926          }
2927          count = kvCount.get(qual);
2928
2929          Get get = new Get(CellUtil.cloneRow(cell));
2930          get.setMaxVersions(count);
2931          get.addColumn(family, qual);
2932          if (coprocessorHost != null) {
2933            if (!coprocessorHost.prePrepareTimeStampForDeleteVersion(mutation, cell,
2934                byteNow, get)) {
2935              updateDeleteLatestVersionTimestamp(cell, get, count, byteNow);
2936            }
2937          } else {
2938            updateDeleteLatestVersionTimestamp(cell, get, count, byteNow);
2939          }
2940        } else {
2941          PrivateCellUtil.updateLatestStamp(cell, byteNow);
2942        }
2943      }
2944    }
2945  }
2946
2947  void updateDeleteLatestVersionTimestamp(Cell cell, Get get, int count, byte[] byteNow)
2948      throws IOException {
2949    List<Cell> result = get(get, false);
2950
2951    if (result.size() < count) {
2952      // Nothing to delete
2953      PrivateCellUtil.updateLatestStamp(cell, byteNow);
2954      return;
2955    }
2956    if (result.size() > count) {
2957      throw new RuntimeException("Unexpected size: " + result.size());
2958    }
2959    Cell getCell = result.get(count - 1);
2960    PrivateCellUtil.setTimestamp(cell, getCell.getTimestamp());
2961  }
2962
2963  @Override
2964  public void put(Put put) throws IOException {
2965    checkReadOnly();
2966
2967    // Do a rough check that we have resources to accept a write.  The check is
2968    // 'rough' in that between the resource check and the call to obtain a
2969    // read lock, resources may run out.  For now, the thought is that this
2970    // will be extremely rare; we'll deal with it when it happens.
2971    checkResources();
2972    startRegionOperation(Operation.PUT);
2973    try {
2974      // All edits for the given row (across all column families) must happen atomically.
2975      doBatchMutate(put);
2976    } finally {
2977      closeRegionOperation(Operation.PUT);
2978    }
2979  }
2980
2981  /**
2982   * Class that tracks the progress of a batch operations, accumulating status codes and tracking
2983   * the index at which processing is proceeding. These batch operations may get split into
2984   * mini-batches for processing.
2985   */
2986  private abstract static class BatchOperation<T> {
2987    protected final T[] operations;
2988    protected final OperationStatus[] retCodeDetails;
2989    protected final WALEdit[] walEditsFromCoprocessors;
2990    // reference family cell maps directly so coprocessors can mutate them if desired
2991    protected final Map<byte[], List<Cell>>[] familyCellMaps;
2992
2993    protected final HRegion region;
2994    protected int nextIndexToProcess = 0;
2995    protected final ObservedExceptionsInBatch observedExceptions;
2996    //Durability of the batch (highest durability of all operations)
2997    protected Durability durability;
2998    protected boolean atomic = false;
2999
3000    public BatchOperation(final HRegion region, T[] operations) {
3001      this.operations = operations;
3002      this.retCodeDetails = new OperationStatus[operations.length];
3003      Arrays.fill(this.retCodeDetails, OperationStatus.NOT_RUN);
3004      this.walEditsFromCoprocessors = new WALEdit[operations.length];
3005      familyCellMaps = new Map[operations.length];
3006
3007      this.region = region;
3008      observedExceptions = new ObservedExceptionsInBatch();
3009      durability = Durability.USE_DEFAULT;
3010    }
3011
3012    /**
3013     * Visitor interface for batch operations
3014     */
3015    @FunctionalInterface
3016    public interface Visitor {
3017      /**
3018       * @param index operation index
3019       * @return If true continue visiting remaining entries, break otherwise
3020       */
3021      boolean visit(int index) throws IOException;
3022    }
3023
3024    /**
3025     * Helper method for visiting pending/ all batch operations
3026     */
3027    public void visitBatchOperations(boolean pendingOnly, int lastIndexExclusive, Visitor visitor)
3028        throws IOException {
3029      assert lastIndexExclusive <= this.size();
3030      for (int i = nextIndexToProcess; i < lastIndexExclusive; i++) {
3031        if (!pendingOnly || isOperationPending(i)) {
3032          if (!visitor.visit(i)) {
3033            break;
3034          }
3035        }
3036      }
3037    }
3038
3039    public abstract Mutation getMutation(int index);
3040
3041    public abstract long getNonceGroup(int index);
3042
3043    public abstract long getNonce(int index);
3044
3045    /**
3046     * This method is potentially expensive and useful mostly for non-replay CP path.
3047     */
3048    public abstract Mutation[] getMutationsForCoprocs();
3049
3050    public abstract boolean isInReplay();
3051
3052    public abstract long getOrigLogSeqNum();
3053
3054    public abstract void startRegionOperation() throws IOException;
3055
3056    public abstract void closeRegionOperation() throws IOException;
3057
3058    /**
3059     * Validates each mutation and prepares a batch for write. If necessary (non-replay case), runs
3060     * CP prePut()/ preDelete() hooks for all mutations in a batch. This is intended to operate on
3061     * entire batch and will be called from outside of class to check and prepare batch. This can
3062     * be implemented by calling helper method {@link #checkAndPrepareMutation(int, long)} in a
3063     * 'for' loop over mutations.
3064     */
3065    public abstract void checkAndPrepare() throws IOException;
3066
3067    /**
3068     * Implement any Put request specific check and prepare logic here. Please refer to
3069     * {@link #checkAndPrepareMutation(Mutation, long)} for how its used.
3070     */
3071    protected abstract void checkAndPreparePut(final Put p) throws IOException;
3072
3073    /**
3074     * If necessary, calls preBatchMutate() CP hook for a mini-batch and updates metrics, cell
3075     * count, tags and timestamp for all cells of all operations in a mini-batch.
3076     */
3077    public abstract void prepareMiniBatchOperations(MiniBatchOperationInProgress<Mutation>
3078        miniBatchOp, long timestamp, final List<RowLock> acquiredRowLocks) throws IOException;
3079
3080    /**
3081     * Write mini-batch operations to MemStore
3082     */
3083    public abstract WriteEntry writeMiniBatchOperationsToMemStore(
3084        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry)
3085        throws IOException;
3086
3087    protected void writeMiniBatchOperationsToMemStore(
3088        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final long writeNumber)
3089        throws IOException {
3090      MemStoreSizing memStoreAccounting = new NonThreadSafeMemStoreSizing();
3091      visitBatchOperations(true, miniBatchOp.getLastIndexExclusive(), (int index) -> {
3092        // We need to update the sequence id for following reasons.
3093        // 1) If the op is in replay mode, FSWALEntry#stampRegionSequenceId won't stamp sequence id.
3094        // 2) If no WAL, FSWALEntry won't be used
3095        // we use durability of the original mutation for the mutation passed by CP.
3096        if (isInReplay() || getMutation(index).getDurability() == Durability.SKIP_WAL) {
3097          region.updateSequenceId(familyCellMaps[index].values(), writeNumber);
3098        }
3099        applyFamilyMapToMemStore(familyCellMaps[index], memStoreAccounting);
3100        return true;
3101      });
3102      // update memStore size
3103      region.incMemStoreSize(memStoreAccounting.getDataSize(), memStoreAccounting.getHeapSize(),
3104          memStoreAccounting.getOffHeapSize());
3105    }
3106
3107    public boolean isDone() {
3108      return nextIndexToProcess == operations.length;
3109    }
3110
3111    public int size() {
3112      return operations.length;
3113    }
3114
3115    public boolean isOperationPending(int index) {
3116      return retCodeDetails[index].getOperationStatusCode() == OperationStatusCode.NOT_RUN;
3117    }
3118
3119    public List<UUID> getClusterIds() {
3120      assert size() != 0;
3121      return getMutation(0).getClusterIds();
3122    }
3123
3124    boolean isAtomic() {
3125      return atomic;
3126    }
3127
3128    /**
3129     * Helper method that checks and prepares only one mutation. This can be used to implement
3130     * {@link #checkAndPrepare()} for entire Batch.
3131     * NOTE: As CP prePut()/ preDelete() hooks may modify mutations, this method should be called
3132     * after prePut()/ preDelete() CP hooks are run for the mutation
3133     */
3134    protected void checkAndPrepareMutation(Mutation mutation, final long timestamp)
3135        throws IOException {
3136      region.checkRow(mutation.getRow(), "batchMutate");
3137      if (mutation instanceof Put) {
3138        // Check the families in the put. If bad, skip this one.
3139        checkAndPreparePut((Put) mutation);
3140        region.checkTimestamps(mutation.getFamilyCellMap(), timestamp);
3141      } else {
3142        region.prepareDelete((Delete) mutation);
3143      }
3144    }
3145
3146    protected void checkAndPrepareMutation(int index, long timestamp) throws IOException {
3147      Mutation mutation = getMutation(index);
3148      try {
3149        this.checkAndPrepareMutation(mutation, timestamp);
3150
3151        // store the family map reference to allow for mutations
3152        familyCellMaps[index] = mutation.getFamilyCellMap();
3153        // store durability for the batch (highest durability of all operations in the batch)
3154        Durability tmpDur = region.getEffectiveDurability(mutation.getDurability());
3155        if (tmpDur.ordinal() > durability.ordinal()) {
3156          durability = tmpDur;
3157        }
3158      } catch (NoSuchColumnFamilyException nscfe) {
3159        final String msg = "No such column family in batch mutation. ";
3160        if (observedExceptions.hasSeenNoSuchFamily()) {
3161          LOG.warn(msg + nscfe.getMessage());
3162        } else {
3163          LOG.warn(msg, nscfe);
3164          observedExceptions.sawNoSuchFamily();
3165        }
3166        retCodeDetails[index] = new OperationStatus(
3167            OperationStatusCode.BAD_FAMILY, nscfe.getMessage());
3168        if (isAtomic()) { // fail, atomic means all or none
3169          throw nscfe;
3170        }
3171      } catch (FailedSanityCheckException fsce) {
3172        final String msg = "Batch Mutation did not pass sanity check. ";
3173        if (observedExceptions.hasSeenFailedSanityCheck()) {
3174          LOG.warn(msg + fsce.getMessage());
3175        } else {
3176          LOG.warn(msg, fsce);
3177          observedExceptions.sawFailedSanityCheck();
3178        }
3179        retCodeDetails[index] = new OperationStatus(
3180            OperationStatusCode.SANITY_CHECK_FAILURE, fsce.getMessage());
3181        if (isAtomic()) {
3182          throw fsce;
3183        }
3184      } catch (WrongRegionException we) {
3185        final String msg = "Batch mutation had a row that does not belong to this region. ";
3186        if (observedExceptions.hasSeenWrongRegion()) {
3187          LOG.warn(msg + we.getMessage());
3188        } else {
3189          LOG.warn(msg, we);
3190          observedExceptions.sawWrongRegion();
3191        }
3192        retCodeDetails[index] = new OperationStatus(
3193            OperationStatusCode.SANITY_CHECK_FAILURE, we.getMessage());
3194        if (isAtomic()) {
3195          throw we;
3196        }
3197      }
3198    }
3199
3200    /**
3201     * Creates Mini-batch of all operations [nextIndexToProcess, lastIndexExclusive) for which
3202     * a row lock can be acquired. All mutations with locked rows are considered to be
3203     * In-progress operations and hence the name {@link MiniBatchOperationInProgress}. Mini batch
3204     * is window over {@link BatchOperation} and contains contiguous pending operations.
3205     *
3206     * @param acquiredRowLocks keeps track of rowLocks acquired.
3207     */
3208    public MiniBatchOperationInProgress<Mutation> lockRowsAndBuildMiniBatch(
3209        List<RowLock> acquiredRowLocks) throws IOException {
3210      int readyToWriteCount = 0;
3211      int lastIndexExclusive = 0;
3212      RowLock prevRowLock = null;
3213      for (; lastIndexExclusive < size(); lastIndexExclusive++) {
3214        // It reaches the miniBatchSize, stop here and process the miniBatch
3215        // This only applies to non-atomic batch operations.
3216        if (!isAtomic() && (readyToWriteCount == region.miniBatchSize)) {
3217          break;
3218        }
3219
3220        if (!isOperationPending(lastIndexExclusive)) {
3221          continue;
3222        }
3223        Mutation mutation = getMutation(lastIndexExclusive);
3224        // If we haven't got any rows in our batch, we should block to get the next one.
3225        RowLock rowLock = null;
3226        try {
3227          // if atomic then get exclusive lock, else shared lock
3228          rowLock = region.getRowLockInternal(mutation.getRow(), !isAtomic(), prevRowLock);
3229        } catch (TimeoutIOException | InterruptedIOException e) {
3230          // NOTE: We will retry when other exceptions, but we should stop if we receive
3231          // TimeoutIOException or InterruptedIOException as operation has timed out or
3232          // interrupted respectively.
3233          throw e;
3234        } catch (IOException ioe) {
3235          LOG.warn("Failed getting lock, row=" + Bytes.toStringBinary(mutation.getRow()), ioe);
3236          if (isAtomic()) { // fail, atomic means all or none
3237            throw ioe;
3238          }
3239        }
3240        if (rowLock == null) {
3241          // We failed to grab another lock
3242          if (isAtomic()) {
3243            throw new IOException("Can't apply all operations atomically!");
3244          }
3245          break; // Stop acquiring more rows for this batch
3246        } else {
3247          if (rowLock != prevRowLock) {
3248            // It is a different row now, add this to the acquiredRowLocks and
3249            // set prevRowLock to the new returned rowLock
3250            acquiredRowLocks.add(rowLock);
3251            prevRowLock = rowLock;
3252          }
3253        }
3254
3255        readyToWriteCount++;
3256      }
3257      return createMiniBatch(lastIndexExclusive, readyToWriteCount);
3258    }
3259
3260    protected MiniBatchOperationInProgress<Mutation> createMiniBatch(final int lastIndexExclusive,
3261        final int readyToWriteCount) {
3262      return new MiniBatchOperationInProgress<>(getMutationsForCoprocs(), retCodeDetails,
3263          walEditsFromCoprocessors, nextIndexToProcess, lastIndexExclusive, readyToWriteCount);
3264    }
3265
3266    /**
3267     * Builds separate WALEdit per nonce by applying input mutations. If WALEdits from CP are
3268     * present, they are merged to result WALEdit.
3269     */
3270    public List<Pair<NonceKey, WALEdit>> buildWALEdits(
3271        final MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
3272      List<Pair<NonceKey, WALEdit>> walEdits = new ArrayList<>();
3273
3274      visitBatchOperations(true, nextIndexToProcess + miniBatchOp.size(), new Visitor() {
3275        private Pair<NonceKey, WALEdit> curWALEditForNonce;
3276
3277        @Override
3278        public boolean visit(int index) throws IOException {
3279          Mutation m = getMutation(index);
3280          // we use durability of the original mutation for the mutation passed by CP.
3281          if (region.getEffectiveDurability(m.getDurability()) == Durability.SKIP_WAL) {
3282            region.recordMutationWithoutWal(m.getFamilyCellMap());
3283            return true;
3284          }
3285
3286          // the batch may contain multiple nonce keys (replay case). If so, write WALEdit for each.
3287          // Given how nonce keys are originally written, these should be contiguous.
3288          // They don't have to be, it will still work, just write more WALEdits than needed.
3289          long nonceGroup = getNonceGroup(index);
3290          long nonce = getNonce(index);
3291          if (curWALEditForNonce == null ||
3292              curWALEditForNonce.getFirst().getNonceGroup() != nonceGroup ||
3293              curWALEditForNonce.getFirst().getNonce() != nonce) {
3294            curWALEditForNonce = new Pair<>(new NonceKey(nonceGroup, nonce),
3295                new WALEdit(miniBatchOp.getCellCount(), isInReplay()));
3296            walEdits.add(curWALEditForNonce);
3297          }
3298          WALEdit walEdit = curWALEditForNonce.getSecond();
3299
3300          // Add WAL edits from CPs.
3301          WALEdit fromCP = walEditsFromCoprocessors[index];
3302          if (fromCP != null) {
3303            for (Cell cell : fromCP.getCells()) {
3304              walEdit.add(cell);
3305            }
3306          }
3307          walEdit.add(familyCellMaps[index]);
3308
3309          return true;
3310        }
3311      });
3312      return walEdits;
3313    }
3314
3315    /**
3316     * This method completes mini-batch operations by calling postBatchMutate() CP hook (if
3317     * required) and completing mvcc.
3318     */
3319    public void completeMiniBatchOperations(
3320        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry)
3321        throws IOException {
3322      if (writeEntry != null) {
3323        region.mvcc.completeAndWait(writeEntry);
3324      }
3325    }
3326
3327    public void doPostOpCleanupForMiniBatch(
3328        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WALEdit walEdit,
3329        boolean success) throws IOException {}
3330
3331    /**
3332     * Atomically apply the given map of family->edits to the memstore.
3333     * This handles the consistency control on its own, but the caller
3334     * should already have locked updatesLock.readLock(). This also does
3335     * <b>not</b> check the families for validity.
3336     *
3337     * @param familyMap Map of Cells by family
3338     */
3339    protected void applyFamilyMapToMemStore(Map<byte[], List<Cell>> familyMap,
3340        MemStoreSizing memstoreAccounting) throws IOException {
3341      for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) {
3342        byte[] family = e.getKey();
3343        List<Cell> cells = e.getValue();
3344        assert cells instanceof RandomAccess;
3345        region.applyToMemStore(region.getStore(family), cells, false, memstoreAccounting);
3346      }
3347    }
3348  }
3349
3350
3351  /**
3352   * Batch of mutation operations. Base class is shared with {@link ReplayBatchOperation} as most
3353   * of the logic is same.
3354   */
3355  static class MutationBatchOperation extends BatchOperation<Mutation> {
3356    private long nonceGroup;
3357    private long nonce;
3358    public MutationBatchOperation(final HRegion region, Mutation[] operations, boolean atomic,
3359        long nonceGroup, long nonce) {
3360      super(region, operations);
3361      this.atomic = atomic;
3362      this.nonceGroup = nonceGroup;
3363      this.nonce = nonce;
3364    }
3365
3366    @Override
3367    public Mutation getMutation(int index) {
3368      return this.operations[index];
3369    }
3370
3371    @Override
3372    public long getNonceGroup(int index) {
3373      return nonceGroup;
3374    }
3375
3376    @Override
3377    public long getNonce(int index) {
3378      return nonce;
3379    }
3380
3381    @Override
3382    public Mutation[] getMutationsForCoprocs() {
3383      return this.operations;
3384    }
3385
3386    @Override
3387    public boolean isInReplay() {
3388      return false;
3389    }
3390
3391    @Override
3392    public long getOrigLogSeqNum() {
3393      return SequenceId.NO_SEQUENCE_ID;
3394    }
3395
3396    @Override
3397    public void startRegionOperation() throws IOException {
3398      region.startRegionOperation(Operation.BATCH_MUTATE);
3399    }
3400
3401    @Override
3402    public void closeRegionOperation() throws IOException {
3403      region.closeRegionOperation(Operation.BATCH_MUTATE);
3404    }
3405
3406    @Override
3407    public void checkAndPreparePut(Put p) throws IOException {
3408      region.checkFamilies(p.getFamilyCellMap().keySet());
3409    }
3410
3411    @Override
3412    public void checkAndPrepare() throws IOException {
3413      final int[] metrics = {0, 0}; // index 0: puts, index 1: deletes
3414      visitBatchOperations(true, this.size(), new Visitor() {
3415        private long now = EnvironmentEdgeManager.currentTime();
3416        private WALEdit walEdit;
3417        @Override
3418        public boolean visit(int index) throws IOException {
3419          // Run coprocessor pre hook outside of locks to avoid deadlock
3420          if (region.coprocessorHost != null) {
3421            if (walEdit == null) {
3422              walEdit = new WALEdit();
3423            }
3424            callPreMutateCPHook(index, walEdit, metrics);
3425            if (!walEdit.isEmpty()) {
3426              walEditsFromCoprocessors[index] = walEdit;
3427              walEdit = null;
3428            }
3429          }
3430          if (isOperationPending(index)) {
3431            // TODO: Currently validation is done with current time before acquiring locks and
3432            // updates are done with different timestamps after acquiring locks. This behavior is
3433            // inherited from the code prior to this change. Can this be changed?
3434            checkAndPrepareMutation(index, now);
3435          }
3436          return true;
3437        }
3438      });
3439
3440      // FIXME: we may update metrics twice! here for all operations bypassed by CP and later in
3441      // normal processing.
3442      // Update metrics in same way as it is done when we go the normal processing route (we now
3443      // update general metrics though a Coprocessor did the work).
3444      if (region.metricsRegion != null) {
3445        if (metrics[0] > 0) {
3446          // There were some Puts in the batch.
3447          region.metricsRegion.updatePut();
3448        }
3449        if (metrics[1] > 0) {
3450          // There were some Deletes in the batch.
3451          region.metricsRegion.updateDelete();
3452        }
3453      }
3454    }
3455
3456    @Override
3457    public void prepareMiniBatchOperations(MiniBatchOperationInProgress<Mutation> miniBatchOp,
3458        long timestamp, final List<RowLock> acquiredRowLocks) throws IOException {
3459      byte[] byteTS = Bytes.toBytes(timestamp);
3460      visitBatchOperations(true, miniBatchOp.getLastIndexExclusive(), (int index) -> {
3461        Mutation mutation = getMutation(index);
3462        if (mutation instanceof Put) {
3463          region.updateCellTimestamps(familyCellMaps[index].values(), byteTS);
3464          miniBatchOp.incrementNumOfPuts();
3465        } else {
3466          region.prepareDeleteTimestamps(mutation, familyCellMaps[index], byteTS);
3467          miniBatchOp.incrementNumOfDeletes();
3468        }
3469        region.rewriteCellTags(familyCellMaps[index], mutation);
3470
3471        // update cell count
3472        if (region.getEffectiveDurability(mutation.getDurability()) != Durability.SKIP_WAL) {
3473          for (List<Cell> cells : mutation.getFamilyCellMap().values()) {
3474            miniBatchOp.addCellCount(cells.size());
3475          }
3476        }
3477
3478        WALEdit fromCP = walEditsFromCoprocessors[index];
3479        if (fromCP != null) {
3480          miniBatchOp.addCellCount(fromCP.size());
3481        }
3482        return true;
3483      });
3484
3485      if (region.coprocessorHost != null) {
3486        // calling the pre CP hook for batch mutation
3487        region.coprocessorHost.preBatchMutate(miniBatchOp);
3488        checkAndMergeCPMutations(miniBatchOp, acquiredRowLocks, timestamp);
3489      }
3490    }
3491
3492    @Override
3493    public List<Pair<NonceKey, WALEdit>> buildWALEdits(final MiniBatchOperationInProgress<Mutation>
3494        miniBatchOp) throws IOException {
3495      List<Pair<NonceKey, WALEdit>> walEdits = super.buildWALEdits(miniBatchOp);
3496      // for MutationBatchOperation, more than one nonce is not allowed
3497      if (walEdits.size() > 1) {
3498        throw new IOException("Found multiple nonce keys per batch!");
3499      }
3500      return walEdits;
3501    }
3502
3503    @Override
3504    public WriteEntry writeMiniBatchOperationsToMemStore(
3505        final MiniBatchOperationInProgress<Mutation> miniBatchOp, @Nullable WriteEntry writeEntry)
3506        throws IOException {
3507      if (writeEntry == null) {
3508        writeEntry = region.mvcc.begin();
3509      }
3510      super.writeMiniBatchOperationsToMemStore(miniBatchOp, writeEntry.getWriteNumber());
3511      return writeEntry;
3512    }
3513
3514    @Override
3515    public void completeMiniBatchOperations(
3516        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry)
3517        throws IOException {
3518      // TODO: can it be done after completing mvcc?
3519      // calling the post CP hook for batch mutation
3520      if (region.coprocessorHost != null) {
3521        region.coprocessorHost.postBatchMutate(miniBatchOp);
3522      }
3523      super.completeMiniBatchOperations(miniBatchOp, writeEntry);
3524    }
3525
3526    @Override
3527    public void doPostOpCleanupForMiniBatch(MiniBatchOperationInProgress<Mutation> miniBatchOp,
3528        final WALEdit walEdit, boolean success) throws IOException {
3529      if (miniBatchOp != null) {
3530        // synced so that the coprocessor contract is adhered to.
3531        if (region.coprocessorHost != null) {
3532          visitBatchOperations(false, miniBatchOp.getLastIndexExclusive(), (int i) -> {
3533            // only for successful puts
3534            if (retCodeDetails[i].getOperationStatusCode() == OperationStatusCode.SUCCESS) {
3535              Mutation m = getMutation(i);
3536              if (m instanceof Put) {
3537                region.coprocessorHost.postPut((Put) m, walEdit, m.getDurability());
3538              } else {
3539                region.coprocessorHost.postDelete((Delete) m, walEdit, m.getDurability());
3540              }
3541            }
3542            return true;
3543          });
3544        }
3545
3546        // See if the column families were consistent through the whole thing.
3547        // if they were then keep them. If they were not then pass a null.
3548        // null will be treated as unknown.
3549        // Total time taken might be involving Puts and Deletes.
3550        // Split the time for puts and deletes based on the total number of Puts and Deletes.
3551        if (region.metricsRegion != null) {
3552          if (miniBatchOp.getNumOfPuts() > 0) {
3553            // There were some Puts in the batch.
3554            region.metricsRegion.updatePut();
3555          }
3556          if (miniBatchOp.getNumOfDeletes() > 0) {
3557            // There were some Deletes in the batch.
3558            region.metricsRegion.updateDelete();
3559          }
3560        }
3561      }
3562
3563      if (region.coprocessorHost != null) {
3564        // call the coprocessor hook to do any finalization steps after the put is done
3565        region.coprocessorHost.postBatchMutateIndispensably(
3566            miniBatchOp != null ? miniBatchOp : createMiniBatch(size(), 0), success);
3567      }
3568    }
3569
3570    /**
3571     * Runs prePut/ preDelete coprocessor hook for input mutation in a batch
3572     * @param metrics Array of 2 ints. index 0: count of puts and index 1: count of deletes
3573     */
3574    private void callPreMutateCPHook(int index, final WALEdit walEdit, final int[] metrics)
3575        throws IOException {
3576      Mutation m = getMutation(index);
3577      if (m instanceof Put) {
3578        if (region.coprocessorHost.prePut((Put) m, walEdit, m.getDurability())) {
3579          // pre hook says skip this Put
3580          // mark as success and skip in doMiniBatchMutation
3581          metrics[0]++;
3582          retCodeDetails[index] = OperationStatus.SUCCESS;
3583        }
3584      } else if (m instanceof Delete) {
3585        Delete curDel = (Delete) m;
3586        if (curDel.getFamilyCellMap().isEmpty()) {
3587          // handle deleting a row case
3588          // TODO: prepareDelete() has been called twice, before and after preDelete() CP hook.
3589          // Can this be avoided?
3590          region.prepareDelete(curDel);
3591        }
3592        if (region.coprocessorHost.preDelete(curDel, walEdit, m.getDurability())) {
3593          // pre hook says skip this Delete
3594          // mark as success and skip in doMiniBatchMutation
3595          metrics[1]++;
3596          retCodeDetails[index] = OperationStatus.SUCCESS;
3597        }
3598      } else {
3599        String msg = "Put/Delete mutations only supported in a batch";
3600        // In case of passing Append mutations along with the Puts and Deletes in batchMutate
3601        // mark the operation return code as failure so that it will not be considered in
3602        // the doMiniBatchMutation
3603        retCodeDetails[index] = new OperationStatus(OperationStatusCode.FAILURE, msg);
3604
3605        if (isAtomic()) { // fail, atomic means all or none
3606          throw new IOException(msg);
3607        }
3608      }
3609    }
3610
3611    private void checkAndMergeCPMutations(final MiniBatchOperationInProgress<Mutation> miniBatchOp,
3612        final List<RowLock> acquiredRowLocks, final long timestamp) throws IOException {
3613      visitBatchOperations(true, nextIndexToProcess + miniBatchOp.size(), (int i) -> {
3614        // we pass (i - firstIndex) below since the call expects a relative index
3615        Mutation[] cpMutations = miniBatchOp.getOperationsFromCoprocessors(i - nextIndexToProcess);
3616        if (cpMutations == null) {
3617          return true;
3618        }
3619        // Else Coprocessor added more Mutations corresponding to the Mutation at this index.
3620        Mutation mutation = getMutation(i);
3621        for (Mutation cpMutation : cpMutations) {
3622          this.checkAndPrepareMutation(cpMutation, timestamp);
3623
3624          // Acquire row locks. If not, the whole batch will fail.
3625          acquiredRowLocks.add(region.getRowLockInternal(cpMutation.getRow(), true, null));
3626
3627          // Returned mutations from coprocessor correspond to the Mutation at index i. We can
3628          // directly add the cells from those mutations to the familyMaps of this mutation.
3629          Map<byte[], List<Cell>> cpFamilyMap = cpMutation.getFamilyCellMap();
3630          // will get added to the memStore later
3631          mergeFamilyMaps(familyCellMaps[i], cpFamilyMap);
3632
3633          // The durability of returned mutation is replaced by the corresponding mutation.
3634          // If the corresponding mutation contains the SKIP_WAL, we shouldn't count the
3635          // cells of returned mutation.
3636          if (region.getEffectiveDurability(mutation.getDurability()) != Durability.SKIP_WAL) {
3637            for (List<Cell> cells : cpFamilyMap.values()) {
3638              miniBatchOp.addCellCount(cells.size());
3639            }
3640          }
3641        }
3642        return true;
3643      });
3644    }
3645
3646    private void mergeFamilyMaps(Map<byte[], List<Cell>> familyMap,
3647        Map<byte[], List<Cell>> toBeMerged) {
3648      for (Map.Entry<byte[], List<Cell>> entry : toBeMerged.entrySet()) {
3649        List<Cell> cells = familyMap.get(entry.getKey());
3650        if (cells == null) {
3651          familyMap.put(entry.getKey(), entry.getValue());
3652        } else {
3653          cells.addAll(entry.getValue());
3654        }
3655      }
3656    }
3657  }
3658
3659  /**
3660   * Batch of mutations for replay. Base class is shared with {@link MutationBatchOperation} as most
3661   * of the logic is same.
3662   */
3663  static class ReplayBatchOperation extends BatchOperation<MutationReplay> {
3664    private long origLogSeqNum = 0;
3665    public ReplayBatchOperation(final HRegion region, MutationReplay[] operations,
3666        long origLogSeqNum) {
3667      super(region, operations);
3668      this.origLogSeqNum = origLogSeqNum;
3669    }
3670
3671    @Override
3672    public Mutation getMutation(int index) {
3673      return this.operations[index].mutation;
3674    }
3675
3676    @Override
3677    public long getNonceGroup(int index) {
3678      return this.operations[index].nonceGroup;
3679    }
3680
3681    @Override
3682    public long getNonce(int index) {
3683      return this.operations[index].nonce;
3684    }
3685
3686    @Override
3687    public Mutation[] getMutationsForCoprocs() {
3688      return null;
3689    }
3690
3691    @Override
3692    public boolean isInReplay() {
3693      return true;
3694    }
3695
3696    @Override
3697    public long getOrigLogSeqNum() {
3698      return this.origLogSeqNum;
3699    }
3700
3701    @Override
3702    public void startRegionOperation() throws IOException {
3703      region.startRegionOperation(Operation.REPLAY_BATCH_MUTATE);
3704    }
3705
3706    @Override
3707    public void closeRegionOperation() throws IOException {
3708      region.closeRegionOperation(Operation.REPLAY_BATCH_MUTATE);
3709    }
3710
3711    /**
3712     * During replay, there could exist column families which are removed between region server
3713     * failure and replay
3714     */
3715    @Override
3716    protected void checkAndPreparePut(Put p) throws IOException {
3717      Map<byte[], List<Cell>> familyCellMap = p.getFamilyCellMap();
3718      List<byte[]> nonExistentList = null;
3719      for (byte[] family : familyCellMap.keySet()) {
3720        if (!region.htableDescriptor.hasColumnFamily(family)) {
3721          if (nonExistentList == null) {
3722            nonExistentList = new ArrayList<>();
3723          }
3724          nonExistentList.add(family);
3725        }
3726      }
3727      if (nonExistentList != null) {
3728        for (byte[] family : nonExistentList) {
3729          // Perhaps schema was changed between crash and replay
3730          LOG.info("No family for " + Bytes.toString(family) + " omit from reply.");
3731          familyCellMap.remove(family);
3732        }
3733      }
3734    }
3735
3736    @Override
3737    public void checkAndPrepare() throws IOException {
3738      long now = EnvironmentEdgeManager.currentTime();
3739      visitBatchOperations(true, this.size(), (int index) -> {
3740        checkAndPrepareMutation(index, now);
3741        return true;
3742      });
3743    }
3744
3745    @Override
3746    public void prepareMiniBatchOperations(MiniBatchOperationInProgress<Mutation> miniBatchOp,
3747        long timestamp, final List<RowLock> acquiredRowLocks) throws IOException {
3748      visitBatchOperations(true, miniBatchOp.getLastIndexExclusive(), (int index) -> {
3749        // update cell count
3750        for (List<Cell> cells : getMutation(index).getFamilyCellMap().values()) {
3751          miniBatchOp.addCellCount(cells.size());
3752        }
3753        return true;
3754      });
3755    }
3756
3757    @Override
3758    public WriteEntry writeMiniBatchOperationsToMemStore(
3759        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry)
3760        throws IOException {
3761      super.writeMiniBatchOperationsToMemStore(miniBatchOp, getOrigLogSeqNum());
3762      return writeEntry;
3763    }
3764
3765    @Override
3766    public void completeMiniBatchOperations(
3767        final MiniBatchOperationInProgress<Mutation> miniBatchOp, final WriteEntry writeEntry)
3768        throws IOException {
3769      super.completeMiniBatchOperations(miniBatchOp, writeEntry);
3770      region.mvcc.advanceTo(getOrigLogSeqNum());
3771    }
3772  }
3773
3774  public OperationStatus[] batchMutate(Mutation[] mutations, long nonceGroup, long nonce)
3775      throws IOException {
3776    return batchMutate(mutations, false, nonceGroup, nonce);
3777  }
3778
3779  public OperationStatus[] batchMutate(Mutation[] mutations, boolean atomic, long nonceGroup,
3780      long nonce) throws IOException {
3781    // As it stands, this is used for 3 things
3782    //  * batchMutate with single mutation - put/delete, separate or from checkAndMutate.
3783    //  * coprocessor calls (see ex. BulkDeleteEndpoint).
3784    // So nonces are not really ever used by HBase. They could be by coprocs, and checkAnd...
3785    return batchMutate(new MutationBatchOperation(this, mutations, atomic, nonceGroup, nonce));
3786  }
3787
3788  @Override
3789  public OperationStatus[] batchMutate(Mutation[] mutations) throws IOException {
3790    return batchMutate(mutations, HConstants.NO_NONCE, HConstants.NO_NONCE);
3791  }
3792
3793  public OperationStatus[] batchReplay(MutationReplay[] mutations, long replaySeqId)
3794      throws IOException {
3795    if (!RegionReplicaUtil.isDefaultReplica(getRegionInfo())
3796        && replaySeqId < lastReplayedOpenRegionSeqId) {
3797      // if it is a secondary replica we should ignore these entries silently
3798      // since they are coming out of order
3799      if (LOG.isTraceEnabled()) {
3800        LOG.trace(getRegionInfo().getEncodedName() + " : "
3801          + "Skipping " + mutations.length + " mutations with replaySeqId=" + replaySeqId
3802          + " which is < than lastReplayedOpenRegionSeqId=" + lastReplayedOpenRegionSeqId);
3803        for (MutationReplay mut : mutations) {
3804          LOG.trace(getRegionInfo().getEncodedName() + " : Skipping : " + mut.mutation);
3805        }
3806      }
3807
3808      OperationStatus[] statuses = new OperationStatus[mutations.length];
3809      for (int i = 0; i < statuses.length; i++) {
3810        statuses[i] = OperationStatus.SUCCESS;
3811      }
3812      return statuses;
3813    }
3814    return batchMutate(new ReplayBatchOperation(this, mutations, replaySeqId));
3815  }
3816
3817  /**
3818   * Perform a batch of mutations.
3819   *
3820   * It supports only Put and Delete mutations and will ignore other types passed. Operations in
3821   * a batch are stored with highest durability specified of for all operations in a batch,
3822   * except for {@link Durability#SKIP_WAL}.
3823   *
3824   * <p>This function is called from {@link #batchReplay(MutationReplay[], long)} with
3825   * {@link ReplayBatchOperation} instance and {@link #batchMutate(Mutation[], long, long)} with
3826   * {@link MutationBatchOperation} instance as an argument. As the processing of replay batch
3827   * and mutation batch is very similar, lot of code is shared by providing generic methods in
3828   * base class {@link BatchOperation}. The logic for this method and
3829   * {@link #doMiniBatchMutate(BatchOperation)} is implemented using methods in base class which
3830   * are overridden by derived classes to implement special behavior.
3831   *
3832   * @param batchOp contains the list of mutations
3833   * @return an array of OperationStatus which internally contains the
3834   *         OperationStatusCode and the exceptionMessage if any.
3835   * @throws IOException
3836   */
3837  OperationStatus[] batchMutate(BatchOperation<?> batchOp) throws IOException {
3838    boolean initialized = false;
3839    batchOp.startRegionOperation();
3840    try {
3841      while (!batchOp.isDone()) {
3842        if (!batchOp.isInReplay()) {
3843          checkReadOnly();
3844        }
3845        checkResources();
3846
3847        if (!initialized) {
3848          this.writeRequestsCount.add(batchOp.size());
3849          // validate and prepare batch for write, for MutationBatchOperation it also calls CP
3850          // prePut()/ preDelete() hooks
3851          batchOp.checkAndPrepare();
3852          initialized = true;
3853        }
3854        doMiniBatchMutate(batchOp);
3855        requestFlushIfNeeded();
3856      }
3857    } finally {
3858      batchOp.closeRegionOperation();
3859    }
3860    return batchOp.retCodeDetails;
3861  }
3862
3863  /**
3864   * Called to do a piece of the batch that came in to {@link #batchMutate(Mutation[], long, long)}
3865   * In here we also handle replay of edits on region recover.
3866   * @return Change in size brought about by applying <code>batchOp</code>
3867   */
3868  private void doMiniBatchMutate(BatchOperation<?> batchOp) throws IOException {
3869    boolean success = false;
3870    WALEdit walEdit = null;
3871    WriteEntry writeEntry = null;
3872    boolean locked = false;
3873    // We try to set up a batch in the range [batchOp.nextIndexToProcess,lastIndexExclusive)
3874    MiniBatchOperationInProgress<Mutation> miniBatchOp = null;
3875    /** Keep track of the locks we hold so we can release them in finally clause */
3876    List<RowLock> acquiredRowLocks = Lists.newArrayListWithCapacity(batchOp.size());
3877    try {
3878      // STEP 1. Try to acquire as many locks as we can and build mini-batch of operations with
3879      // locked rows
3880      miniBatchOp = batchOp.lockRowsAndBuildMiniBatch(acquiredRowLocks);
3881
3882      // We've now grabbed as many mutations off the list as we can
3883      // Ensure we acquire at least one.
3884      if (miniBatchOp.getReadyToWriteCount() <= 0) {
3885        // Nothing to put/delete -- an exception in the above such as NoSuchColumnFamily?
3886        return;
3887      }
3888
3889      lock(this.updatesLock.readLock(), miniBatchOp.getReadyToWriteCount());
3890      locked = true;
3891
3892      // STEP 2. Update mini batch of all operations in progress with  LATEST_TIMESTAMP timestamp
3893      // We should record the timestamp only after we have acquired the rowLock,
3894      // otherwise, newer puts/deletes are not guaranteed to have a newer timestamp
3895      long now = EnvironmentEdgeManager.currentTime();
3896      batchOp.prepareMiniBatchOperations(miniBatchOp, now, acquiredRowLocks);
3897
3898      // STEP 3. Build WAL edit
3899      List<Pair<NonceKey, WALEdit>> walEdits = batchOp.buildWALEdits(miniBatchOp);
3900
3901      // STEP 4. Append the WALEdits to WAL and sync.
3902      for(Iterator<Pair<NonceKey, WALEdit>> it = walEdits.iterator(); it.hasNext();) {
3903        Pair<NonceKey, WALEdit> nonceKeyWALEditPair = it.next();
3904        walEdit = nonceKeyWALEditPair.getSecond();
3905        NonceKey nonceKey = nonceKeyWALEditPair.getFirst();
3906
3907        if (walEdit != null && !walEdit.isEmpty()) {
3908          writeEntry = doWALAppend(walEdit, batchOp.durability, batchOp.getClusterIds(), now,
3909              nonceKey.getNonceGroup(), nonceKey.getNonce(), batchOp.getOrigLogSeqNum());
3910        }
3911
3912        // Complete mvcc for all but last writeEntry (for replay case)
3913        if (it.hasNext() && writeEntry != null) {
3914          mvcc.complete(writeEntry);
3915          writeEntry = null;
3916        }
3917      }
3918
3919      // STEP 5. Write back to memStore
3920      // NOTE: writeEntry can be null here
3921      writeEntry = batchOp.writeMiniBatchOperationsToMemStore(miniBatchOp, writeEntry);
3922
3923      // STEP 6. Complete MiniBatchOperations: If required calls postBatchMutate() CP hook and
3924      // complete mvcc for last writeEntry
3925      batchOp.completeMiniBatchOperations(miniBatchOp, writeEntry);
3926      writeEntry = null;
3927      success = true;
3928    } finally {
3929      // Call complete rather than completeAndWait because we probably had error if walKey != null
3930      if (writeEntry != null) mvcc.complete(writeEntry);
3931
3932      if (locked) {
3933        this.updatesLock.readLock().unlock();
3934      }
3935      releaseRowLocks(acquiredRowLocks);
3936
3937      final int finalLastIndexExclusive =
3938          miniBatchOp != null ? miniBatchOp.getLastIndexExclusive() : batchOp.size();
3939      final boolean finalSuccess = success;
3940      batchOp.visitBatchOperations(true, finalLastIndexExclusive, (int i) -> {
3941        batchOp.retCodeDetails[i] =
3942            finalSuccess ? OperationStatus.SUCCESS : OperationStatus.FAILURE;
3943        return true;
3944      });
3945
3946      batchOp.doPostOpCleanupForMiniBatch(miniBatchOp, walEdit, finalSuccess);
3947
3948      batchOp.nextIndexToProcess = finalLastIndexExclusive;
3949    }
3950  }
3951
3952  /**
3953   * Returns effective durability from the passed durability and
3954   * the table descriptor.
3955   */
3956  protected Durability getEffectiveDurability(Durability d) {
3957    return d == Durability.USE_DEFAULT ? this.regionDurability : d;
3958  }
3959
3960  @Override
3961  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareOperator op,
3962    ByteArrayComparable comparator, TimeRange timeRange, Mutation mutation) throws IOException {
3963    checkMutationType(mutation, row);
3964    return doCheckAndRowMutate(row, family, qualifier, op, comparator, timeRange, null, mutation);
3965  }
3966
3967  @Override
3968  public boolean checkAndRowMutate(byte[] row, byte[] family, byte[] qualifier, CompareOperator op,
3969    ByteArrayComparable comparator, TimeRange timeRange, RowMutations rm) throws IOException {
3970    return doCheckAndRowMutate(row, family, qualifier, op, comparator, timeRange, rm, null);
3971  }
3972
3973  /**
3974   * checkAndMutate and checkAndRowMutate are 90% the same. Rather than copy/paste, below has
3975   * switches in the few places where there is deviation.
3976   */
3977  private boolean doCheckAndRowMutate(byte[] row, byte[] family, byte[] qualifier,
3978    CompareOperator op, ByteArrayComparable comparator, TimeRange timeRange,
3979    RowMutations rowMutations, Mutation mutation)
3980  throws IOException {
3981    // Could do the below checks but seems wacky with two callers only. Just comment out for now.
3982    // One caller passes a Mutation, the other passes RowMutation. Presume all good so we don't
3983    // need these commented out checks.
3984    // if (rowMutations == null && mutation == null) throw new DoNotRetryIOException("Both null");
3985    // if (rowMutations != null && mutation != null) throw new DoNotRetryIOException("Both set");
3986    checkReadOnly();
3987    // TODO, add check for value length also move this check to the client
3988    checkResources();
3989    startRegionOperation();
3990    try {
3991      Get get = new Get(row);
3992      checkFamily(family);
3993      get.addColumn(family, qualifier);
3994      if (timeRange != null) {
3995        get.setTimeRange(timeRange.getMin(), timeRange.getMax());
3996      }
3997      // Lock row - note that doBatchMutate will relock this row if called
3998      checkRow(row, "doCheckAndRowMutate");
3999      RowLock rowLock = getRowLockInternal(get.getRow(), false, null);
4000      try {
4001        if (mutation != null && this.getCoprocessorHost() != null) {
4002          // Call coprocessor.
4003          Boolean processed = null;
4004          if (mutation instanceof Put) {
4005            processed = this.getCoprocessorHost().preCheckAndPutAfterRowLock(row, family,
4006                qualifier, op, comparator, (Put)mutation);
4007          } else if (mutation instanceof Delete) {
4008            processed = this.getCoprocessorHost().preCheckAndDeleteAfterRowLock(row, family,
4009                qualifier, op, comparator, (Delete)mutation);
4010          }
4011          if (processed != null) {
4012            return processed;
4013          }
4014        }
4015        // NOTE: We used to wait here until mvcc caught up:  mvcc.await();
4016        // Supposition is that now all changes are done under row locks, then when we go to read,
4017        // we'll get the latest on this row.
4018        List<Cell> result = get(get, false);
4019        boolean valueIsNull = comparator.getValue() == null || comparator.getValue().length == 0;
4020        boolean matches = false;
4021        long cellTs = 0;
4022        if (result.isEmpty() && valueIsNull) {
4023          matches = true;
4024        } else if (result.size() > 0 && result.get(0).getValueLength() == 0 && valueIsNull) {
4025          matches = true;
4026          cellTs = result.get(0).getTimestamp();
4027        } else if (result.size() == 1 && !valueIsNull) {
4028          Cell kv = result.get(0);
4029          cellTs = kv.getTimestamp();
4030          int compareResult = PrivateCellUtil.compareValue(kv, comparator);
4031          matches = matches(op, compareResult);
4032        }
4033        // If matches put the new put or delete the new delete
4034        if (matches) {
4035          // We have acquired the row lock already. If the system clock is NOT monotonically
4036          // non-decreasing (see HBASE-14070) we should make sure that the mutation has a
4037          // larger timestamp than what was observed via Get. doBatchMutate already does this, but
4038          // there is no way to pass the cellTs. See HBASE-14054.
4039          long now = EnvironmentEdgeManager.currentTime();
4040          long ts = Math.max(now, cellTs); // ensure write is not eclipsed
4041          byte[] byteTs = Bytes.toBytes(ts);
4042          if (mutation != null) {
4043            if (mutation instanceof Put) {
4044              updateCellTimestamps(mutation.getFamilyCellMap().values(), byteTs);
4045            }
4046            // And else 'delete' is not needed since it already does a second get, and sets the
4047            // timestamp from get (see prepareDeleteTimestamps).
4048          } else {
4049            for (Mutation m: rowMutations.getMutations()) {
4050              if (m instanceof Put) {
4051                updateCellTimestamps(m.getFamilyCellMap().values(), byteTs);
4052              }
4053            }
4054            // And else 'delete' is not needed since it already does a second get, and sets the
4055            // timestamp from get (see prepareDeleteTimestamps).
4056          }
4057          // All edits for the given row (across all column families) must happen atomically.
4058          if (mutation != null) {
4059            doBatchMutate(mutation);
4060          } else {
4061            mutateRow(rowMutations);
4062          }
4063          this.checkAndMutateChecksPassed.increment();
4064          return true;
4065        }
4066        this.checkAndMutateChecksFailed.increment();
4067        return false;
4068      } finally {
4069        rowLock.release();
4070      }
4071    } finally {
4072      closeRegionOperation();
4073    }
4074  }
4075
4076  private void checkMutationType(final Mutation mutation, final byte [] row)
4077  throws DoNotRetryIOException {
4078    boolean isPut = mutation instanceof Put;
4079    if (!isPut && !(mutation instanceof Delete)) {
4080      throw new org.apache.hadoop.hbase.DoNotRetryIOException("Action must be Put or Delete");
4081    }
4082    if (!Bytes.equals(row, mutation.getRow())) {
4083      throw new org.apache.hadoop.hbase.DoNotRetryIOException("Action's getRow must match");
4084    }
4085  }
4086
4087  private boolean matches(final CompareOperator op, final int compareResult) {
4088    boolean matches = false;
4089    switch (op) {
4090      case LESS:
4091        matches = compareResult < 0;
4092        break;
4093      case LESS_OR_EQUAL:
4094        matches = compareResult <= 0;
4095        break;
4096      case EQUAL:
4097        matches = compareResult == 0;
4098        break;
4099      case NOT_EQUAL:
4100        matches = compareResult != 0;
4101        break;
4102      case GREATER_OR_EQUAL:
4103        matches = compareResult >= 0;
4104        break;
4105      case GREATER:
4106        matches = compareResult > 0;
4107        break;
4108      default:
4109        throw new RuntimeException("Unknown Compare op " + op.name());
4110    }
4111    return matches;
4112  }
4113
4114
4115  private void doBatchMutate(Mutation mutation) throws IOException {
4116    // Currently this is only called for puts and deletes, so no nonces.
4117    OperationStatus[] batchMutate = this.batchMutate(new Mutation[]{mutation});
4118    if (batchMutate[0].getOperationStatusCode().equals(OperationStatusCode.SANITY_CHECK_FAILURE)) {
4119      throw new FailedSanityCheckException(batchMutate[0].getExceptionMsg());
4120    } else if (batchMutate[0].getOperationStatusCode().equals(OperationStatusCode.BAD_FAMILY)) {
4121      throw new NoSuchColumnFamilyException(batchMutate[0].getExceptionMsg());
4122    }
4123  }
4124
4125  /**
4126   * Complete taking the snapshot on the region. Writes the region info and adds references to the
4127   * working snapshot directory.
4128   *
4129   * TODO for api consistency, consider adding another version with no {@link ForeignExceptionSnare}
4130   * arg.  (In the future other cancellable HRegion methods could eventually add a
4131   * {@link ForeignExceptionSnare}, or we could do something fancier).
4132   *
4133   * @param desc snapshot description object
4134   * @param exnSnare ForeignExceptionSnare that captures external exceptions in case we need to
4135   *   bail out.  This is allowed to be null and will just be ignored in that case.
4136   * @throws IOException if there is an external or internal error causing the snapshot to fail
4137   */
4138  public void addRegionToSnapshot(SnapshotDescription desc,
4139      ForeignExceptionSnare exnSnare) throws IOException {
4140    Path rootDir = FSUtils.getRootDir(conf);
4141    Path snapshotDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir);
4142
4143    SnapshotManifest manifest = SnapshotManifest.create(conf, getFilesystem(),
4144            snapshotDir, desc, exnSnare);
4145    manifest.addRegion(this);
4146  }
4147
4148  private void updateSequenceId(final Iterable<List<Cell>> cellItr, final long sequenceId)
4149      throws IOException {
4150    for (List<Cell> cells: cellItr) {
4151      if (cells == null) return;
4152      for (Cell cell : cells) {
4153        PrivateCellUtil.setSequenceId(cell, sequenceId);
4154      }
4155    }
4156  }
4157
4158  /**
4159   * Replace any cell timestamps set to {@link org.apache.hadoop.hbase.HConstants#LATEST_TIMESTAMP}
4160   * provided current timestamp.
4161   * @param cellItr
4162   * @param now
4163   */
4164  private static void updateCellTimestamps(final Iterable<List<Cell>> cellItr, final byte[] now)
4165      throws IOException {
4166    for (List<Cell> cells: cellItr) {
4167      if (cells == null) continue;
4168      // Optimization: 'foreach' loop is not used. See:
4169      // HBASE-12023 HRegion.applyFamilyMapToMemstore creates too many iterator objects
4170      assert cells instanceof RandomAccess;
4171      int listSize = cells.size();
4172      for (int i = 0; i < listSize; i++) {
4173        PrivateCellUtil.updateLatestStamp(cells.get(i), now);
4174      }
4175    }
4176  }
4177
4178  /**
4179   * Possibly rewrite incoming cell tags.
4180   */
4181  void rewriteCellTags(Map<byte[], List<Cell>> familyMap, final Mutation m) {
4182    // Check if we have any work to do and early out otherwise
4183    // Update these checks as more logic is added here
4184    if (m.getTTL() == Long.MAX_VALUE) {
4185      return;
4186    }
4187
4188    // From this point we know we have some work to do
4189    for (Map.Entry<byte[], List<Cell>> e: familyMap.entrySet()) {
4190      List<Cell> cells = e.getValue();
4191      assert cells instanceof RandomAccess;
4192      int listSize = cells.size();
4193      for (int i = 0; i < listSize; i++) {
4194        Cell cell = cells.get(i);
4195        List<Tag> newTags = TagUtil.carryForwardTags(null, cell);
4196        newTags = TagUtil.carryForwardTTLTag(newTags, m.getTTL());
4197        // Rewrite the cell with the updated set of tags
4198        cells.set(i, PrivateCellUtil.createCell(cell, newTags));
4199      }
4200    }
4201  }
4202
4203  /*
4204   * Check if resources to support an update.
4205   *
4206   * We throw RegionTooBusyException if above memstore limit
4207   * and expect client to retry using some kind of backoff
4208  */
4209  void checkResources() throws RegionTooBusyException {
4210    // If catalog region, do not impose resource constraints or block updates.
4211    if (this.getRegionInfo().isMetaRegion()) return;
4212
4213    MemStoreSize mss = this.memStoreSizing.getMemStoreSize();
4214    if (mss.getHeapSize() + mss.getOffHeapSize() > this.blockingMemStoreSize) {
4215      blockedRequestsCount.increment();
4216      requestFlush();
4217      // Don't print current limit because it will vary too much. The message is used as a key
4218      // over in RetriesExhaustedWithDetailsException processing.
4219      throw new RegionTooBusyException("Over memstore limit=" +
4220        org.apache.hadoop.hbase.procedure2.util.StringUtils.humanSize(this.blockingMemStoreSize) +
4221        ", regionName=" +
4222          (this.getRegionInfo() == null? "unknown": this.getRegionInfo().getEncodedName()) +
4223          ", server=" + (this.getRegionServerServices() == null? "unknown":
4224              this.getRegionServerServices().getServerName()));
4225    }
4226  }
4227
4228  /**
4229   * @throws IOException Throws exception if region is in read-only mode.
4230   */
4231  protected void checkReadOnly() throws IOException {
4232    if (isReadOnly()) {
4233      throw new DoNotRetryIOException("region is read only");
4234    }
4235  }
4236
4237  protected void checkReadsEnabled() throws IOException {
4238    if (!this.writestate.readsEnabled) {
4239      throw new IOException(getRegionInfo().getEncodedName()
4240        + ": The region's reads are disabled. Cannot serve the request");
4241    }
4242  }
4243
4244  public void setReadsEnabled(boolean readsEnabled) {
4245   if (readsEnabled && !this.writestate.readsEnabled) {
4246     LOG.info(getRegionInfo().getEncodedName() + " : Enabling reads for region.");
4247    }
4248    this.writestate.setReadsEnabled(readsEnabled);
4249  }
4250
4251  /**
4252   * Add updates first to the wal and then add values to memstore.
4253   * Warning: Assumption is caller has lock on passed in row.
4254   * @param edits Cell updates by column
4255   * @throws IOException
4256   */
4257  void put(final byte [] row, byte [] family, List<Cell> edits)
4258  throws IOException {
4259    NavigableMap<byte[], List<Cell>> familyMap;
4260    familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
4261
4262    familyMap.put(family, edits);
4263    Put p = new Put(row);
4264    p.setFamilyCellMap(familyMap);
4265    doBatchMutate(p);
4266  }
4267
4268  /**
4269   * @param delta If we are doing delta changes -- e.g. increment/append -- then this flag will be
4270   *          set; when set we will run operations that make sense in the increment/append scenario
4271   *          but that do not make sense otherwise.
4272   * @see #applyToMemStore(HStore, Cell, MemStoreSizing)
4273   */
4274  private void applyToMemStore(HStore store, List<Cell> cells, boolean delta,
4275      MemStoreSizing memstoreAccounting) throws IOException {
4276    // Any change in how we update Store/MemStore needs to also be done in other applyToMemStore!!!!
4277    boolean upsert = delta && store.getColumnFamilyDescriptor().getMaxVersions() == 1;
4278    if (upsert) {
4279      store.upsert(cells, getSmallestReadPoint(), memstoreAccounting);
4280    } else {
4281      store.add(cells, memstoreAccounting);
4282    }
4283  }
4284
4285  /**
4286   * @see #applyToMemStore(HStore, List, boolean, MemStoreSizing)
4287   */
4288  private void applyToMemStore(HStore store, Cell cell, MemStoreSizing memstoreAccounting)
4289      throws IOException {
4290    // Any change in how we update Store/MemStore needs to also be done in other applyToMemStore!!!!
4291    if (store == null) {
4292      checkFamily(CellUtil.cloneFamily(cell));
4293      // Unreachable because checkFamily will throw exception
4294    }
4295    store.add(cell, memstoreAccounting);
4296  }
4297
4298  /**
4299   * Check the collection of families for validity.
4300   * @param families
4301   * @throws NoSuchColumnFamilyException
4302   */
4303  public void checkFamilies(Collection<byte[]> families) throws NoSuchColumnFamilyException {
4304    for (byte[] family : families) {
4305      checkFamily(family);
4306    }
4307  }
4308
4309  /**
4310   * Check the collection of families for valid timestamps
4311   * @param familyMap
4312   * @param now current timestamp
4313   * @throws FailedSanityCheckException
4314   */
4315  public void checkTimestamps(final Map<byte[], List<Cell>> familyMap, long now)
4316      throws FailedSanityCheckException {
4317    if (timestampSlop == HConstants.LATEST_TIMESTAMP) {
4318      return;
4319    }
4320    long maxTs = now + timestampSlop;
4321    for (List<Cell> kvs : familyMap.values()) {
4322      // Optimization: 'foreach' loop is not used. See:
4323      // HBASE-12023 HRegion.applyFamilyMapToMemstore creates too many iterator objects
4324      assert kvs instanceof RandomAccess;
4325      int listSize  = kvs.size();
4326      for (int i=0; i < listSize; i++) {
4327        Cell cell = kvs.get(i);
4328        // see if the user-side TS is out of range. latest = server-side
4329        long ts = cell.getTimestamp();
4330        if (ts != HConstants.LATEST_TIMESTAMP && ts > maxTs) {
4331          throw new FailedSanityCheckException("Timestamp for KV out of range "
4332              + cell + " (too.new=" + timestampSlop + ")");
4333        }
4334      }
4335    }
4336  }
4337
4338  /*
4339   * @param size
4340   * @return True if size is over the flush threshold
4341   */
4342  private boolean isFlushSize(MemStoreSize size) {
4343    return size.getHeapSize() + size.getOffHeapSize() > getMemStoreFlushSize();
4344  }
4345
4346  /**
4347   * Read the edits put under this region by wal splitting process.  Put
4348   * the recovered edits back up into this region.
4349   *
4350   * <p>We can ignore any wal message that has a sequence ID that's equal to or
4351   * lower than minSeqId.  (Because we know such messages are already
4352   * reflected in the HFiles.)
4353   *
4354   * <p>While this is running we are putting pressure on memory yet we are
4355   * outside of our usual accounting because we are not yet an onlined region
4356   * (this stuff is being run as part of Region initialization).  This means
4357   * that if we're up against global memory limits, we'll not be flagged to flush
4358   * because we are not online. We can't be flushed by usual mechanisms anyways;
4359   * we're not yet online so our relative sequenceids are not yet aligned with
4360   * WAL sequenceids -- not till we come up online, post processing of split
4361   * edits.
4362   *
4363   * <p>But to help relieve memory pressure, at least manage our own heap size
4364   * flushing if are in excess of per-region limits.  Flushing, though, we have
4365   * to be careful and avoid using the regionserver/wal sequenceid.  Its running
4366   * on a different line to whats going on in here in this region context so if we
4367   * crashed replaying these edits, but in the midst had a flush that used the
4368   * regionserver wal with a sequenceid in excess of whats going on in here
4369   * in this region and with its split editlogs, then we could miss edits the
4370   * next time we go to recover. So, we have to flush inline, using seqids that
4371   * make sense in a this single region context only -- until we online.
4372   *
4373   * @param maxSeqIdInStores Any edit found in split editlogs needs to be in excess of
4374   * the maxSeqId for the store to be applied, else its skipped.
4375   * @return the sequence id of the last edit added to this region out of the
4376   * recovered edits log or <code>minSeqId</code> if nothing added from editlogs.
4377   * @throws IOException
4378   */
4379  protected long replayRecoveredEditsIfAny(final Path regiondir,
4380      Map<byte[], Long> maxSeqIdInStores,
4381      final CancelableProgressable reporter, final MonitoredTask status)
4382      throws IOException {
4383    long minSeqIdForTheRegion = -1;
4384    for (Long maxSeqIdInStore : maxSeqIdInStores.values()) {
4385      if (maxSeqIdInStore < minSeqIdForTheRegion || minSeqIdForTheRegion == -1) {
4386        minSeqIdForTheRegion = maxSeqIdInStore;
4387      }
4388    }
4389    long seqid = minSeqIdForTheRegion;
4390
4391    FileSystem fs = this.fs.getFileSystem();
4392    NavigableSet<Path> files = WALSplitter.getSplitEditFilesSorted(fs, regiondir);
4393    if (LOG.isDebugEnabled()) {
4394      LOG.debug("Found " + (files == null ? 0 : files.size())
4395        + " recovered edits file(s) under " + regiondir);
4396    }
4397
4398    if (files == null || files.isEmpty()) return seqid;
4399
4400    for (Path edits: files) {
4401      if (edits == null || !fs.exists(edits)) {
4402        LOG.warn("Null or non-existent edits file: " + edits);
4403        continue;
4404      }
4405      if (isZeroLengthThenDelete(fs, edits)) continue;
4406
4407      long maxSeqId;
4408      String fileName = edits.getName();
4409      maxSeqId = Math.abs(Long.parseLong(fileName));
4410      if (maxSeqId <= minSeqIdForTheRegion) {
4411        if (LOG.isDebugEnabled()) {
4412          String msg = "Maximum sequenceid for this wal is " + maxSeqId
4413            + " and minimum sequenceid for the region is " + minSeqIdForTheRegion
4414            + ", skipped the whole file, path=" + edits;
4415          LOG.debug(msg);
4416        }
4417        continue;
4418      }
4419
4420      try {
4421        // replay the edits. Replay can return -1 if everything is skipped, only update
4422        // if seqId is greater
4423        seqid = Math.max(seqid, replayRecoveredEdits(edits, maxSeqIdInStores, reporter));
4424      } catch (IOException e) {
4425        boolean skipErrors = conf.getBoolean(
4426            HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS,
4427            conf.getBoolean(
4428                "hbase.skip.errors",
4429                HConstants.DEFAULT_HREGION_EDITS_REPLAY_SKIP_ERRORS));
4430        if (conf.get("hbase.skip.errors") != null) {
4431          LOG.warn(
4432              "The property 'hbase.skip.errors' has been deprecated. Please use " +
4433              HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS + " instead.");
4434        }
4435        if (skipErrors) {
4436          Path p = WALSplitter.moveAsideBadEditsFile(fs, edits);
4437          LOG.error(HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS
4438              + "=true so continuing. Renamed " + edits +
4439              " as " + p, e);
4440        } else {
4441          throw e;
4442        }
4443      }
4444    }
4445    if (seqid > minSeqIdForTheRegion) {
4446      // Then we added some edits to memory. Flush and cleanup split edit files.
4447      internalFlushcache(null, seqid, stores.values(), status, false, FlushLifeCycleTracker.DUMMY);
4448    }
4449    // Now delete the content of recovered edits.  We're done w/ them.
4450    if (files.size() > 0 && this.conf.getBoolean("hbase.region.archive.recovered.edits", false)) {
4451      // For debugging data loss issues!
4452      // If this flag is set, make use of the hfile archiving by making recovered.edits a fake
4453      // column family. Have to fake out file type too by casting our recovered.edits as storefiles
4454      String fakeFamilyName = WALSplitter.getRegionDirRecoveredEditsDir(regiondir).getName();
4455      Set<HStoreFile> fakeStoreFiles = new HashSet<>(files.size());
4456      for (Path file: files) {
4457        fakeStoreFiles.add(
4458          new HStoreFile(getRegionFileSystem().getFileSystem(), file, this.conf, null, null, true));
4459      }
4460      getRegionFileSystem().removeStoreFiles(fakeFamilyName, fakeStoreFiles);
4461    } else {
4462      for (Path file: files) {
4463        if (!fs.delete(file, false)) {
4464          LOG.error("Failed delete of " + file);
4465        } else {
4466          LOG.debug("Deleted recovered.edits file=" + file);
4467        }
4468      }
4469    }
4470    return seqid;
4471  }
4472
4473  /*
4474   * @param edits File of recovered edits.
4475   * @param maxSeqIdInStores Maximum sequenceid found in each store.  Edits in wal
4476   * must be larger than this to be replayed for each store.
4477   * @param reporter
4478   * @return the sequence id of the last edit added to this region out of the
4479   * recovered edits log or <code>minSeqId</code> if nothing added from editlogs.
4480   * @throws IOException
4481   */
4482  private long replayRecoveredEdits(final Path edits,
4483      Map<byte[], Long> maxSeqIdInStores, final CancelableProgressable reporter)
4484    throws IOException {
4485    String msg = "Replaying edits from " + edits;
4486    LOG.info(msg);
4487    MonitoredTask status = TaskMonitor.get().createStatus(msg);
4488    FileSystem fs = this.fs.getFileSystem();
4489
4490    status.setStatus("Opening recovered edits");
4491    WAL.Reader reader = null;
4492    try {
4493      reader = WALFactory.createReader(fs, edits, conf);
4494      long currentEditSeqId = -1;
4495      long currentReplaySeqId = -1;
4496      long firstSeqIdInLog = -1;
4497      long skippedEdits = 0;
4498      long editsCount = 0;
4499      long intervalEdits = 0;
4500      WAL.Entry entry;
4501      HStore store = null;
4502      boolean reported_once = false;
4503      ServerNonceManager ng = this.rsServices == null ? null : this.rsServices.getNonceManager();
4504
4505      try {
4506        // How many edits seen before we check elapsed time
4507        int interval = this.conf.getInt("hbase.hstore.report.interval.edits", 2000);
4508        // How often to send a progress report (default 1/2 master timeout)
4509        int period = this.conf.getInt("hbase.hstore.report.period", 300000);
4510        long lastReport = EnvironmentEdgeManager.currentTime();
4511
4512        if (coprocessorHost != null) {
4513          coprocessorHost.preReplayWALs(this.getRegionInfo(), edits);
4514        }
4515
4516        while ((entry = reader.next()) != null) {
4517          WALKey key = entry.getKey();
4518          WALEdit val = entry.getEdit();
4519
4520          if (ng != null) { // some test, or nonces disabled
4521            ng.reportOperationFromWal(key.getNonceGroup(), key.getNonce(), key.getWriteTime());
4522          }
4523
4524          if (reporter != null) {
4525            intervalEdits += val.size();
4526            if (intervalEdits >= interval) {
4527              // Number of edits interval reached
4528              intervalEdits = 0;
4529              long cur = EnvironmentEdgeManager.currentTime();
4530              if (lastReport + period <= cur) {
4531                status.setStatus("Replaying edits..." +
4532                    " skipped=" + skippedEdits +
4533                    " edits=" + editsCount);
4534                // Timeout reached
4535                if(!reporter.progress()) {
4536                  msg = "Progressable reporter failed, stopping replay";
4537                  LOG.warn(msg);
4538                  status.abort(msg);
4539                  throw new IOException(msg);
4540                }
4541                reported_once = true;
4542                lastReport = cur;
4543              }
4544            }
4545          }
4546
4547          if (firstSeqIdInLog == -1) {
4548            firstSeqIdInLog = key.getSequenceId();
4549          }
4550          if (currentEditSeqId > key.getSequenceId()) {
4551            // when this condition is true, it means we have a serious defect because we need to
4552            // maintain increasing SeqId for WAL edits per region
4553            LOG.error(getRegionInfo().getEncodedName() + " : "
4554                 + "Found decreasing SeqId. PreId=" + currentEditSeqId + " key=" + key
4555                + "; edit=" + val);
4556          } else {
4557            currentEditSeqId = key.getSequenceId();
4558          }
4559          currentReplaySeqId = (key.getOrigLogSeqNum() > 0) ?
4560            key.getOrigLogSeqNum() : currentEditSeqId;
4561
4562          // Start coprocessor replay here. The coprocessor is for each WALEdit
4563          // instead of a KeyValue.
4564          if (coprocessorHost != null) {
4565            status.setStatus("Running pre-WAL-restore hook in coprocessors");
4566            if (coprocessorHost.preWALRestore(this.getRegionInfo(), key, val)) {
4567              // if bypass this wal entry, ignore it ...
4568              continue;
4569            }
4570          }
4571          boolean checkRowWithinBoundary = false;
4572          // Check this edit is for this region.
4573          if (!Bytes.equals(key.getEncodedRegionName(),
4574              this.getRegionInfo().getEncodedNameAsBytes())) {
4575            checkRowWithinBoundary = true;
4576          }
4577
4578          boolean flush = false;
4579          MemStoreSizing memStoreSizing = new NonThreadSafeMemStoreSizing();
4580          for (Cell cell: val.getCells()) {
4581            // Check this edit is for me. Also, guard against writing the special
4582            // METACOLUMN info such as HBASE::CACHEFLUSH entries
4583            if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) {
4584              // if region names don't match, skipp replaying compaction marker
4585              if (!checkRowWithinBoundary) {
4586                //this is a special edit, we should handle it
4587                CompactionDescriptor compaction = WALEdit.getCompaction(cell);
4588                if (compaction != null) {
4589                  //replay the compaction
4590                  replayWALCompactionMarker(compaction, false, true, Long.MAX_VALUE);
4591                }
4592              }
4593              skippedEdits++;
4594              continue;
4595            }
4596            // Figure which store the edit is meant for.
4597            if (store == null || !CellUtil.matchingFamily(cell,
4598                store.getColumnFamilyDescriptor().getName())) {
4599              store = getStore(cell);
4600            }
4601            if (store == null) {
4602              // This should never happen.  Perhaps schema was changed between
4603              // crash and redeploy?
4604              LOG.warn("No family for " + cell);
4605              skippedEdits++;
4606              continue;
4607            }
4608            if (checkRowWithinBoundary && !rowIsInRange(this.getRegionInfo(),
4609              cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())) {
4610              LOG.warn("Row of " + cell + " is not within region boundary");
4611              skippedEdits++;
4612              continue;
4613            }
4614            // Now, figure if we should skip this edit.
4615            if (key.getSequenceId() <= maxSeqIdInStores.get(store.getColumnFamilyDescriptor()
4616                .getName())) {
4617              skippedEdits++;
4618              continue;
4619            }
4620            PrivateCellUtil.setSequenceId(cell, currentReplaySeqId);
4621
4622            restoreEdit(store, cell, memStoreSizing);
4623            editsCount++;
4624          }
4625          MemStoreSize mss = memStoreSizing.getMemStoreSize();
4626          incMemStoreSize(mss);
4627          flush = isFlushSize(this.memStoreSizing.getMemStoreSize());
4628          if (flush) {
4629            internalFlushcache(null, currentEditSeqId, stores.values(), status, false,
4630              FlushLifeCycleTracker.DUMMY);
4631          }
4632
4633          if (coprocessorHost != null) {
4634            coprocessorHost.postWALRestore(this.getRegionInfo(), key, val);
4635          }
4636        }
4637
4638        if (coprocessorHost != null) {
4639          coprocessorHost.postReplayWALs(this.getRegionInfo(), edits);
4640        }
4641      } catch (EOFException eof) {
4642        Path p = WALSplitter.moveAsideBadEditsFile(fs, edits);
4643        msg = "EnLongAddered EOF. Most likely due to Master failure during " +
4644            "wal splitting, so we have this data in another edit.  " +
4645            "Continuing, but renaming " + edits + " as " + p;
4646        LOG.warn(msg, eof);
4647        status.abort(msg);
4648      } catch (IOException ioe) {
4649        // If the IOE resulted from bad file format,
4650        // then this problem is idempotent and retrying won't help
4651        if (ioe.getCause() instanceof ParseException) {
4652          Path p = WALSplitter.moveAsideBadEditsFile(fs, edits);
4653          msg = "File corruption enLongAddered!  " +
4654              "Continuing, but renaming " + edits + " as " + p;
4655          LOG.warn(msg, ioe);
4656          status.setStatus(msg);
4657        } else {
4658          status.abort(StringUtils.stringifyException(ioe));
4659          // other IO errors may be transient (bad network connection,
4660          // checksum exception on one datanode, etc).  throw & retry
4661          throw ioe;
4662        }
4663      }
4664      if (reporter != null && !reported_once) {
4665        reporter.progress();
4666      }
4667      msg = "Applied " + editsCount + ", skipped " + skippedEdits +
4668        ", firstSequenceIdInLog=" + firstSeqIdInLog +
4669        ", maxSequenceIdInLog=" + currentEditSeqId + ", path=" + edits;
4670      status.markComplete(msg);
4671      LOG.debug(msg);
4672      return currentEditSeqId;
4673    } finally {
4674      status.cleanup();
4675      if (reader != null) {
4676         reader.close();
4677      }
4678    }
4679  }
4680
4681  /**
4682   * Call to complete a compaction. Its for the case where we find in the WAL a compaction
4683   * that was not finished.  We could find one recovering a WAL after a regionserver crash.
4684   * See HBASE-2331.
4685   */
4686  void replayWALCompactionMarker(CompactionDescriptor compaction, boolean pickCompactionFiles,
4687      boolean removeFiles, long replaySeqId)
4688      throws IOException {
4689    try {
4690      checkTargetRegion(compaction.getEncodedRegionName().toByteArray(),
4691        "Compaction marker from WAL ", compaction);
4692    } catch (WrongRegionException wre) {
4693      if (RegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) {
4694        // skip the compaction marker since it is not for this region
4695        return;
4696      }
4697      throw wre;
4698    }
4699
4700    synchronized (writestate) {
4701      if (replaySeqId < lastReplayedOpenRegionSeqId) {
4702        LOG.warn(getRegionInfo().getEncodedName() + " : "
4703            + "Skipping replaying compaction event :" + TextFormat.shortDebugString(compaction)
4704            + " because its sequence id " + replaySeqId + " is smaller than this regions "
4705            + "lastReplayedOpenRegionSeqId of " + lastReplayedOpenRegionSeqId);
4706        return;
4707      }
4708      if (replaySeqId < lastReplayedCompactionSeqId) {
4709        LOG.warn(getRegionInfo().getEncodedName() + " : "
4710            + "Skipping replaying compaction event :" + TextFormat.shortDebugString(compaction)
4711            + " because its sequence id " + replaySeqId + " is smaller than this regions "
4712            + "lastReplayedCompactionSeqId of " + lastReplayedCompactionSeqId);
4713        return;
4714      } else {
4715        lastReplayedCompactionSeqId = replaySeqId;
4716      }
4717
4718      if (LOG.isDebugEnabled()) {
4719        LOG.debug(getRegionInfo().getEncodedName() + " : "
4720            + "Replaying compaction marker " + TextFormat.shortDebugString(compaction)
4721            + " with seqId=" + replaySeqId + " and lastReplayedOpenRegionSeqId="
4722            + lastReplayedOpenRegionSeqId);
4723      }
4724
4725      startRegionOperation(Operation.REPLAY_EVENT);
4726      try {
4727        HStore store = this.getStore(compaction.getFamilyName().toByteArray());
4728        if (store == null) {
4729          LOG.warn(getRegionInfo().getEncodedName() + " : "
4730              + "Found Compaction WAL edit for deleted family:"
4731              + Bytes.toString(compaction.getFamilyName().toByteArray()));
4732          return;
4733        }
4734        store.replayCompactionMarker(compaction, pickCompactionFiles, removeFiles);
4735        logRegionFiles();
4736      } catch (FileNotFoundException ex) {
4737        LOG.warn(getRegionInfo().getEncodedName() + " : "
4738            + "At least one of the store files in compaction: "
4739            + TextFormat.shortDebugString(compaction)
4740            + " doesn't exist any more. Skip loading the file(s)", ex);
4741      } finally {
4742        closeRegionOperation(Operation.REPLAY_EVENT);
4743      }
4744    }
4745  }
4746
4747  void replayWALFlushMarker(FlushDescriptor flush, long replaySeqId) throws IOException {
4748    checkTargetRegion(flush.getEncodedRegionName().toByteArray(),
4749      "Flush marker from WAL ", flush);
4750
4751    if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) {
4752      return; // if primary nothing to do
4753    }
4754
4755    if (LOG.isDebugEnabled()) {
4756      LOG.debug(getRegionInfo().getEncodedName() + " : "
4757          + "Replaying flush marker " + TextFormat.shortDebugString(flush));
4758    }
4759
4760    startRegionOperation(Operation.REPLAY_EVENT); // use region close lock to guard against close
4761    try {
4762      FlushAction action = flush.getAction();
4763      switch (action) {
4764      case START_FLUSH:
4765        replayWALFlushStartMarker(flush);
4766        break;
4767      case COMMIT_FLUSH:
4768        replayWALFlushCommitMarker(flush);
4769        break;
4770      case ABORT_FLUSH:
4771        replayWALFlushAbortMarker(flush);
4772        break;
4773      case CANNOT_FLUSH:
4774        replayWALFlushCannotFlushMarker(flush, replaySeqId);
4775        break;
4776      default:
4777        LOG.warn(getRegionInfo().getEncodedName() + " : " +
4778          "Received a flush event with unknown action, ignoring. " +
4779          TextFormat.shortDebugString(flush));
4780        break;
4781      }
4782
4783      logRegionFiles();
4784    } finally {
4785      closeRegionOperation(Operation.REPLAY_EVENT);
4786    }
4787  }
4788
4789  /** Replay the flush marker from primary region by creating a corresponding snapshot of
4790   * the store memstores, only if the memstores do not have a higher seqId from an earlier wal
4791   * edit (because the events may be coming out of order).
4792   */
4793  @VisibleForTesting
4794  PrepareFlushResult replayWALFlushStartMarker(FlushDescriptor flush) throws IOException {
4795    long flushSeqId = flush.getFlushSequenceNumber();
4796
4797    HashSet<HStore> storesToFlush = new HashSet<>();
4798    for (StoreFlushDescriptor storeFlush : flush.getStoreFlushesList()) {
4799      byte[] family = storeFlush.getFamilyName().toByteArray();
4800      HStore store = getStore(family);
4801      if (store == null) {
4802        LOG.warn(getRegionInfo().getEncodedName() + " : "
4803          + "Received a flush start marker from primary, but the family is not found. Ignoring"
4804          + " StoreFlushDescriptor:" + TextFormat.shortDebugString(storeFlush));
4805        continue;
4806      }
4807      storesToFlush.add(store);
4808    }
4809
4810    MonitoredTask status = TaskMonitor.get().createStatus("Preparing flush " + this);
4811
4812    // we will use writestate as a coarse-grain lock for all the replay events
4813    // (flush, compaction, region open etc)
4814    synchronized (writestate) {
4815      try {
4816        if (flush.getFlushSequenceNumber() < lastReplayedOpenRegionSeqId) {
4817          LOG.warn(getRegionInfo().getEncodedName() + " : "
4818              + "Skipping replaying flush event :" + TextFormat.shortDebugString(flush)
4819              + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId "
4820              + " of " + lastReplayedOpenRegionSeqId);
4821          return null;
4822        }
4823        if (numMutationsWithoutWAL.sum() > 0) {
4824          numMutationsWithoutWAL.reset();
4825          dataInMemoryWithoutWAL.reset();
4826        }
4827
4828        if (!writestate.flushing) {
4829          // we do not have an active snapshot and corresponding this.prepareResult. This means
4830          // we can just snapshot our memstores and continue as normal.
4831
4832          // invoke prepareFlushCache. Send null as wal since we do not want the flush events in wal
4833          PrepareFlushResult prepareResult = internalPrepareFlushCache(null, flushSeqId,
4834            storesToFlush, status, false, FlushLifeCycleTracker.DUMMY);
4835          if (prepareResult.result == null) {
4836            // save the PrepareFlushResult so that we can use it later from commit flush
4837            this.writestate.flushing = true;
4838            this.prepareFlushResult = prepareResult;
4839            status.markComplete("Flush prepare successful");
4840            if (LOG.isDebugEnabled()) {
4841              LOG.debug(getRegionInfo().getEncodedName() + " : "
4842                  + " Prepared flush with seqId:" + flush.getFlushSequenceNumber());
4843            }
4844          } else {
4845            // special case empty memstore. We will still save the flush result in this case, since
4846            // our memstore ie empty, but the primary is still flushing
4847            if (prepareResult.getResult().getResult() ==
4848                  FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY) {
4849              this.writestate.flushing = true;
4850              this.prepareFlushResult = prepareResult;
4851              if (LOG.isDebugEnabled()) {
4852                LOG.debug(getRegionInfo().getEncodedName() + " : "
4853                  + " Prepared empty flush with seqId:" + flush.getFlushSequenceNumber());
4854              }
4855            }
4856            status.abort("Flush prepare failed with " + prepareResult.result);
4857            // nothing much to do. prepare flush failed because of some reason.
4858          }
4859          return prepareResult;
4860        } else {
4861          // we already have an active snapshot.
4862          if (flush.getFlushSequenceNumber() == this.prepareFlushResult.flushOpSeqId) {
4863            // They define the same flush. Log and continue.
4864            LOG.warn(getRegionInfo().getEncodedName() + " : "
4865                + "Received a flush prepare marker with the same seqId: " +
4866                + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: "
4867                + prepareFlushResult.flushOpSeqId + ". Ignoring");
4868            // ignore
4869          } else if (flush.getFlushSequenceNumber() < this.prepareFlushResult.flushOpSeqId) {
4870            // We received a flush with a smaller seqNum than what we have prepared. We can only
4871            // ignore this prepare flush request.
4872            LOG.warn(getRegionInfo().getEncodedName() + " : "
4873                + "Received a flush prepare marker with a smaller seqId: " +
4874                + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: "
4875                + prepareFlushResult.flushOpSeqId + ". Ignoring");
4876            // ignore
4877          } else {
4878            // We received a flush with a larger seqNum than what we have prepared
4879            LOG.warn(getRegionInfo().getEncodedName() + " : "
4880                + "Received a flush prepare marker with a larger seqId: " +
4881                + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: "
4882                + prepareFlushResult.flushOpSeqId + ". Ignoring");
4883            // We do not have multiple active snapshots in the memstore or a way to merge current
4884            // memstore snapshot with the contents and resnapshot for now. We cannot take
4885            // another snapshot and drop the previous one because that will cause temporary
4886            // data loss in the secondary. So we ignore this for now, deferring the resolution
4887            // to happen when we see the corresponding flush commit marker. If we have a memstore
4888            // snapshot with x, and later received another prepare snapshot with y (where x < y),
4889            // when we see flush commit for y, we will drop snapshot for x, and can also drop all
4890            // the memstore edits if everything in memstore is < y. This is the usual case for
4891            // RS crash + recovery where we might see consequtive prepare flush wal markers.
4892            // Otherwise, this will cause more memory to be used in secondary replica until a
4893            // further prapare + commit flush is seen and replayed.
4894          }
4895        }
4896      } finally {
4897        status.cleanup();
4898        writestate.notifyAll();
4899      }
4900    }
4901    return null;
4902  }
4903
4904  @VisibleForTesting
4905  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY",
4906    justification="Intentional; post memstore flush")
4907  void replayWALFlushCommitMarker(FlushDescriptor flush) throws IOException {
4908    MonitoredTask status = TaskMonitor.get().createStatus("Committing flush " + this);
4909
4910    // check whether we have the memstore snapshot with the corresponding seqId. Replay to
4911    // secondary region replicas are in order, except for when the region moves or then the
4912    // region server crashes. In those cases, we may receive replay requests out of order from
4913    // the original seqIds.
4914    synchronized (writestate) {
4915      try {
4916        if (flush.getFlushSequenceNumber() < lastReplayedOpenRegionSeqId) {
4917          LOG.warn(getRegionInfo().getEncodedName() + " : "
4918            + "Skipping replaying flush event :" + TextFormat.shortDebugString(flush)
4919            + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId "
4920            + " of " + lastReplayedOpenRegionSeqId);
4921          return;
4922        }
4923
4924        if (writestate.flushing) {
4925          PrepareFlushResult prepareFlushResult = this.prepareFlushResult;
4926          if (flush.getFlushSequenceNumber() == prepareFlushResult.flushOpSeqId) {
4927            if (LOG.isDebugEnabled()) {
4928              LOG.debug(getRegionInfo().getEncodedName() + " : "
4929                  + "Received a flush commit marker with seqId:" + flush.getFlushSequenceNumber()
4930                  + " and a previous prepared snapshot was found");
4931            }
4932            // This is the regular case where we received commit flush after prepare flush
4933            // corresponding to the same seqId.
4934            replayFlushInStores(flush, prepareFlushResult, true);
4935
4936            // Set down the memstore size by amount of flush.
4937            this.decrMemStoreSize(prepareFlushResult.totalFlushableSize.getMemStoreSize());
4938            this.prepareFlushResult = null;
4939            writestate.flushing = false;
4940          } else if (flush.getFlushSequenceNumber() < prepareFlushResult.flushOpSeqId) {
4941            // This should not happen normally. However, lets be safe and guard against these cases
4942            // we received a flush commit with a smaller seqId than what we have prepared
4943            // we will pick the flush file up from this commit (if we have not seen it), but we
4944            // will not drop the memstore
4945            LOG.warn(getRegionInfo().getEncodedName() + " : "
4946                + "Received a flush commit marker with smaller seqId: "
4947                + flush.getFlushSequenceNumber() + " than what we have prepared with seqId: "
4948                + prepareFlushResult.flushOpSeqId + ". Picking up new file, but not dropping"
4949                +"  prepared memstore snapshot");
4950            replayFlushInStores(flush, prepareFlushResult, false);
4951
4952            // snapshot is not dropped, so memstore sizes should not be decremented
4953            // we still have the prepared snapshot, flushing should still be true
4954          } else {
4955            // This should not happen normally. However, lets be safe and guard against these cases
4956            // we received a flush commit with a larger seqId than what we have prepared
4957            // we will pick the flush file for this. We will also obtain the updates lock and
4958            // look for contents of the memstore to see whether we have edits after this seqId.
4959            // If not, we will drop all the memstore edits and the snapshot as well.
4960            LOG.warn(getRegionInfo().getEncodedName() + " : "
4961                + "Received a flush commit marker with larger seqId: "
4962                + flush.getFlushSequenceNumber() + " than what we have prepared with seqId: " +
4963                prepareFlushResult.flushOpSeqId + ". Picking up new file and dropping prepared"
4964                +" memstore snapshot");
4965
4966            replayFlushInStores(flush, prepareFlushResult, true);
4967
4968            // Set down the memstore size by amount of flush.
4969            this.decrMemStoreSize(prepareFlushResult.totalFlushableSize.getMemStoreSize());
4970
4971            // Inspect the memstore contents to see whether the memstore contains only edits
4972            // with seqId smaller than the flush seqId. If so, we can discard those edits.
4973            dropMemStoreContentsForSeqId(flush.getFlushSequenceNumber(), null);
4974
4975            this.prepareFlushResult = null;
4976            writestate.flushing = false;
4977          }
4978          // If we were waiting for observing a flush or region opening event for not showing
4979          // partial data after a secondary region crash, we can allow reads now. We can only make
4980          // sure that we are not showing partial data (for example skipping some previous edits)
4981          // until we observe a full flush start and flush commit. So if we were not able to find
4982          // a previous flush we will not enable reads now.
4983          this.setReadsEnabled(true);
4984        } else {
4985          LOG.warn(getRegionInfo().getEncodedName() + " : "
4986              + "Received a flush commit marker with seqId:" + flush.getFlushSequenceNumber()
4987              + ", but no previous prepared snapshot was found");
4988          // There is no corresponding prepare snapshot from before.
4989          // We will pick up the new flushed file
4990          replayFlushInStores(flush, null, false);
4991
4992          // Inspect the memstore contents to see whether the memstore contains only edits
4993          // with seqId smaller than the flush seqId. If so, we can discard those edits.
4994          dropMemStoreContentsForSeqId(flush.getFlushSequenceNumber(), null);
4995        }
4996
4997        status.markComplete("Flush commit successful");
4998
4999        // Update the last flushed sequence id for region.
5000        this.maxFlushedSeqId = flush.getFlushSequenceNumber();
5001
5002        // advance the mvcc read point so that the new flushed file is visible.
5003        mvcc.advanceTo(flush.getFlushSequenceNumber());
5004
5005      } catch (FileNotFoundException ex) {
5006        LOG.warn(getRegionInfo().getEncodedName() + " : "
5007            + "At least one of the store files in flush: " + TextFormat.shortDebugString(flush)
5008            + " doesn't exist any more. Skip loading the file(s)", ex);
5009      }
5010      finally {
5011        status.cleanup();
5012        writestate.notifyAll();
5013      }
5014    }
5015
5016    // C. Finally notify anyone waiting on memstore to clear:
5017    // e.g. checkResources().
5018    synchronized (this) {
5019      notifyAll(); // FindBugs NN_NAKED_NOTIFY
5020    }
5021  }
5022
5023  /**
5024   * Replays the given flush descriptor by opening the flush files in stores and dropping the
5025   * memstore snapshots if requested.
5026   * @param flush
5027   * @param prepareFlushResult
5028   * @param dropMemstoreSnapshot
5029   * @throws IOException
5030   */
5031  private void replayFlushInStores(FlushDescriptor flush, PrepareFlushResult prepareFlushResult,
5032      boolean dropMemstoreSnapshot)
5033      throws IOException {
5034    for (StoreFlushDescriptor storeFlush : flush.getStoreFlushesList()) {
5035      byte[] family = storeFlush.getFamilyName().toByteArray();
5036      HStore store = getStore(family);
5037      if (store == null) {
5038        LOG.warn(getRegionInfo().getEncodedName() + " : "
5039            + "Received a flush commit marker from primary, but the family is not found."
5040            + "Ignoring StoreFlushDescriptor:" + storeFlush);
5041        continue;
5042      }
5043      List<String> flushFiles = storeFlush.getFlushOutputList();
5044      StoreFlushContext ctx = null;
5045      long startTime = EnvironmentEdgeManager.currentTime();
5046      if (prepareFlushResult == null || prepareFlushResult.storeFlushCtxs == null) {
5047        ctx = store.createFlushContext(flush.getFlushSequenceNumber(), FlushLifeCycleTracker.DUMMY);
5048      } else {
5049        ctx = prepareFlushResult.storeFlushCtxs.get(family);
5050        startTime = prepareFlushResult.startTime;
5051      }
5052
5053      if (ctx == null) {
5054        LOG.warn(getRegionInfo().getEncodedName() + " : "
5055            + "Unexpected: flush commit marker received from store "
5056            + Bytes.toString(family) + " but no associated flush context. Ignoring");
5057        continue;
5058      }
5059
5060      ctx.replayFlush(flushFiles, dropMemstoreSnapshot); // replay the flush
5061
5062      // Record latest flush time
5063      this.lastStoreFlushTimeMap.put(store, startTime);
5064    }
5065  }
5066
5067  /**
5068   * Be careful, this method will drop all data in the memstore of this region.
5069   * Currently, this method is used to drop memstore to prevent memory leak
5070   * when replaying recovered.edits while opening region.
5071   */
5072  public MemStoreSize dropMemStoreContents() throws IOException {
5073    MemStoreSizing totalFreedSize = new NonThreadSafeMemStoreSizing();
5074    this.updatesLock.writeLock().lock();
5075    try {
5076      for (HStore s : stores.values()) {
5077        MemStoreSize memStoreSize = doDropStoreMemStoreContentsForSeqId(s, HConstants.NO_SEQNUM);
5078        LOG.info("Drop memstore for Store " + s.getColumnFamilyName() + " in region "
5079                + this.getRegionInfo().getRegionNameAsString()
5080                + " , dropped memstoresize: [" + memStoreSize + " }");
5081        totalFreedSize.incMemStoreSize(memStoreSize);
5082      }
5083      return totalFreedSize.getMemStoreSize();
5084    } finally {
5085      this.updatesLock.writeLock().unlock();
5086    }
5087  }
5088
5089  /**
5090   * Drops the memstore contents after replaying a flush descriptor or region open event replay
5091   * if the memstore edits have seqNums smaller than the given seq id
5092   * @throws IOException
5093   */
5094  private MemStoreSize dropMemStoreContentsForSeqId(long seqId, HStore store) throws IOException {
5095    MemStoreSizing totalFreedSize = new NonThreadSafeMemStoreSizing();
5096    this.updatesLock.writeLock().lock();
5097    try {
5098
5099      long currentSeqId = mvcc.getReadPoint();
5100      if (seqId >= currentSeqId) {
5101        // then we can drop the memstore contents since everything is below this seqId
5102        LOG.info(getRegionInfo().getEncodedName() + " : "
5103            + "Dropping memstore contents as well since replayed flush seqId: "
5104            + seqId + " is greater than current seqId:" + currentSeqId);
5105
5106        // Prepare flush (take a snapshot) and then abort (drop the snapshot)
5107        if (store == null) {
5108          for (HStore s : stores.values()) {
5109            totalFreedSize.incMemStoreSize(doDropStoreMemStoreContentsForSeqId(s, currentSeqId));
5110          }
5111        } else {
5112          totalFreedSize.incMemStoreSize(doDropStoreMemStoreContentsForSeqId(store, currentSeqId));
5113        }
5114      } else {
5115        LOG.info(getRegionInfo().getEncodedName() + " : "
5116            + "Not dropping memstore contents since replayed flush seqId: "
5117            + seqId + " is smaller than current seqId:" + currentSeqId);
5118      }
5119    } finally {
5120      this.updatesLock.writeLock().unlock();
5121    }
5122    return totalFreedSize.getMemStoreSize();
5123  }
5124
5125  private MemStoreSize doDropStoreMemStoreContentsForSeqId(HStore s, long currentSeqId)
5126      throws IOException {
5127    MemStoreSize flushableSize = s.getFlushableSize();
5128    this.decrMemStoreSize(flushableSize);
5129    StoreFlushContext ctx = s.createFlushContext(currentSeqId, FlushLifeCycleTracker.DUMMY);
5130    ctx.prepare();
5131    ctx.abort();
5132    return flushableSize;
5133  }
5134
5135  private void replayWALFlushAbortMarker(FlushDescriptor flush) {
5136    // nothing to do for now. A flush abort will cause a RS abort which means that the region
5137    // will be opened somewhere else later. We will see the region open event soon, and replaying
5138    // that will drop the snapshot
5139  }
5140
5141  private void replayWALFlushCannotFlushMarker(FlushDescriptor flush, long replaySeqId) {
5142    synchronized (writestate) {
5143      if (this.lastReplayedOpenRegionSeqId > replaySeqId) {
5144        LOG.warn(getRegionInfo().getEncodedName() + " : "
5145          + "Skipping replaying flush event :" + TextFormat.shortDebugString(flush)
5146          + " because its sequence id " + replaySeqId + " is smaller than this regions "
5147          + "lastReplayedOpenRegionSeqId of " + lastReplayedOpenRegionSeqId);
5148        return;
5149      }
5150
5151      // If we were waiting for observing a flush or region opening event for not showing partial
5152      // data after a secondary region crash, we can allow reads now. This event means that the
5153      // primary was not able to flush because memstore is empty when we requested flush. By the
5154      // time we observe this, we are guaranteed to have up to date seqId with our previous
5155      // assignment.
5156      this.setReadsEnabled(true);
5157    }
5158  }
5159
5160  @VisibleForTesting
5161  PrepareFlushResult getPrepareFlushResult() {
5162    return prepareFlushResult;
5163  }
5164
5165  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY",
5166      justification="Intentional; cleared the memstore")
5167  void replayWALRegionEventMarker(RegionEventDescriptor regionEvent) throws IOException {
5168    checkTargetRegion(regionEvent.getEncodedRegionName().toByteArray(),
5169      "RegionEvent marker from WAL ", regionEvent);
5170
5171    startRegionOperation(Operation.REPLAY_EVENT);
5172    try {
5173      if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) {
5174        return; // if primary nothing to do
5175      }
5176
5177      if (regionEvent.getEventType() == EventType.REGION_CLOSE) {
5178        // nothing to do on REGION_CLOSE for now.
5179        return;
5180      }
5181      if (regionEvent.getEventType() != EventType.REGION_OPEN) {
5182        LOG.warn(getRegionInfo().getEncodedName() + " : "
5183            + "Unknown region event received, ignoring :"
5184            + TextFormat.shortDebugString(regionEvent));
5185        return;
5186      }
5187
5188      if (LOG.isDebugEnabled()) {
5189        LOG.debug(getRegionInfo().getEncodedName() + " : "
5190          + "Replaying region open event marker " + TextFormat.shortDebugString(regionEvent));
5191      }
5192
5193      // we will use writestate as a coarse-grain lock for all the replay events
5194      synchronized (writestate) {
5195        // Replication can deliver events out of order when primary region moves or the region
5196        // server crashes, since there is no coordination between replication of different wal files
5197        // belonging to different region servers. We have to safe guard against this case by using
5198        // region open event's seqid. Since this is the first event that the region puts (after
5199        // possibly flushing recovered.edits), after seeing this event, we can ignore every edit
5200        // smaller than this seqId
5201        if (this.lastReplayedOpenRegionSeqId <= regionEvent.getLogSequenceNumber()) {
5202          this.lastReplayedOpenRegionSeqId = regionEvent.getLogSequenceNumber();
5203        } else {
5204          LOG.warn(getRegionInfo().getEncodedName() + " : "
5205            + "Skipping replaying region event :" + TextFormat.shortDebugString(regionEvent)
5206            + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId "
5207            + " of " + lastReplayedOpenRegionSeqId);
5208          return;
5209        }
5210
5211        // region open lists all the files that the region has at the time of the opening. Just pick
5212        // all the files and drop prepared flushes and empty memstores
5213        for (StoreDescriptor storeDescriptor : regionEvent.getStoresList()) {
5214          // stores of primary may be different now
5215          byte[] family = storeDescriptor.getFamilyName().toByteArray();
5216          HStore store = getStore(family);
5217          if (store == null) {
5218            LOG.warn(getRegionInfo().getEncodedName() + " : "
5219                + "Received a region open marker from primary, but the family is not found. "
5220                + "Ignoring. StoreDescriptor:" + storeDescriptor);
5221            continue;
5222          }
5223
5224          long storeSeqId = store.getMaxSequenceId().orElse(0L);
5225          List<String> storeFiles = storeDescriptor.getStoreFileList();
5226          try {
5227            store.refreshStoreFiles(storeFiles); // replace the files with the new ones
5228          } catch (FileNotFoundException ex) {
5229            LOG.warn(getRegionInfo().getEncodedName() + " : "
5230                    + "At least one of the store files: " + storeFiles
5231                    + " doesn't exist any more. Skip loading the file(s)", ex);
5232            continue;
5233          }
5234          if (store.getMaxSequenceId().orElse(0L) != storeSeqId) {
5235            // Record latest flush time if we picked up new files
5236            lastStoreFlushTimeMap.put(store, EnvironmentEdgeManager.currentTime());
5237          }
5238
5239          if (writestate.flushing) {
5240            // only drop memstore snapshots if they are smaller than last flush for the store
5241            if (this.prepareFlushResult.flushOpSeqId <= regionEvent.getLogSequenceNumber()) {
5242              StoreFlushContext ctx = this.prepareFlushResult.storeFlushCtxs == null ?
5243                  null : this.prepareFlushResult.storeFlushCtxs.get(family);
5244              if (ctx != null) {
5245                MemStoreSize mss = store.getFlushableSize();
5246                ctx.abort();
5247                this.decrMemStoreSize(mss);
5248                this.prepareFlushResult.storeFlushCtxs.remove(family);
5249              }
5250            }
5251          }
5252
5253          // Drop the memstore contents if they are now smaller than the latest seen flushed file
5254          dropMemStoreContentsForSeqId(regionEvent.getLogSequenceNumber(), store);
5255          if (storeSeqId > this.maxFlushedSeqId) {
5256            this.maxFlushedSeqId = storeSeqId;
5257          }
5258        }
5259
5260        // if all stores ended up dropping their snapshots, we can safely drop the
5261        // prepareFlushResult
5262        dropPrepareFlushIfPossible();
5263
5264        // advance the mvcc read point so that the new flushed file is visible.
5265        mvcc.await();
5266
5267        // If we were waiting for observing a flush or region opening event for not showing partial
5268        // data after a secondary region crash, we can allow reads now.
5269        this.setReadsEnabled(true);
5270
5271        // C. Finally notify anyone waiting on memstore to clear:
5272        // e.g. checkResources().
5273        synchronized (this) {
5274          notifyAll(); // FindBugs NN_NAKED_NOTIFY
5275        }
5276      }
5277      logRegionFiles();
5278    } finally {
5279      closeRegionOperation(Operation.REPLAY_EVENT);
5280    }
5281  }
5282
5283  void replayWALBulkLoadEventMarker(WALProtos.BulkLoadDescriptor bulkLoadEvent) throws IOException {
5284    checkTargetRegion(bulkLoadEvent.getEncodedRegionName().toByteArray(),
5285      "BulkLoad marker from WAL ", bulkLoadEvent);
5286
5287    if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) {
5288      return; // if primary nothing to do
5289    }
5290
5291    if (LOG.isDebugEnabled()) {
5292      LOG.debug(getRegionInfo().getEncodedName() + " : "
5293              +  "Replaying bulkload event marker " + TextFormat.shortDebugString(bulkLoadEvent));
5294    }
5295    // check if multiple families involved
5296    boolean multipleFamilies = false;
5297    byte[] family = null;
5298    for (StoreDescriptor storeDescriptor : bulkLoadEvent.getStoresList()) {
5299      byte[] fam = storeDescriptor.getFamilyName().toByteArray();
5300      if (family == null) {
5301        family = fam;
5302      } else if (!Bytes.equals(family, fam)) {
5303        multipleFamilies = true;
5304        break;
5305      }
5306    }
5307
5308    startBulkRegionOperation(multipleFamilies);
5309    try {
5310      // we will use writestate as a coarse-grain lock for all the replay events
5311      synchronized (writestate) {
5312        // Replication can deliver events out of order when primary region moves or the region
5313        // server crashes, since there is no coordination between replication of different wal files
5314        // belonging to different region servers. We have to safe guard against this case by using
5315        // region open event's seqid. Since this is the first event that the region puts (after
5316        // possibly flushing recovered.edits), after seeing this event, we can ignore every edit
5317        // smaller than this seqId
5318        if (bulkLoadEvent.getBulkloadSeqNum() >= 0
5319            && this.lastReplayedOpenRegionSeqId >= bulkLoadEvent.getBulkloadSeqNum()) {
5320          LOG.warn(getRegionInfo().getEncodedName() + " : "
5321              + "Skipping replaying bulkload event :"
5322              + TextFormat.shortDebugString(bulkLoadEvent)
5323              + " because its sequence id is smaller than this region's lastReplayedOpenRegionSeqId"
5324              + " =" + lastReplayedOpenRegionSeqId);
5325
5326          return;
5327        }
5328
5329        for (StoreDescriptor storeDescriptor : bulkLoadEvent.getStoresList()) {
5330          // stores of primary may be different now
5331          family = storeDescriptor.getFamilyName().toByteArray();
5332          HStore store = getStore(family);
5333          if (store == null) {
5334            LOG.warn(getRegionInfo().getEncodedName() + " : "
5335                    + "Received a bulk load marker from primary, but the family is not found. "
5336                    + "Ignoring. StoreDescriptor:" + storeDescriptor);
5337            continue;
5338          }
5339
5340          List<String> storeFiles = storeDescriptor.getStoreFileList();
5341          for (String storeFile : storeFiles) {
5342            StoreFileInfo storeFileInfo = null;
5343            try {
5344              storeFileInfo = fs.getStoreFileInfo(Bytes.toString(family), storeFile);
5345              store.bulkLoadHFile(storeFileInfo);
5346            } catch(FileNotFoundException ex) {
5347              LOG.warn(getRegionInfo().getEncodedName() + " : "
5348                      + ((storeFileInfo != null) ? storeFileInfo.toString() :
5349                            (new Path(Bytes.toString(family), storeFile)).toString())
5350                      + " doesn't exist any more. Skip loading the file");
5351            }
5352          }
5353        }
5354      }
5355      if (bulkLoadEvent.getBulkloadSeqNum() > 0) {
5356        mvcc.advanceTo(bulkLoadEvent.getBulkloadSeqNum());
5357      }
5358    } finally {
5359      closeBulkRegionOperation();
5360    }
5361  }
5362
5363  /**
5364   * If all stores ended up dropping their snapshots, we can safely drop the prepareFlushResult
5365   */
5366  private void dropPrepareFlushIfPossible() {
5367    if (writestate.flushing) {
5368      boolean canDrop = true;
5369      if (prepareFlushResult.storeFlushCtxs != null) {
5370        for (Entry<byte[], StoreFlushContext> entry : prepareFlushResult.storeFlushCtxs
5371            .entrySet()) {
5372          HStore store = getStore(entry.getKey());
5373          if (store == null) {
5374            continue;
5375          }
5376          if (store.getSnapshotSize().getDataSize() > 0) {
5377            canDrop = false;
5378            break;
5379          }
5380        }
5381      }
5382
5383      // this means that all the stores in the region has finished flushing, but the WAL marker
5384      // may not have been written or we did not receive it yet.
5385      if (canDrop) {
5386        writestate.flushing = false;
5387        this.prepareFlushResult = null;
5388      }
5389    }
5390  }
5391
5392  @Override
5393  public boolean refreshStoreFiles() throws IOException {
5394    return refreshStoreFiles(false);
5395  }
5396
5397  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NN_NAKED_NOTIFY",
5398      justification = "Notify is about post replay. Intentional")
5399  protected boolean refreshStoreFiles(boolean force) throws IOException {
5400    if (!force && ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) {
5401      return false; // if primary nothing to do
5402    }
5403
5404    if (LOG.isDebugEnabled()) {
5405      LOG.debug(getRegionInfo().getEncodedName() + " : "
5406          + "Refreshing store files to see whether we can free up memstore");
5407    }
5408
5409    long totalFreedDataSize = 0;
5410
5411    long smallestSeqIdInStores = Long.MAX_VALUE;
5412
5413    startRegionOperation(); // obtain region close lock
5414    try {
5415      Map<HStore, Long> map = new HashMap<>();
5416      synchronized (writestate) {
5417        for (HStore store : stores.values()) {
5418          // TODO: some stores might see new data from flush, while others do not which
5419          // MIGHT break atomic edits across column families.
5420          long maxSeqIdBefore = store.getMaxSequenceId().orElse(0L);
5421
5422          // refresh the store files. This is similar to observing a region open wal marker.
5423          store.refreshStoreFiles();
5424
5425          long storeSeqId = store.getMaxSequenceId().orElse(0L);
5426          if (storeSeqId < smallestSeqIdInStores) {
5427            smallestSeqIdInStores = storeSeqId;
5428          }
5429
5430          // see whether we can drop the memstore or the snapshot
5431          if (storeSeqId > maxSeqIdBefore) {
5432            if (writestate.flushing) {
5433              // only drop memstore snapshots if they are smaller than last flush for the store
5434              if (this.prepareFlushResult.flushOpSeqId <= storeSeqId) {
5435                StoreFlushContext ctx = this.prepareFlushResult.storeFlushCtxs == null ?
5436                    null : this.prepareFlushResult.storeFlushCtxs.get(
5437                            store.getColumnFamilyDescriptor().getName());
5438                if (ctx != null) {
5439                  MemStoreSize mss = store.getFlushableSize();
5440                  ctx.abort();
5441                  this.decrMemStoreSize(mss);
5442                  this.prepareFlushResult.storeFlushCtxs.
5443                      remove(store.getColumnFamilyDescriptor().getName());
5444                  totalFreedDataSize += mss.getDataSize();
5445                }
5446              }
5447            }
5448
5449            map.put(store, storeSeqId);
5450          }
5451        }
5452
5453        // if all stores ended up dropping their snapshots, we can safely drop the
5454        // prepareFlushResult
5455        dropPrepareFlushIfPossible();
5456
5457        // advance the mvcc read point so that the new flushed files are visible.
5458        // either greater than flush seq number or they were already picked up via flush.
5459        for (HStore s : stores.values()) {
5460          mvcc.advanceTo(s.getMaxMemStoreTS().orElse(0L));
5461        }
5462
5463
5464        // smallestSeqIdInStores is the seqId that we have a corresponding hfile for. We can safely
5465        // skip all edits that are to be replayed in the future with that has a smaller seqId
5466        // than this. We are updating lastReplayedOpenRegionSeqId so that we can skip all edits
5467        // that we have picked the flush files for
5468        if (this.lastReplayedOpenRegionSeqId < smallestSeqIdInStores) {
5469          this.lastReplayedOpenRegionSeqId = smallestSeqIdInStores;
5470        }
5471      }
5472      if (!map.isEmpty()) {
5473        for (Map.Entry<HStore, Long> entry : map.entrySet()) {
5474          // Drop the memstore contents if they are now smaller than the latest seen flushed file
5475          totalFreedDataSize += dropMemStoreContentsForSeqId(entry.getValue(), entry.getKey())
5476              .getDataSize();
5477        }
5478      }
5479      // C. Finally notify anyone waiting on memstore to clear:
5480      // e.g. checkResources().
5481      synchronized (this) {
5482        notifyAll(); // FindBugs NN_NAKED_NOTIFY
5483      }
5484      return totalFreedDataSize > 0;
5485    } finally {
5486      closeRegionOperation();
5487    }
5488  }
5489
5490  private void logRegionFiles() {
5491    if (LOG.isTraceEnabled()) {
5492      LOG.trace(getRegionInfo().getEncodedName() + " : Store files for region: ");
5493      stores.values().stream().filter(s -> s.getStorefiles() != null)
5494          .flatMap(s -> s.getStorefiles().stream())
5495          .forEachOrdered(sf -> LOG.trace(getRegionInfo().getEncodedName() + " : " + sf));
5496    }
5497  }
5498
5499  /** Checks whether the given regionName is either equal to our region, or that
5500   * the regionName is the primary region to our corresponding range for the secondary replica.
5501   */
5502  private void checkTargetRegion(byte[] encodedRegionName, String exceptionMsg, Object payload)
5503      throws WrongRegionException {
5504    if (Bytes.equals(this.getRegionInfo().getEncodedNameAsBytes(), encodedRegionName)) {
5505      return;
5506    }
5507
5508    if (!RegionReplicaUtil.isDefaultReplica(this.getRegionInfo()) &&
5509        Bytes.equals(encodedRegionName,
5510          this.fs.getRegionInfoForFS().getEncodedNameAsBytes())) {
5511      return;
5512    }
5513
5514    throw new WrongRegionException(exceptionMsg + payload
5515      + " targetted for region " + Bytes.toStringBinary(encodedRegionName)
5516      + " does not match this region: " + this.getRegionInfo());
5517  }
5518
5519  /**
5520   * Used by tests
5521   * @param s Store to add edit too.
5522   * @param cell Cell to add.
5523   */
5524  @VisibleForTesting
5525  protected void restoreEdit(HStore s, Cell cell, MemStoreSizing memstoreAccounting) {
5526    s.add(cell, memstoreAccounting);
5527  }
5528
5529  /**
5530   * @param p File to check.
5531   * @return True if file was zero-length (and if so, we'll delete it in here).
5532   * @throws IOException
5533   */
5534  private static boolean isZeroLengthThenDelete(final FileSystem fs, final Path p)
5535      throws IOException {
5536    FileStatus stat = fs.getFileStatus(p);
5537    if (stat.getLen() > 0) {
5538      return false;
5539    }
5540    LOG.warn("File " + p + " is zero-length, deleting.");
5541    fs.delete(p, false);
5542    return true;
5543  }
5544
5545  protected HStore instantiateHStore(final ColumnFamilyDescriptor family) throws IOException {
5546    if (family.isMobEnabled()) {
5547      if (HFile.getFormatVersion(this.conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) {
5548        throw new IOException("A minimum HFile version of "
5549            + HFile.MIN_FORMAT_VERSION_WITH_TAGS
5550            + " is required for MOB feature. Consider setting " + HFile.FORMAT_VERSION_KEY
5551            + " accordingly.");
5552      }
5553      return new HMobStore(this, family, this.conf);
5554    }
5555    return new HStore(this, family, this.conf);
5556  }
5557
5558  @Override
5559  public HStore getStore(byte[] column) {
5560    return this.stores.get(column);
5561  }
5562
5563  /**
5564   * Return HStore instance. Does not do any copy: as the number of store is limited, we iterate on
5565   * the list.
5566   */
5567  private HStore getStore(Cell cell) {
5568    return stores.entrySet().stream().filter(e -> CellUtil.matchingFamily(cell, e.getKey()))
5569        .map(e -> e.getValue()).findFirst().orElse(null);
5570  }
5571
5572  @Override
5573  public List<HStore> getStores() {
5574    return new ArrayList<>(stores.values());
5575  }
5576
5577  @Override
5578  public List<String> getStoreFileList(byte[][] columns) throws IllegalArgumentException {
5579    List<String> storeFileNames = new ArrayList<>();
5580    synchronized (closeLock) {
5581      for (byte[] column : columns) {
5582        HStore store = this.stores.get(column);
5583        if (store == null) {
5584          throw new IllegalArgumentException(
5585              "No column family : " + new String(column, StandardCharsets.UTF_8) + " available");
5586        }
5587        Collection<HStoreFile> storeFiles = store.getStorefiles();
5588        if (storeFiles == null) {
5589          continue;
5590        }
5591        for (HStoreFile storeFile : storeFiles) {
5592          storeFileNames.add(storeFile.getPath().toString());
5593        }
5594
5595        logRegionFiles();
5596      }
5597    }
5598    return storeFileNames;
5599  }
5600
5601  //////////////////////////////////////////////////////////////////////////////
5602  // Support code
5603  //////////////////////////////////////////////////////////////////////////////
5604
5605  /** Make sure this is a valid row for the HRegion */
5606  void checkRow(byte[] row, String op) throws IOException {
5607    if (!rowIsInRange(getRegionInfo(), row)) {
5608      throw new WrongRegionException("Requested row out of range for " +
5609          op + " on HRegion " + this + ", startKey='" +
5610          Bytes.toStringBinary(getRegionInfo().getStartKey()) + "', getEndKey()='" +
5611          Bytes.toStringBinary(getRegionInfo().getEndKey()) + "', row='" +
5612          Bytes.toStringBinary(row) + "'");
5613    }
5614  }
5615
5616
5617  /**
5618   * Get an exclusive ( write lock ) lock on a given row.
5619   * @param row Which row to lock.
5620   * @return A locked RowLock. The lock is exclusive and already aqquired.
5621   * @throws IOException
5622   */
5623  public RowLock getRowLock(byte[] row) throws IOException {
5624    return getRowLock(row, false);
5625  }
5626
5627  @Override
5628  public RowLock getRowLock(byte[] row, boolean readLock) throws IOException {
5629    checkRow(row, "row lock");
5630    return getRowLockInternal(row, readLock, null);
5631  }
5632
5633  protected RowLock getRowLockInternal(byte[] row, boolean readLock, final RowLock prevRowLock)
5634      throws IOException {
5635    // create an object to use a a key in the row lock map
5636    HashedBytes rowKey = new HashedBytes(row);
5637
5638    RowLockContext rowLockContext = null;
5639    RowLockImpl result = null;
5640
5641    boolean success = false;
5642    try (TraceScope scope = TraceUtil.createTrace("HRegion.getRowLock")) {
5643      TraceUtil.addTimelineAnnotation("Getting a " + (readLock?"readLock":"writeLock"));
5644      // Keep trying until we have a lock or error out.
5645      // TODO: do we need to add a time component here?
5646      while (result == null) {
5647        rowLockContext = computeIfAbsent(lockedRows, rowKey, () -> new RowLockContext(rowKey));
5648        // Now try an get the lock.
5649        // This can fail as
5650        if (readLock) {
5651          // For read lock, if the caller has locked the same row previously, it will not try
5652          // to acquire the same read lock. It simply returns the previous row lock.
5653          RowLockImpl prevRowLockImpl = (RowLockImpl)prevRowLock;
5654          if ((prevRowLockImpl != null) && (prevRowLockImpl.getLock() ==
5655              rowLockContext.readWriteLock.readLock())) {
5656            success = true;
5657            return prevRowLock;
5658          }
5659          result = rowLockContext.newReadLock();
5660        } else {
5661          result = rowLockContext.newWriteLock();
5662        }
5663      }
5664
5665      int timeout = rowLockWaitDuration;
5666      boolean reachDeadlineFirst = false;
5667      Optional<RpcCall> call = RpcServer.getCurrentCall();
5668      if (call.isPresent()) {
5669        long deadline = call.get().getDeadline();
5670        if (deadline < Long.MAX_VALUE) {
5671          int timeToDeadline = (int) (deadline - System.currentTimeMillis());
5672          if (timeToDeadline <= this.rowLockWaitDuration) {
5673            reachDeadlineFirst = true;
5674            timeout = timeToDeadline;
5675          }
5676        }
5677      }
5678
5679      if (timeout <= 0 || !result.getLock().tryLock(timeout, TimeUnit.MILLISECONDS)) {
5680        TraceUtil.addTimelineAnnotation("Failed to get row lock");
5681        String message = "Timed out waiting for lock for row: " + rowKey + " in region "
5682            + getRegionInfo().getEncodedName();
5683        if (reachDeadlineFirst) {
5684          throw new TimeoutIOException(message);
5685        } else {
5686          // If timeToDeadline is larger than rowLockWaitDuration, we can not drop the request.
5687          throw new IOException(message);
5688        }
5689      }
5690      rowLockContext.setThreadName(Thread.currentThread().getName());
5691      success = true;
5692      return result;
5693    } catch (InterruptedException ie) {
5694      LOG.warn("Thread interrupted waiting for lock on row: " + rowKey);
5695      InterruptedIOException iie = new InterruptedIOException();
5696      iie.initCause(ie);
5697      TraceUtil.addTimelineAnnotation("Interrupted exception getting row lock");
5698      Thread.currentThread().interrupt();
5699      throw iie;
5700    } catch (Error error) {
5701      // The maximum lock count for read lock is 64K (hardcoded), when this maximum count
5702      // is reached, it will throw out an Error. This Error needs to be caught so it can
5703      // go ahead to process the minibatch with lock acquired.
5704      LOG.warn("Error to get row lock for " + Bytes.toStringBinary(row) + ", cause: " + error);
5705      IOException ioe = new IOException();
5706      ioe.initCause(error);
5707      TraceUtil.addTimelineAnnotation("Error getting row lock");
5708      throw ioe;
5709    } finally {
5710      // Clean up the counts just in case this was the thing keeping the context alive.
5711      if (!success && rowLockContext != null) {
5712        rowLockContext.cleanUp();
5713      }
5714    }
5715  }
5716
5717  private void releaseRowLocks(List<RowLock> rowLocks) {
5718    if (rowLocks != null) {
5719      for (RowLock rowLock : rowLocks) {
5720        rowLock.release();
5721      }
5722      rowLocks.clear();
5723    }
5724  }
5725
5726  @VisibleForTesting
5727  public int getReadLockCount() {
5728    return lock.getReadLockCount();
5729  }
5730
5731  public ConcurrentHashMap<HashedBytes, RowLockContext> getLockedRows() {
5732    return lockedRows;
5733  }
5734
5735  @VisibleForTesting
5736  class RowLockContext {
5737    private final HashedBytes row;
5738    final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
5739    final AtomicBoolean usable = new AtomicBoolean(true);
5740    final AtomicInteger count = new AtomicInteger(0);
5741    final Object lock = new Object();
5742    private String threadName;
5743
5744    RowLockContext(HashedBytes row) {
5745      this.row = row;
5746    }
5747
5748    RowLockImpl newWriteLock() {
5749      Lock l = readWriteLock.writeLock();
5750      return getRowLock(l);
5751    }
5752    RowLockImpl newReadLock() {
5753      Lock l = readWriteLock.readLock();
5754      return getRowLock(l);
5755    }
5756
5757    private RowLockImpl getRowLock(Lock l) {
5758      count.incrementAndGet();
5759      synchronized (lock) {
5760        if (usable.get()) {
5761          return new RowLockImpl(this, l);
5762        } else {
5763          return null;
5764        }
5765      }
5766    }
5767
5768    void cleanUp() {
5769      long c = count.decrementAndGet();
5770      if (c <= 0) {
5771        synchronized (lock) {
5772          if (count.get() <= 0 && usable.get()){ // Don't attempt to remove row if already removed
5773            usable.set(false);
5774            RowLockContext removed = lockedRows.remove(row);
5775            assert removed == this: "we should never remove a different context";
5776          }
5777        }
5778      }
5779    }
5780
5781    public void setThreadName(String threadName) {
5782      this.threadName = threadName;
5783    }
5784
5785    @Override
5786    public String toString() {
5787      return "RowLockContext{" +
5788          "row=" + row +
5789          ", readWriteLock=" + readWriteLock +
5790          ", count=" + count +
5791          ", threadName=" + threadName +
5792          '}';
5793    }
5794  }
5795
5796  /**
5797   * Class used to represent a lock on a row.
5798   */
5799  public static class RowLockImpl implements RowLock {
5800    private final RowLockContext context;
5801    private final Lock lock;
5802
5803    public RowLockImpl(RowLockContext context, Lock lock) {
5804      this.context = context;
5805      this.lock = lock;
5806    }
5807
5808    public Lock getLock() {
5809      return lock;
5810    }
5811
5812    @VisibleForTesting
5813    public RowLockContext getContext() {
5814      return context;
5815    }
5816
5817    @Override
5818    public void release() {
5819      lock.unlock();
5820      context.cleanUp();
5821    }
5822
5823    @Override
5824    public String toString() {
5825      return "RowLockImpl{" +
5826          "context=" + context +
5827          ", lock=" + lock +
5828          '}';
5829    }
5830  }
5831
5832  /**
5833   * Determines whether multiple column families are present
5834   * Precondition: familyPaths is not null
5835   *
5836   * @param familyPaths List of (column family, hfilePath)
5837   */
5838  private static boolean hasMultipleColumnFamilies(Collection<Pair<byte[], String>> familyPaths) {
5839    boolean multipleFamilies = false;
5840    byte[] family = null;
5841    for (Pair<byte[], String> pair : familyPaths) {
5842      byte[] fam = pair.getFirst();
5843      if (family == null) {
5844        family = fam;
5845      } else if (!Bytes.equals(family, fam)) {
5846        multipleFamilies = true;
5847        break;
5848      }
5849    }
5850    return multipleFamilies;
5851  }
5852
5853  /**
5854   * Attempts to atomically load a group of hfiles.  This is critical for loading
5855   * rows with multiple column families atomically.
5856   *
5857   * @param familyPaths List of Pair&lt;byte[] column family, String hfilePath&gt;
5858   * @param bulkLoadListener Internal hooks enabling massaging/preparation of a
5859   * file about to be bulk loaded
5860   * @param assignSeqId
5861   * @return Map from family to List of store file paths if successful, null if failed recoverably
5862   * @throws IOException if failed unrecoverably.
5863   */
5864  public Map<byte[], List<Path>> bulkLoadHFiles(Collection<Pair<byte[], String>> familyPaths, boolean assignSeqId,
5865      BulkLoadListener bulkLoadListener) throws IOException {
5866    return bulkLoadHFiles(familyPaths, assignSeqId, bulkLoadListener, false);
5867  }
5868
5869  /**
5870   * Listener class to enable callers of
5871   * bulkLoadHFile() to perform any necessary
5872   * pre/post processing of a given bulkload call
5873   */
5874  public interface BulkLoadListener {
5875    /**
5876     * Called before an HFile is actually loaded
5877     * @param family family being loaded to
5878     * @param srcPath path of HFile
5879     * @return final path to be used for actual loading
5880     * @throws IOException
5881     */
5882    String prepareBulkLoad(byte[] family, String srcPath, boolean copyFile)
5883        throws IOException;
5884
5885    /**
5886     * Called after a successful HFile load
5887     * @param family family being loaded to
5888     * @param srcPath path of HFile
5889     * @throws IOException
5890     */
5891    void doneBulkLoad(byte[] family, String srcPath) throws IOException;
5892
5893    /**
5894     * Called after a failed HFile load
5895     * @param family family being loaded to
5896     * @param srcPath path of HFile
5897     * @throws IOException
5898     */
5899    void failedBulkLoad(byte[] family, String srcPath) throws IOException;
5900  }
5901
5902  /**
5903   * Attempts to atomically load a group of hfiles.  This is critical for loading
5904   * rows with multiple column families atomically.
5905   *
5906   * @param familyPaths List of Pair&lt;byte[] column family, String hfilePath&gt;
5907   * @param assignSeqId
5908   * @param bulkLoadListener Internal hooks enabling massaging/preparation of a
5909   * file about to be bulk loaded
5910   * @param copyFile always copy hfiles if true
5911   * @return Map from family to List of store file paths if successful, null if failed recoverably
5912   * @throws IOException if failed unrecoverably.
5913   */
5914  public Map<byte[], List<Path>> bulkLoadHFiles(Collection<Pair<byte[], String>> familyPaths,
5915      boolean assignSeqId, BulkLoadListener bulkLoadListener, boolean copyFile) throws IOException {
5916    long seqId = -1;
5917    Map<byte[], List<Path>> storeFiles = new TreeMap<>(Bytes.BYTES_COMPARATOR);
5918    Map<String, Long> storeFilesSizes = new HashMap<>();
5919    Preconditions.checkNotNull(familyPaths);
5920    // we need writeLock for multi-family bulk load
5921    startBulkRegionOperation(hasMultipleColumnFamilies(familyPaths));
5922    boolean isSuccessful = false;
5923    try {
5924      this.writeRequestsCount.increment();
5925
5926      // There possibly was a split that happened between when the split keys
5927      // were gathered and before the HRegion's write lock was taken.  We need
5928      // to validate the HFile region before attempting to bulk load all of them
5929      List<IOException> ioes = new ArrayList<>();
5930      List<Pair<byte[], String>> failures = new ArrayList<>();
5931      for (Pair<byte[], String> p : familyPaths) {
5932        byte[] familyName = p.getFirst();
5933        String path = p.getSecond();
5934
5935        HStore store = getStore(familyName);
5936        if (store == null) {
5937          IOException ioe = new org.apache.hadoop.hbase.DoNotRetryIOException(
5938              "No such column family " + Bytes.toStringBinary(familyName));
5939          ioes.add(ioe);
5940        } else {
5941          try {
5942            store.assertBulkLoadHFileOk(new Path(path));
5943          } catch (WrongRegionException wre) {
5944            // recoverable (file doesn't fit in region)
5945            failures.add(p);
5946          } catch (IOException ioe) {
5947            // unrecoverable (hdfs problem)
5948            ioes.add(ioe);
5949          }
5950        }
5951      }
5952
5953      // validation failed because of some sort of IO problem.
5954      if (ioes.size() != 0) {
5955        IOException e = MultipleIOException.createIOException(ioes);
5956        LOG.error("There were one or more IO errors when checking if the bulk load is ok.", e);
5957        throw e;
5958      }
5959
5960      // validation failed, bail out before doing anything permanent.
5961      if (failures.size() != 0) {
5962        StringBuilder list = new StringBuilder();
5963        for (Pair<byte[], String> p : failures) {
5964          list.append("\n").append(Bytes.toString(p.getFirst())).append(" : ")
5965              .append(p.getSecond());
5966        }
5967        // problem when validating
5968        LOG.warn("There was a recoverable bulk load failure likely due to a" +
5969            " split.  These (family, HFile) pairs were not loaded: " + list);
5970        return null;
5971      }
5972
5973      // We need to assign a sequential ID that's in between two memstores in order to preserve
5974      // the guarantee that all the edits lower than the highest sequential ID from all the
5975      // HFiles are flushed on disk. See HBASE-10958.  The sequence id returned when we flush is
5976      // guaranteed to be one beyond the file made when we flushed (or if nothing to flush, it is
5977      // a sequence id that we can be sure is beyond the last hfile written).
5978      if (assignSeqId) {
5979        FlushResult fs = flushcache(true, false, FlushLifeCycleTracker.DUMMY);
5980        if (fs.isFlushSucceeded()) {
5981          seqId = ((FlushResultImpl)fs).flushSequenceId;
5982        } else if (fs.getResult() == FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY) {
5983          seqId = ((FlushResultImpl)fs).flushSequenceId;
5984        } else if (fs.getResult() == FlushResult.Result.CANNOT_FLUSH) {
5985          // CANNOT_FLUSH may mean that a flush is already on-going
5986          // we need to wait for that flush to complete
5987          waitForFlushes();
5988        } else {
5989          throw new IOException("Could not bulk load with an assigned sequential ID because the "+
5990            "flush didn't run. Reason for not flushing: " + ((FlushResultImpl)fs).failureReason);
5991        }
5992      }
5993
5994      Map<byte[], List<Pair<Path, Path>>> familyWithFinalPath =
5995          new TreeMap<>(Bytes.BYTES_COMPARATOR);
5996      for (Pair<byte[], String> p : familyPaths) {
5997        byte[] familyName = p.getFirst();
5998        String path = p.getSecond();
5999        HStore store = getStore(familyName);
6000        if (!familyWithFinalPath.containsKey(familyName)) {
6001          familyWithFinalPath.put(familyName, new ArrayList<>());
6002        }
6003        List<Pair<Path, Path>> lst = familyWithFinalPath.get(familyName);
6004        try {
6005          String finalPath = path;
6006          if (bulkLoadListener != null) {
6007            finalPath = bulkLoadListener.prepareBulkLoad(familyName, path, copyFile);
6008          }
6009          Pair<Path, Path> pair = store.preBulkLoadHFile(finalPath, seqId);
6010          lst.add(pair);
6011        } catch (IOException ioe) {
6012          // A failure here can cause an atomicity violation that we currently
6013          // cannot recover from since it is likely a failed HDFS operation.
6014
6015          LOG.error("There was a partial failure due to IO when attempting to" +
6016              " load " + Bytes.toString(p.getFirst()) + " : " + p.getSecond(), ioe);
6017          if (bulkLoadListener != null) {
6018            try {
6019              bulkLoadListener.failedBulkLoad(familyName, path);
6020            } catch (Exception ex) {
6021              LOG.error("Error while calling failedBulkLoad for family " +
6022                  Bytes.toString(familyName) + " with path " + path, ex);
6023            }
6024          }
6025          throw ioe;
6026        }
6027      }
6028
6029      if (this.getCoprocessorHost() != null) {
6030        for (Map.Entry<byte[], List<Pair<Path, Path>>> entry : familyWithFinalPath.entrySet()) {
6031          this.getCoprocessorHost().preCommitStoreFile(entry.getKey(), entry.getValue());
6032        }
6033      }
6034      for (Map.Entry<byte[], List<Pair<Path, Path>>> entry : familyWithFinalPath.entrySet()) {
6035        byte[] familyName = entry.getKey();
6036        for (Pair<Path, Path> p : entry.getValue()) {
6037          String path = p.getFirst().toString();
6038          Path commitedStoreFile = p.getSecond();
6039          HStore store = getStore(familyName);
6040          try {
6041            store.bulkLoadHFile(familyName, path, commitedStoreFile);
6042            // Note the size of the store file
6043            try {
6044              FileSystem fs = commitedStoreFile.getFileSystem(baseConf);
6045              storeFilesSizes.put(commitedStoreFile.getName(), fs.getFileStatus(commitedStoreFile)
6046                  .getLen());
6047            } catch (IOException e) {
6048              LOG.warn("Failed to find the size of hfile " + commitedStoreFile, e);
6049              storeFilesSizes.put(commitedStoreFile.getName(), 0L);
6050            }
6051
6052            if(storeFiles.containsKey(familyName)) {
6053              storeFiles.get(familyName).add(commitedStoreFile);
6054            } else {
6055              List<Path> storeFileNames = new ArrayList<>();
6056              storeFileNames.add(commitedStoreFile);
6057              storeFiles.put(familyName, storeFileNames);
6058            }
6059            if (bulkLoadListener != null) {
6060              bulkLoadListener.doneBulkLoad(familyName, path);
6061            }
6062          } catch (IOException ioe) {
6063            // A failure here can cause an atomicity violation that we currently
6064            // cannot recover from since it is likely a failed HDFS operation.
6065
6066            // TODO Need a better story for reverting partial failures due to HDFS.
6067            LOG.error("There was a partial failure due to IO when attempting to" +
6068                " load " + Bytes.toString(familyName) + " : " + p.getSecond(), ioe);
6069            if (bulkLoadListener != null) {
6070              try {
6071                bulkLoadListener.failedBulkLoad(familyName, path);
6072              } catch (Exception ex) {
6073                LOG.error("Error while calling failedBulkLoad for family " +
6074                    Bytes.toString(familyName) + " with path " + path, ex);
6075              }
6076            }
6077            throw ioe;
6078          }
6079        }
6080      }
6081
6082      isSuccessful = true;
6083    } finally {
6084      if (wal != null && !storeFiles.isEmpty()) {
6085        // Write a bulk load event for hfiles that are loaded
6086        try {
6087          WALProtos.BulkLoadDescriptor loadDescriptor =
6088              ProtobufUtil.toBulkLoadDescriptor(this.getRegionInfo().getTable(),
6089                  UnsafeByteOperations.unsafeWrap(this.getRegionInfo().getEncodedNameAsBytes()),
6090                  storeFiles,
6091                storeFilesSizes, seqId);
6092          WALUtil.writeBulkLoadMarkerAndSync(this.wal, this.getReplicationScope(), getRegionInfo(),
6093              loadDescriptor, mvcc);
6094        } catch (IOException ioe) {
6095          if (this.rsServices != null) {
6096            // Have to abort region server because some hfiles has been loaded but we can't write
6097            // the event into WAL
6098            isSuccessful = false;
6099            this.rsServices.abort("Failed to write bulk load event into WAL.", ioe);
6100          }
6101        }
6102      }
6103
6104      closeBulkRegionOperation();
6105    }
6106    return isSuccessful ? storeFiles : null;
6107  }
6108
6109  @Override
6110  public boolean equals(Object o) {
6111    return o instanceof HRegion && Bytes.equals(getRegionInfo().getRegionName(),
6112                                                ((HRegion) o).getRegionInfo().getRegionName());
6113  }
6114
6115  @Override
6116  public int hashCode() {
6117    return Bytes.hashCode(getRegionInfo().getRegionName());
6118  }
6119
6120  @Override
6121  public String toString() {
6122    return getRegionInfo().getRegionNameAsString();
6123  }
6124
6125  /**
6126   * RegionScannerImpl is used to combine scanners from multiple Stores (aka column families).
6127   */
6128  class RegionScannerImpl
6129      implements RegionScanner, Shipper, org.apache.hadoop.hbase.ipc.RpcCallback {
6130    // Package local for testability
6131    KeyValueHeap storeHeap = null;
6132    /** Heap of key-values that are not essential for the provided filters and are thus read
6133     * on demand, if on-demand column family loading is enabled.*/
6134    KeyValueHeap joinedHeap = null;
6135    /**
6136     * If the joined heap data gathering is interrupted due to scan limits, this will
6137     * contain the row for which we are populating the values.*/
6138    protected Cell joinedContinuationRow = null;
6139    private boolean filterClosed = false;
6140
6141    protected final byte[] stopRow;
6142    protected final boolean includeStopRow;
6143    protected final HRegion region;
6144    protected final CellComparator comparator;
6145
6146    private final long readPt;
6147    private final long maxResultSize;
6148    private final ScannerContext defaultScannerContext;
6149    private final FilterWrapper filter;
6150
6151    @Override
6152    public RegionInfo getRegionInfo() {
6153      return region.getRegionInfo();
6154    }
6155
6156    RegionScannerImpl(Scan scan, List<KeyValueScanner> additionalScanners, HRegion region)
6157        throws IOException {
6158      this(scan, additionalScanners, region, HConstants.NO_NONCE, HConstants.NO_NONCE);
6159    }
6160
6161    RegionScannerImpl(Scan scan, List<KeyValueScanner> additionalScanners, HRegion region,
6162        long nonceGroup, long nonce) throws IOException {
6163      this.region = region;
6164      this.maxResultSize = scan.getMaxResultSize();
6165      if (scan.hasFilter()) {
6166        this.filter = new FilterWrapper(scan.getFilter());
6167      } else {
6168        this.filter = null;
6169      }
6170      this.comparator = region.getCellComparator();
6171      /**
6172       * By default, calls to next/nextRaw must enforce the batch limit. Thus, construct a default
6173       * scanner context that can be used to enforce the batch limit in the event that a
6174       * ScannerContext is not specified during an invocation of next/nextRaw
6175       */
6176      defaultScannerContext = ScannerContext.newBuilder()
6177          .setBatchLimit(scan.getBatch()).build();
6178      this.stopRow = scan.getStopRow();
6179      this.includeStopRow = scan.includeStopRow();
6180
6181      // synchronize on scannerReadPoints so that nobody calculates
6182      // getSmallestReadPoint, before scannerReadPoints is updated.
6183      IsolationLevel isolationLevel = scan.getIsolationLevel();
6184      long mvccReadPoint = PackagePrivateFieldAccessor.getMvccReadPoint(scan);
6185      synchronized (scannerReadPoints) {
6186        if (mvccReadPoint > 0) {
6187          this.readPt = mvccReadPoint;
6188        } else if (nonce == HConstants.NO_NONCE || rsServices == null
6189            || rsServices.getNonceManager() == null) {
6190          this.readPt = getReadPoint(isolationLevel);
6191        } else {
6192          this.readPt = rsServices.getNonceManager().getMvccFromOperationContext(nonceGroup, nonce);
6193        }
6194        scannerReadPoints.put(this, this.readPt);
6195      }
6196      initializeScanners(scan, additionalScanners);
6197    }
6198
6199    protected void initializeScanners(Scan scan, List<KeyValueScanner> additionalScanners)
6200        throws IOException {
6201      // Here we separate all scanners into two lists - scanner that provide data required
6202      // by the filter to operate (scanners list) and all others (joinedScanners list).
6203      List<KeyValueScanner> scanners = new ArrayList<>(scan.getFamilyMap().size());
6204      List<KeyValueScanner> joinedScanners = new ArrayList<>(scan.getFamilyMap().size());
6205      // Store all already instantiated scanners for exception handling
6206      List<KeyValueScanner> instantiatedScanners = new ArrayList<>();
6207      // handle additionalScanners
6208      if (additionalScanners != null && !additionalScanners.isEmpty()) {
6209        scanners.addAll(additionalScanners);
6210        instantiatedScanners.addAll(additionalScanners);
6211      }
6212
6213      try {
6214        for (Map.Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap().entrySet()) {
6215          HStore store = stores.get(entry.getKey());
6216          KeyValueScanner scanner = store.getScanner(scan, entry.getValue(), this.readPt);
6217          instantiatedScanners.add(scanner);
6218          if (this.filter == null || !scan.doLoadColumnFamiliesOnDemand()
6219              || this.filter.isFamilyEssential(entry.getKey())) {
6220            scanners.add(scanner);
6221          } else {
6222            joinedScanners.add(scanner);
6223          }
6224        }
6225        initializeKVHeap(scanners, joinedScanners, region);
6226      } catch (Throwable t) {
6227        throw handleException(instantiatedScanners, t);
6228      }
6229    }
6230
6231    protected void initializeKVHeap(List<KeyValueScanner> scanners,
6232        List<KeyValueScanner> joinedScanners, HRegion region)
6233        throws IOException {
6234      this.storeHeap = new KeyValueHeap(scanners, comparator);
6235      if (!joinedScanners.isEmpty()) {
6236        this.joinedHeap = new KeyValueHeap(joinedScanners, comparator);
6237      }
6238    }
6239
6240    private IOException handleException(List<KeyValueScanner> instantiatedScanners,
6241        Throwable t) {
6242      // remove scaner read point before throw the exception
6243      scannerReadPoints.remove(this);
6244      if (storeHeap != null) {
6245        storeHeap.close();
6246        storeHeap = null;
6247        if (joinedHeap != null) {
6248          joinedHeap.close();
6249          joinedHeap = null;
6250        }
6251      } else {
6252        // close all already instantiated scanners before throwing the exception
6253        for (KeyValueScanner scanner : instantiatedScanners) {
6254          scanner.close();
6255        }
6256      }
6257      return t instanceof IOException ? (IOException) t : new IOException(t);
6258    }
6259
6260    @Override
6261    public long getMaxResultSize() {
6262      return maxResultSize;
6263    }
6264
6265    @Override
6266    public long getMvccReadPoint() {
6267      return this.readPt;
6268    }
6269
6270    @Override
6271    public int getBatch() {
6272      return this.defaultScannerContext.getBatchLimit();
6273    }
6274
6275    /**
6276     * Reset both the filter and the old filter.
6277     *
6278     * @throws IOException in case a filter raises an I/O exception.
6279     */
6280    protected void resetFilters() throws IOException {
6281      if (filter != null) {
6282        filter.reset();
6283      }
6284    }
6285
6286    @Override
6287    public boolean next(List<Cell> outResults)
6288        throws IOException {
6289      // apply the batching limit by default
6290      return next(outResults, defaultScannerContext);
6291    }
6292
6293    @Override
6294    public synchronized boolean next(List<Cell> outResults, ScannerContext scannerContext)
6295    throws IOException {
6296      if (this.filterClosed) {
6297        throw new UnknownScannerException("Scanner was closed (timed out?) " +
6298            "after we renewed it. Could be caused by a very slow scanner " +
6299            "or a lengthy garbage collection");
6300      }
6301      startRegionOperation(Operation.SCAN);
6302      try {
6303        return nextRaw(outResults, scannerContext);
6304      } finally {
6305        closeRegionOperation(Operation.SCAN);
6306      }
6307    }
6308
6309    @Override
6310    public boolean nextRaw(List<Cell> outResults) throws IOException {
6311      // Use the RegionScanner's context by default
6312      return nextRaw(outResults, defaultScannerContext);
6313    }
6314
6315    @Override
6316    public boolean nextRaw(List<Cell> outResults, ScannerContext scannerContext)
6317        throws IOException {
6318      if (storeHeap == null) {
6319        // scanner is closed
6320        throw new UnknownScannerException("Scanner was closed");
6321      }
6322      boolean moreValues = false;
6323      if (outResults.isEmpty()) {
6324        // Usually outResults is empty. This is true when next is called
6325        // to handle scan or get operation.
6326        moreValues = nextInternal(outResults, scannerContext);
6327      } else {
6328        List<Cell> tmpList = new ArrayList<>();
6329        moreValues = nextInternal(tmpList, scannerContext);
6330        outResults.addAll(tmpList);
6331      }
6332
6333      if (!outResults.isEmpty()) {
6334        readRequestsCount.increment();
6335      }
6336
6337      // If the size limit was reached it means a partial Result is being returned. Returning a
6338      // partial Result means that we should not reset the filters; filters should only be reset in
6339      // between rows
6340      if (!scannerContext.mayHaveMoreCellsInRow()) {
6341        resetFilters();
6342      }
6343
6344      if (isFilterDoneInternal()) {
6345        moreValues = false;
6346      }
6347      return moreValues;
6348    }
6349
6350    /**
6351     * @return true if more cells exist after this batch, false if scanner is done
6352     */
6353    private boolean populateFromJoinedHeap(List<Cell> results, ScannerContext scannerContext)
6354            throws IOException {
6355      assert joinedContinuationRow != null;
6356      boolean moreValues = populateResult(results, this.joinedHeap, scannerContext,
6357          joinedContinuationRow);
6358
6359      if (!scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) {
6360        // We are done with this row, reset the continuation.
6361        joinedContinuationRow = null;
6362      }
6363      // As the data is obtained from two independent heaps, we need to
6364      // ensure that result list is sorted, because Result relies on that.
6365      sort(results, comparator);
6366      return moreValues;
6367    }
6368
6369    /**
6370     * Fetches records with currentRow into results list, until next row, batchLimit (if not -1) is
6371     * reached, or remainingResultSize (if not -1) is reaced
6372     * @param heap KeyValueHeap to fetch data from.It must be positioned on correct row before call.
6373     * @param scannerContext
6374     * @param currentRowCell
6375     * @return state of last call to {@link KeyValueHeap#next()}
6376     */
6377    private boolean populateResult(List<Cell> results, KeyValueHeap heap,
6378        ScannerContext scannerContext, Cell currentRowCell) throws IOException {
6379      Cell nextKv;
6380      boolean moreCellsInRow = false;
6381      boolean tmpKeepProgress = scannerContext.getKeepProgress();
6382      // Scanning between column families and thus the scope is between cells
6383      LimitScope limitScope = LimitScope.BETWEEN_CELLS;
6384      do {
6385        // We want to maintain any progress that is made towards the limits while scanning across
6386        // different column families. To do this, we toggle the keep progress flag on during calls
6387        // to the StoreScanner to ensure that any progress made thus far is not wiped away.
6388        scannerContext.setKeepProgress(true);
6389        heap.next(results, scannerContext);
6390        scannerContext.setKeepProgress(tmpKeepProgress);
6391
6392        nextKv = heap.peek();
6393        moreCellsInRow = moreCellsInRow(nextKv, currentRowCell);
6394        if (!moreCellsInRow) incrementCountOfRowsScannedMetric(scannerContext);
6395        if (moreCellsInRow && scannerContext.checkBatchLimit(limitScope)) {
6396          return scannerContext.setScannerState(NextState.BATCH_LIMIT_REACHED).hasMoreValues();
6397        } else if (scannerContext.checkSizeLimit(limitScope)) {
6398          ScannerContext.NextState state =
6399              moreCellsInRow ? NextState.SIZE_LIMIT_REACHED_MID_ROW : NextState.SIZE_LIMIT_REACHED;
6400          return scannerContext.setScannerState(state).hasMoreValues();
6401        } else if (scannerContext.checkTimeLimit(limitScope)) {
6402          ScannerContext.NextState state =
6403              moreCellsInRow ? NextState.TIME_LIMIT_REACHED_MID_ROW : NextState.TIME_LIMIT_REACHED;
6404          return scannerContext.setScannerState(state).hasMoreValues();
6405        }
6406      } while (moreCellsInRow);
6407      return nextKv != null;
6408    }
6409
6410    /**
6411     * Based on the nextKv in the heap, and the current row, decide whether or not there are more
6412     * cells to be read in the heap. If the row of the nextKv in the heap matches the current row
6413     * then there are more cells to be read in the row.
6414     * @param nextKv
6415     * @param currentRowCell
6416     * @return true When there are more cells in the row to be read
6417     */
6418    private boolean moreCellsInRow(final Cell nextKv, Cell currentRowCell) {
6419      return nextKv != null && CellUtil.matchingRows(nextKv, currentRowCell);
6420    }
6421
6422    /*
6423     * @return True if a filter rules the scanner is over, done.
6424     */
6425    @Override
6426    public synchronized boolean isFilterDone() throws IOException {
6427      return isFilterDoneInternal();
6428    }
6429
6430    private boolean isFilterDoneInternal() throws IOException {
6431      return this.filter != null && this.filter.filterAllRemaining();
6432    }
6433
6434    private boolean nextInternal(List<Cell> results, ScannerContext scannerContext)
6435        throws IOException {
6436      if (!results.isEmpty()) {
6437        throw new IllegalArgumentException("First parameter should be an empty list");
6438      }
6439      if (scannerContext == null) {
6440        throw new IllegalArgumentException("Scanner context cannot be null");
6441      }
6442      Optional<RpcCall> rpcCall = RpcServer.getCurrentCall();
6443
6444      // Save the initial progress from the Scanner context in these local variables. The progress
6445      // may need to be reset a few times if rows are being filtered out so we save the initial
6446      // progress.
6447      int initialBatchProgress = scannerContext.getBatchProgress();
6448      long initialSizeProgress = scannerContext.getDataSizeProgress();
6449      long initialHeapSizeProgress = scannerContext.getHeapSizeProgress();
6450
6451      // Used to check time limit
6452      LimitScope limitScope = LimitScope.BETWEEN_CELLS;
6453
6454      // The loop here is used only when at some point during the next we determine
6455      // that due to effects of filters or otherwise, we have an empty row in the result.
6456      // Then we loop and try again. Otherwise, we must get out on the first iteration via return,
6457      // "true" if there's more data to read, "false" if there isn't (storeHeap is at a stop row,
6458      // and joinedHeap has no more data to read for the last row (if set, joinedContinuationRow).
6459      while (true) {
6460        // Starting to scan a new row. Reset the scanner progress according to whether or not
6461        // progress should be kept.
6462        if (scannerContext.getKeepProgress()) {
6463          // Progress should be kept. Reset to initial values seen at start of method invocation.
6464          scannerContext.setProgress(initialBatchProgress, initialSizeProgress,
6465              initialHeapSizeProgress);
6466        } else {
6467          scannerContext.clearProgress();
6468        }
6469        if (rpcCall.isPresent()) {
6470          // If a user specifies a too-restrictive or too-slow scanner, the
6471          // client might time out and disconnect while the server side
6472          // is still processing the request. We should abort aggressively
6473          // in that case.
6474          long afterTime = rpcCall.get().disconnectSince();
6475          if (afterTime >= 0) {
6476            throw new CallerDisconnectedException(
6477                "Aborting on region " + getRegionInfo().getRegionNameAsString() + ", call " +
6478                    this + " after " + afterTime + " ms, since " +
6479                    "caller disconnected");
6480          }
6481        }
6482
6483        // Let's see what we have in the storeHeap.
6484        Cell current = this.storeHeap.peek();
6485
6486        boolean shouldStop = shouldStop(current);
6487        // When has filter row is true it means that the all the cells for a particular row must be
6488        // read before a filtering decision can be made. This means that filters where hasFilterRow
6489        // run the risk of enLongAddering out of memory errors in the case that they are applied to a
6490        // table that has very large rows.
6491        boolean hasFilterRow = this.filter != null && this.filter.hasFilterRow();
6492
6493        // If filter#hasFilterRow is true, partial results are not allowed since allowing them
6494        // would prevent the filters from being evaluated. Thus, if it is true, change the
6495        // scope of any limits that could potentially create partial results to
6496        // LimitScope.BETWEEN_ROWS so that those limits are not reached mid-row
6497        if (hasFilterRow) {
6498          if (LOG.isTraceEnabled()) {
6499            LOG.trace("filter#hasFilterRow is true which prevents partial results from being "
6500                + " formed. Changing scope of limits that may create partials");
6501          }
6502          scannerContext.setSizeLimitScope(LimitScope.BETWEEN_ROWS);
6503          scannerContext.setTimeLimitScope(LimitScope.BETWEEN_ROWS);
6504          limitScope = LimitScope.BETWEEN_ROWS;
6505        }
6506
6507        if (scannerContext.checkTimeLimit(LimitScope.BETWEEN_CELLS)) {
6508          if (hasFilterRow) {
6509            throw new IncompatibleFilterException(
6510                "Filter whose hasFilterRow() returns true is incompatible with scans that must " +
6511                    " stop mid-row because of a limit. ScannerContext:" + scannerContext);
6512          }
6513          return true;
6514        }
6515
6516        // Check if we were getting data from the joinedHeap and hit the limit.
6517        // If not, then it's main path - getting results from storeHeap.
6518        if (joinedContinuationRow == null) {
6519          // First, check if we are at a stop row. If so, there are no more results.
6520          if (shouldStop) {
6521            if (hasFilterRow) {
6522              filter.filterRowCells(results);
6523            }
6524            return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6525          }
6526
6527          // Check if rowkey filter wants to exclude this row. If so, loop to next.
6528          // Technically, if we hit limits before on this row, we don't need this call.
6529          if (filterRowKey(current)) {
6530            incrementCountOfRowsFilteredMetric(scannerContext);
6531            // early check, see HBASE-16296
6532            if (isFilterDoneInternal()) {
6533              return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6534            }
6535            // Typically the count of rows scanned is incremented inside #populateResult. However,
6536            // here we are filtering a row based purely on its row key, preventing us from calling
6537            // #populateResult. Thus, perform the necessary increment here to rows scanned metric
6538            incrementCountOfRowsScannedMetric(scannerContext);
6539            boolean moreRows = nextRow(scannerContext, current);
6540            if (!moreRows) {
6541              return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6542            }
6543            results.clear();
6544
6545            // Read nothing as the rowkey was filtered, but still need to check time limit
6546            if (scannerContext.checkTimeLimit(limitScope)) {
6547              return true;
6548            }
6549            continue;
6550          }
6551
6552          // Ok, we are good, let's try to get some results from the main heap.
6553          populateResult(results, this.storeHeap, scannerContext, current);
6554          if (scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) {
6555            if (hasFilterRow) {
6556              throw new IncompatibleFilterException(
6557                  "Filter whose hasFilterRow() returns true is incompatible with scans that must "
6558                      + " stop mid-row because of a limit. ScannerContext:" + scannerContext);
6559            }
6560            return true;
6561          }
6562
6563          Cell nextKv = this.storeHeap.peek();
6564          shouldStop = shouldStop(nextKv);
6565          // save that the row was empty before filters applied to it.
6566          final boolean isEmptyRow = results.isEmpty();
6567
6568          // We have the part of the row necessary for filtering (all of it, usually).
6569          // First filter with the filterRow(List).
6570          FilterWrapper.FilterRowRetCode ret = FilterWrapper.FilterRowRetCode.NOT_CALLED;
6571          if (hasFilterRow) {
6572            ret = filter.filterRowCellsWithRet(results);
6573
6574            // We don't know how the results have changed after being filtered. Must set progress
6575            // according to contents of results now.
6576            if (scannerContext.getKeepProgress()) {
6577              scannerContext.setProgress(initialBatchProgress, initialSizeProgress,
6578                  initialHeapSizeProgress);
6579            } else {
6580              scannerContext.clearProgress();
6581            }
6582            scannerContext.incrementBatchProgress(results.size());
6583            for (Cell cell : results) {
6584              scannerContext.incrementSizeProgress(PrivateCellUtil.estimatedSerializedSizeOf(cell),
6585                PrivateCellUtil.estimatedSizeOfCell(cell));
6586            }
6587          }
6588
6589          if (isEmptyRow || ret == FilterWrapper.FilterRowRetCode.EXCLUDE || filterRow()) {
6590            incrementCountOfRowsFilteredMetric(scannerContext);
6591            results.clear();
6592            boolean moreRows = nextRow(scannerContext, current);
6593            if (!moreRows) {
6594              return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6595            }
6596
6597            // This row was totally filtered out, if this is NOT the last row,
6598            // we should continue on. Otherwise, nothing else to do.
6599            if (!shouldStop) {
6600              // Read nothing as the cells was filtered, but still need to check time limit
6601              if (scannerContext.checkTimeLimit(limitScope)) {
6602                return true;
6603              }
6604              continue;
6605            }
6606            return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6607          }
6608
6609          // Ok, we are done with storeHeap for this row.
6610          // Now we may need to fetch additional, non-essential data into row.
6611          // These values are not needed for filter to work, so we postpone their
6612          // fetch to (possibly) reduce amount of data loads from disk.
6613          if (this.joinedHeap != null) {
6614            boolean mayHaveData = joinedHeapMayHaveData(current);
6615            if (mayHaveData) {
6616              joinedContinuationRow = current;
6617              populateFromJoinedHeap(results, scannerContext);
6618
6619              if (scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) {
6620                return true;
6621              }
6622            }
6623          }
6624        } else {
6625          // Populating from the joined heap was stopped by limits, populate some more.
6626          populateFromJoinedHeap(results, scannerContext);
6627          if (scannerContext.checkAnyLimitReached(LimitScope.BETWEEN_CELLS)) {
6628            return true;
6629          }
6630        }
6631        // We may have just called populateFromJoinedMap and hit the limits. If that is
6632        // the case, we need to call it again on the next next() invocation.
6633        if (joinedContinuationRow != null) {
6634          return scannerContext.setScannerState(NextState.MORE_VALUES).hasMoreValues();
6635        }
6636
6637        // Finally, we are done with both joinedHeap and storeHeap.
6638        // Double check to prevent empty rows from appearing in result. It could be
6639        // the case when SingleColumnValueExcludeFilter is used.
6640        if (results.isEmpty()) {
6641          incrementCountOfRowsFilteredMetric(scannerContext);
6642          boolean moreRows = nextRow(scannerContext, current);
6643          if (!moreRows) {
6644            return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6645          }
6646          if (!shouldStop) continue;
6647        }
6648
6649        if (shouldStop) {
6650          return scannerContext.setScannerState(NextState.NO_MORE_VALUES).hasMoreValues();
6651        } else {
6652          return scannerContext.setScannerState(NextState.MORE_VALUES).hasMoreValues();
6653        }
6654      }
6655    }
6656
6657    protected void incrementCountOfRowsFilteredMetric(ScannerContext scannerContext) {
6658      filteredReadRequestsCount.increment();
6659
6660      if (scannerContext == null || !scannerContext.isTrackingMetrics()) return;
6661
6662      scannerContext.getMetrics().countOfRowsFiltered.incrementAndGet();
6663    }
6664
6665    protected void incrementCountOfRowsScannedMetric(ScannerContext scannerContext) {
6666      if (scannerContext == null || !scannerContext.isTrackingMetrics()) return;
6667
6668      scannerContext.getMetrics().countOfRowsScanned.incrementAndGet();
6669    }
6670
6671    /**
6672     * @param currentRowCell
6673     * @return true when the joined heap may have data for the current row
6674     * @throws IOException
6675     */
6676    private boolean joinedHeapMayHaveData(Cell currentRowCell)
6677        throws IOException {
6678      Cell nextJoinedKv = joinedHeap.peek();
6679      boolean matchCurrentRow =
6680          nextJoinedKv != null && CellUtil.matchingRows(nextJoinedKv, currentRowCell);
6681      boolean matchAfterSeek = false;
6682
6683      // If the next value in the joined heap does not match the current row, try to seek to the
6684      // correct row
6685      if (!matchCurrentRow) {
6686        Cell firstOnCurrentRow = PrivateCellUtil.createFirstOnRow(currentRowCell);
6687        boolean seekSuccessful = this.joinedHeap.requestSeek(firstOnCurrentRow, true, true);
6688        matchAfterSeek =
6689            seekSuccessful && joinedHeap.peek() != null
6690                && CellUtil.matchingRows(joinedHeap.peek(), currentRowCell);
6691      }
6692
6693      return matchCurrentRow || matchAfterSeek;
6694    }
6695
6696    /**
6697     * This function is to maintain backward compatibility for 0.94 filters. HBASE-6429 combines
6698     * both filterRow & filterRow({@code List<KeyValue> kvs}) functions. While 0.94 code or older,
6699     * it may not implement hasFilterRow as HBase-6429 expects because 0.94 hasFilterRow() only
6700     * returns true when filterRow({@code List<KeyValue> kvs}) is overridden not the filterRow().
6701     * Therefore, the filterRow() will be skipped.
6702     */
6703    private boolean filterRow() throws IOException {
6704      // when hasFilterRow returns true, filter.filterRow() will be called automatically inside
6705      // filterRowCells(List<Cell> kvs) so we skip that scenario here.
6706      return filter != null && (!filter.hasFilterRow())
6707          && filter.filterRow();
6708    }
6709
6710    private boolean filterRowKey(Cell current) throws IOException {
6711      return filter != null && filter.filterRowKey(current);
6712    }
6713
6714    protected boolean nextRow(ScannerContext scannerContext, Cell curRowCell) throws IOException {
6715      assert this.joinedContinuationRow == null: "Trying to go to next row during joinedHeap read.";
6716      Cell next;
6717      while ((next = this.storeHeap.peek()) != null &&
6718             CellUtil.matchingRows(next, curRowCell)) {
6719        this.storeHeap.next(MOCKED_LIST);
6720      }
6721      resetFilters();
6722
6723      // Calling the hook in CP which allows it to do a fast forward
6724      return this.region.getCoprocessorHost() == null
6725          || this.region.getCoprocessorHost()
6726              .postScannerFilterRow(this, curRowCell);
6727    }
6728
6729    protected boolean shouldStop(Cell currentRowCell) {
6730      if (currentRowCell == null) {
6731        return true;
6732      }
6733      if (stopRow == null || Bytes.equals(stopRow, HConstants.EMPTY_END_ROW)) {
6734        return false;
6735      }
6736      int c = comparator.compareRows(currentRowCell, stopRow, 0, stopRow.length);
6737      return c > 0 || (c == 0 && !includeStopRow);
6738    }
6739
6740    @Override
6741    public synchronized void close() {
6742      if (storeHeap != null) {
6743        storeHeap.close();
6744        storeHeap = null;
6745      }
6746      if (joinedHeap != null) {
6747        joinedHeap.close();
6748        joinedHeap = null;
6749      }
6750      // no need to synchronize here.
6751      scannerReadPoints.remove(this);
6752      this.filterClosed = true;
6753    }
6754
6755    KeyValueHeap getStoreHeapForTesting() {
6756      return storeHeap;
6757    }
6758
6759    @Override
6760    public synchronized boolean reseek(byte[] row) throws IOException {
6761      if (row == null) {
6762        throw new IllegalArgumentException("Row cannot be null.");
6763      }
6764      boolean result = false;
6765      startRegionOperation();
6766      Cell kv = PrivateCellUtil.createFirstOnRow(row, 0, (short) row.length);
6767      try {
6768        // use request seek to make use of the lazy seek option. See HBASE-5520
6769        result = this.storeHeap.requestSeek(kv, true, true);
6770        if (this.joinedHeap != null) {
6771          result = this.joinedHeap.requestSeek(kv, true, true) || result;
6772        }
6773      } finally {
6774        closeRegionOperation();
6775      }
6776      return result;
6777    }
6778
6779    @Override
6780    public void shipped() throws IOException {
6781      if (storeHeap != null) {
6782        storeHeap.shipped();
6783      }
6784      if (joinedHeap != null) {
6785        joinedHeap.shipped();
6786      }
6787    }
6788
6789    @Override
6790    public void run() throws IOException {
6791      // This is the RPC callback method executed. We do the close in of the scanner in this
6792      // callback
6793      this.close();
6794    }
6795  }
6796
6797  // Utility methods
6798  /**
6799   * A utility method to create new instances of HRegion based on the
6800   * {@link HConstants#REGION_IMPL} configuration property.
6801   * @param tableDir qualified path of directory where region should be located,
6802   * usually the table directory.
6803   * @param wal The WAL is the outbound log for any updates to the HRegion
6804   * The wal file is a logfile from the previous execution that's
6805   * custom-computed for this HRegion. The HRegionServer computes and sorts the
6806   * appropriate wal info for this HRegion. If there is a previous file
6807   * (implying that the HRegion has been written-to before), then read it from
6808   * the supplied path.
6809   * @param fs is the filesystem.
6810   * @param conf is global configuration settings.
6811   * @param regionInfo - RegionInfo that describes the region
6812   * is new), then read them from the supplied path.
6813   * @param htd the table descriptor
6814   * @return the new instance
6815   */
6816  static HRegion newHRegion(Path tableDir, WAL wal, FileSystem fs,
6817      Configuration conf, RegionInfo regionInfo, final TableDescriptor htd,
6818      RegionServerServices rsServices) {
6819    try {
6820      @SuppressWarnings("unchecked")
6821      Class<? extends HRegion> regionClass =
6822          (Class<? extends HRegion>) conf.getClass(HConstants.REGION_IMPL, HRegion.class);
6823
6824      Constructor<? extends HRegion> c =
6825          regionClass.getConstructor(Path.class, WAL.class, FileSystem.class,
6826              Configuration.class, RegionInfo.class, TableDescriptor.class,
6827              RegionServerServices.class);
6828
6829      return c.newInstance(tableDir, wal, fs, conf, regionInfo, htd, rsServices);
6830    } catch (Throwable e) {
6831      // todo: what should I throw here?
6832      throw new IllegalStateException("Could not instantiate a region instance.", e);
6833    }
6834  }
6835
6836  /**
6837   * Convenience method creating new HRegions. Used by createTable.
6838   *
6839   * @param info Info for region to create.
6840   * @param rootDir Root directory for HBase instance
6841   * @param wal shared WAL
6842   * @param initialize - true to initialize the region
6843   * @return new HRegion
6844   * @throws IOException
6845   */
6846  public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
6847        final Configuration conf, final TableDescriptor hTableDescriptor,
6848        final WAL wal, final boolean initialize)
6849  throws IOException {
6850    LOG.info("creating HRegion " + info.getTable().getNameAsString()
6851        + " HTD == " + hTableDescriptor + " RootDir = " + rootDir +
6852        " Table name == " + info.getTable().getNameAsString());
6853    FileSystem fs = FileSystem.get(conf);
6854    Path tableDir = FSUtils.getTableDir(rootDir, info.getTable());
6855    HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, info);
6856    HRegion region = HRegion.newHRegion(tableDir, wal, fs, conf, info, hTableDescriptor, null);
6857    if (initialize) region.initialize(null);
6858    return region;
6859  }
6860
6861  public static HRegion createHRegion(final RegionInfo info, final Path rootDir,
6862                                      final Configuration conf,
6863                                      final TableDescriptor hTableDescriptor,
6864                                      final WAL wal)
6865    throws IOException {
6866    return createHRegion(info, rootDir, conf, hTableDescriptor, wal, true);
6867  }
6868
6869
6870  /**
6871   * Open a Region.
6872   * @param info Info for region to be opened.
6873   * @param wal WAL for region to use. This method will call
6874   * WAL#setSequenceNumber(long) passing the result of the call to
6875   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
6876   * up.  HRegionStore does this every time it opens a new region.
6877   * @return new HRegion
6878   *
6879   * @throws IOException
6880   */
6881  public static HRegion openHRegion(final RegionInfo info,
6882      final TableDescriptor htd, final WAL wal,
6883      final Configuration conf)
6884  throws IOException {
6885    return openHRegion(info, htd, wal, conf, null, null);
6886  }
6887
6888  /**
6889   * Open a Region.
6890   * @param info Info for region to be opened
6891   * @param htd the table descriptor
6892   * @param wal WAL for region to use. This method will call
6893   * WAL#setSequenceNumber(long) passing the result of the call to
6894   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
6895   * up.  HRegionStore does this every time it opens a new region.
6896   * @param conf The Configuration object to use.
6897   * @param rsServices An interface we can request flushes against.
6898   * @param reporter An interface we can report progress against.
6899   * @return new HRegion
6900   *
6901   * @throws IOException
6902   */
6903  public static HRegion openHRegion(final RegionInfo info,
6904    final TableDescriptor htd, final WAL wal, final Configuration conf,
6905    final RegionServerServices rsServices,
6906    final CancelableProgressable reporter)
6907  throws IOException {
6908    return openHRegion(FSUtils.getRootDir(conf), info, htd, wal, conf, rsServices, reporter);
6909  }
6910
6911  /**
6912   * Open a Region.
6913   * @param rootDir Root directory for HBase instance
6914   * @param info Info for region to be opened.
6915   * @param htd the table descriptor
6916   * @param wal WAL for region to use. This method will call
6917   * WAL#setSequenceNumber(long) passing the result of the call to
6918   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
6919   * up.  HRegionStore does this every time it opens a new region.
6920   * @param conf The Configuration object to use.
6921   * @return new HRegion
6922   * @throws IOException
6923   */
6924  public static HRegion openHRegion(Path rootDir, final RegionInfo info,
6925      final TableDescriptor htd, final WAL wal, final Configuration conf)
6926  throws IOException {
6927    return openHRegion(rootDir, info, htd, wal, conf, null, null);
6928  }
6929
6930  /**
6931   * Open a Region.
6932   * @param rootDir Root directory for HBase instance
6933   * @param info Info for region to be opened.
6934   * @param htd the table descriptor
6935   * @param wal WAL for region to use. This method will call
6936   * WAL#setSequenceNumber(long) passing the result of the call to
6937   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
6938   * up.  HRegionStore does this every time it opens a new region.
6939   * @param conf The Configuration object to use.
6940   * @param rsServices An interface we can request flushes against.
6941   * @param reporter An interface we can report progress against.
6942   * @return new HRegion
6943   * @throws IOException
6944   */
6945  public static HRegion openHRegion(final Path rootDir, final RegionInfo info,
6946      final TableDescriptor htd, final WAL wal, final Configuration conf,
6947      final RegionServerServices rsServices,
6948      final CancelableProgressable reporter)
6949  throws IOException {
6950    FileSystem fs = null;
6951    if (rsServices != null) {
6952      fs = rsServices.getFileSystem();
6953    }
6954    if (fs == null) {
6955      fs = FileSystem.get(conf);
6956    }
6957    return openHRegion(conf, fs, rootDir, info, htd, wal, rsServices, reporter);
6958  }
6959
6960  /**
6961   * Open a Region.
6962   * @param conf The Configuration object to use.
6963   * @param fs Filesystem to use
6964   * @param rootDir Root directory for HBase instance
6965   * @param info Info for region to be opened.
6966   * @param htd the table descriptor
6967   * @param wal WAL for region to use. This method will call
6968   * WAL#setSequenceNumber(long) passing the result of the call to
6969   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
6970   * up.  HRegionStore does this every time it opens a new region.
6971   * @return new HRegion
6972   */
6973  public static HRegion openHRegion(final Configuration conf, final FileSystem fs,
6974      final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal)
6975      throws IOException {
6976    return openHRegion(conf, fs, rootDir, info, htd, wal, null, null);
6977  }
6978
6979  /**
6980   * Open a Region.
6981   * @param conf The Configuration object to use.
6982   * @param fs Filesystem to use
6983   * @param rootDir Root directory for HBase instance
6984   * @param info Info for region to be opened.
6985   * @param htd the table descriptor
6986   * @param wal WAL for region to use. This method will call
6987   * WAL#setSequenceNumber(long) passing the result of the call to
6988   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
6989   * up.  HRegionStore does this every time it opens a new region.
6990   * @param rsServices An interface we can request flushes against.
6991   * @param reporter An interface we can report progress against.
6992   * @return new HRegion
6993   */
6994  public static HRegion openHRegion(final Configuration conf, final FileSystem fs,
6995      final Path rootDir, final RegionInfo info, final TableDescriptor htd, final WAL wal,
6996      final RegionServerServices rsServices, final CancelableProgressable reporter)
6997      throws IOException {
6998    Path tableDir = FSUtils.getTableDir(rootDir, info.getTable());
6999    return openHRegion(conf, fs, rootDir, tableDir, info, htd, wal, rsServices, reporter);
7000  }
7001
7002  /**
7003   * Open a Region.
7004   * @param conf The Configuration object to use.
7005   * @param fs Filesystem to use
7006   * @param rootDir Root directory for HBase instance
7007   * @param info Info for region to be opened.
7008   * @param htd the table descriptor
7009   * @param wal WAL for region to use. This method will call
7010   * WAL#setSequenceNumber(long) passing the result of the call to
7011   * HRegion#getMinSequenceId() to ensure the wal id is properly kept
7012   * up.  HRegionStore does this every time it opens a new region.
7013   * @param rsServices An interface we can request flushes against.
7014   * @param reporter An interface we can report progress against.
7015   * @return new HRegion
7016   */
7017  public static HRegion openHRegion(final Configuration conf, final FileSystem fs,
7018      final Path rootDir, final Path tableDir, final RegionInfo info, final TableDescriptor htd,
7019      final WAL wal, final RegionServerServices rsServices,
7020      final CancelableProgressable reporter)
7021      throws IOException {
7022    if (info == null) throw new NullPointerException("Passed region info is null");
7023    if (LOG.isDebugEnabled()) {
7024      LOG.debug("Opening region: " + info);
7025    }
7026    HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, rsServices);
7027    return r.openHRegion(reporter);
7028  }
7029
7030  @VisibleForTesting
7031  public NavigableMap<byte[], Integer> getReplicationScope() {
7032    return this.replicationScope;
7033  }
7034
7035  /**
7036   * Useful when reopening a closed region (normally for unit tests)
7037   * @param other original object
7038   * @param reporter An interface we can report progress against.
7039   * @return new HRegion
7040   */
7041  public static HRegion openHRegion(final HRegion other, final CancelableProgressable reporter)
7042      throws IOException {
7043    HRegionFileSystem regionFs = other.getRegionFileSystem();
7044    HRegion r = newHRegion(regionFs.getTableDir(), other.getWAL(), regionFs.getFileSystem(),
7045        other.baseConf, other.getRegionInfo(), other.getTableDescriptor(), null);
7046    return r.openHRegion(reporter);
7047  }
7048
7049  public static Region openHRegion(final Region other, final CancelableProgressable reporter)
7050        throws IOException {
7051    return openHRegion((HRegion)other, reporter);
7052  }
7053
7054  /**
7055   * Open HRegion.
7056   * Calls initialize and sets sequenceId.
7057   * @return Returns <code>this</code>
7058   */
7059  protected HRegion openHRegion(final CancelableProgressable reporter)
7060  throws IOException {
7061    // Refuse to open the region if we are missing local compression support
7062    checkCompressionCodecs();
7063    // Refuse to open the region if encryption configuration is incorrect or
7064    // codec support is missing
7065    checkEncryption();
7066    // Refuse to open the region if a required class cannot be loaded
7067    checkClassLoading();
7068    this.openSeqNum = initialize(reporter);
7069    this.mvcc.advanceTo(openSeqNum);
7070    // The openSeqNum must be increased every time when a region is assigned, as we rely on it to
7071    // determine whether a region has been successfully reopened. So here we always write open
7072    // marker, even if the table is read only.
7073    if (wal != null && getRegionServerServices() != null &&
7074      RegionReplicaUtil.isDefaultReplica(getRegionInfo())) {
7075      writeRegionOpenMarker(wal, openSeqNum);
7076    }
7077    return this;
7078  }
7079
7080  /**
7081   * Open a Region on a read-only file-system (like hdfs snapshots)
7082   * @param conf The Configuration object to use.
7083   * @param fs Filesystem to use
7084   * @param info Info for region to be opened.
7085   * @param htd the table descriptor
7086   * @return new HRegion
7087   */
7088  public static HRegion openReadOnlyFileSystemHRegion(final Configuration conf, final FileSystem fs,
7089      final Path tableDir, RegionInfo info, final TableDescriptor htd) throws IOException {
7090    if (info == null) {
7091      throw new NullPointerException("Passed region info is null");
7092    }
7093    if (LOG.isDebugEnabled()) {
7094      LOG.debug("Opening region (readOnly filesystem): " + info);
7095    }
7096    if (info.getReplicaId() <= 0) {
7097      info = RegionInfoBuilder.newBuilder(info).setReplicaId(1).build();
7098    }
7099    HRegion r = HRegion.newHRegion(tableDir, null, fs, conf, info, htd, null);
7100    r.writestate.setReadOnly(true);
7101    return r.openHRegion(null);
7102  }
7103
7104  public static void warmupHRegion(final RegionInfo info,
7105      final TableDescriptor htd, final WAL wal, final Configuration conf,
7106      final RegionServerServices rsServices,
7107      final CancelableProgressable reporter)
7108      throws IOException {
7109
7110    if (info == null) throw new NullPointerException("Passed region info is null");
7111
7112    if (LOG.isDebugEnabled()) {
7113      LOG.debug("HRegion.Warming up region: " + info);
7114    }
7115
7116    Path rootDir = FSUtils.getRootDir(conf);
7117    Path tableDir = FSUtils.getTableDir(rootDir, info.getTable());
7118
7119    FileSystem fs = null;
7120    if (rsServices != null) {
7121      fs = rsServices.getFileSystem();
7122    }
7123    if (fs == null) {
7124      fs = FileSystem.get(conf);
7125    }
7126
7127    HRegion r = HRegion.newHRegion(tableDir, wal, fs, conf, info, htd, null);
7128    r.initializeWarmup(reporter);
7129  }
7130
7131
7132  private void checkCompressionCodecs() throws IOException {
7133    for (ColumnFamilyDescriptor fam: this.htableDescriptor.getColumnFamilies()) {
7134      CompressionTest.testCompression(fam.getCompressionType());
7135      CompressionTest.testCompression(fam.getCompactionCompressionType());
7136    }
7137  }
7138
7139  private void checkEncryption() throws IOException {
7140    for (ColumnFamilyDescriptor fam: this.htableDescriptor.getColumnFamilies()) {
7141      EncryptionTest.testEncryption(conf, fam.getEncryptionType(), fam.getEncryptionKey());
7142    }
7143  }
7144
7145  private void checkClassLoading() throws IOException {
7146    RegionSplitPolicy.getSplitPolicyClass(this.htableDescriptor, conf);
7147    RegionCoprocessorHost.testTableCoprocessorAttrs(conf, this.htableDescriptor);
7148  }
7149
7150  /**
7151   * Computes the Path of the HRegion
7152   *
7153   * @param tabledir qualified path for table
7154   * @param name ENCODED region name
7155   * @return Path of HRegion directory
7156   * @deprecated For tests only; to be removed.
7157   */
7158  @Deprecated
7159  public static Path getRegionDir(final Path tabledir, final String name) {
7160    return new Path(tabledir, name);
7161  }
7162
7163  /**
7164   * Computes the Path of the HRegion
7165   *
7166   * @param rootdir qualified path of HBase root directory
7167   * @param info RegionInfo for the region
7168   * @return qualified path of region directory
7169   * @deprecated For tests only; to be removed.
7170   */
7171  @Deprecated
7172  @VisibleForTesting
7173  public static Path getRegionDir(final Path rootdir, final RegionInfo info) {
7174    return new Path(
7175      FSUtils.getTableDir(rootdir, info.getTable()), info.getEncodedName());
7176  }
7177
7178  /**
7179   * Determines if the specified row is within the row range specified by the
7180   * specified RegionInfo
7181   *
7182   * @param info RegionInfo that specifies the row range
7183   * @param row row to be checked
7184   * @return true if the row is within the range specified by the RegionInfo
7185   */
7186  public static boolean rowIsInRange(RegionInfo info, final byte [] row) {
7187    return ((info.getStartKey().length == 0) ||
7188        (Bytes.compareTo(info.getStartKey(), row) <= 0)) &&
7189        ((info.getEndKey().length == 0) ||
7190            (Bytes.compareTo(info.getEndKey(), row) > 0));
7191  }
7192
7193  public static boolean rowIsInRange(RegionInfo info, final byte [] row, final int offset,
7194      final short length) {
7195    return ((info.getStartKey().length == 0) ||
7196        (Bytes.compareTo(info.getStartKey(), 0, info.getStartKey().length,
7197          row, offset, length) <= 0)) &&
7198        ((info.getEndKey().length == 0) ||
7199          (Bytes.compareTo(info.getEndKey(), 0, info.getEndKey().length, row, offset, length) > 0));
7200  }
7201
7202  @Override
7203  public Result get(final Get get) throws IOException {
7204    prepareGet(get);
7205    List<Cell> results = get(get, true);
7206    boolean stale = this.getRegionInfo().getReplicaId() != 0;
7207    return Result.create(results, get.isCheckExistenceOnly() ? !results.isEmpty() : null, stale);
7208  }
7209
7210  void prepareGet(final Get get) throws IOException {
7211    checkRow(get.getRow(), "Get");
7212    // Verify families are all valid
7213    if (get.hasFamilies()) {
7214      for (byte[] family : get.familySet()) {
7215        checkFamily(family);
7216      }
7217    } else { // Adding all families to scanner
7218      for (byte[] family : this.htableDescriptor.getColumnFamilyNames()) {
7219        get.addFamily(family);
7220      }
7221    }
7222  }
7223
7224  @Override
7225  public List<Cell> get(Get get, boolean withCoprocessor) throws IOException {
7226    return get(get, withCoprocessor, HConstants.NO_NONCE, HConstants.NO_NONCE);
7227  }
7228
7229  public List<Cell> get(Get get, boolean withCoprocessor, long nonceGroup, long nonce)
7230      throws IOException {
7231    List<Cell> results = new ArrayList<>();
7232    long before =  EnvironmentEdgeManager.currentTime();
7233
7234    // pre-get CP hook
7235    if (withCoprocessor && (coprocessorHost != null)) {
7236      if (coprocessorHost.preGet(get, results)) {
7237        metricsUpdateForGet(results, before);
7238        return results;
7239      }
7240    }
7241    Scan scan = new Scan(get);
7242    if (scan.getLoadColumnFamiliesOnDemandValue() == null) {
7243      scan.setLoadColumnFamiliesOnDemand(isLoadingCfsOnDemandDefault());
7244    }
7245    RegionScanner scanner = null;
7246    try {
7247      scanner = getScanner(scan, null, nonceGroup, nonce);
7248      scanner.next(results);
7249    } finally {
7250      if (scanner != null)
7251        scanner.close();
7252    }
7253
7254    // post-get CP hook
7255    if (withCoprocessor && (coprocessorHost != null)) {
7256      coprocessorHost.postGet(get, results);
7257    }
7258
7259    metricsUpdateForGet(results, before);
7260
7261    return results;
7262  }
7263
7264  void metricsUpdateForGet(List<Cell> results, long before) {
7265    if (this.metricsRegion != null) {
7266      this.metricsRegion.updateGet(EnvironmentEdgeManager.currentTime() - before);
7267    }
7268  }
7269
7270  @Override
7271  public void mutateRow(RowMutations rm) throws IOException {
7272    // Don't need nonces here - RowMutations only supports puts and deletes
7273    final List<Mutation> m = rm.getMutations();
7274    batchMutate(m.toArray(new Mutation[m.size()]), true, HConstants.NO_NONCE,
7275        HConstants.NO_NONCE);
7276  }
7277
7278  /**
7279   * Perform atomic (all or none) mutations within the region.
7280   * @param mutations The list of mutations to perform.
7281   * <code>mutations</code> can contain operations for multiple rows.
7282   * Caller has to ensure that all rows are contained in this region.
7283   * @param rowsToLock Rows to lock
7284   * @param nonceGroup Optional nonce group of the operation (client Id)
7285   * @param nonce Optional nonce of the operation (unique random id to ensure "more idempotence")
7286   * If multiple rows are locked care should be taken that
7287   * <code>rowsToLock</code> is sorted in order to avoid deadlocks.
7288   * @throws IOException
7289   */
7290  @Override
7291  public void mutateRowsWithLocks(Collection<Mutation> mutations,
7292      Collection<byte[]> rowsToLock, long nonceGroup, long nonce) throws IOException {
7293    batchMutate(new MutationBatchOperation(this, mutations.toArray(new Mutation[mutations.size()]),
7294        true, nonceGroup, nonce) {
7295      @Override
7296      public MiniBatchOperationInProgress<Mutation> lockRowsAndBuildMiniBatch(
7297          List<RowLock> acquiredRowLocks) throws IOException {
7298        RowLock prevRowLock = null;
7299        for (byte[] row : rowsToLock) {
7300          try {
7301            RowLock rowLock = region.getRowLockInternal(row, false, prevRowLock); // write lock
7302            if (rowLock != prevRowLock) {
7303              acquiredRowLocks.add(rowLock);
7304              prevRowLock = rowLock;
7305            }
7306          } catch (IOException ioe) {
7307            LOG.warn("Failed getting lock, row=" + Bytes.toStringBinary(row), ioe);
7308            throw ioe;
7309          }
7310        }
7311        return createMiniBatch(size(), size());
7312      }
7313    });
7314  }
7315
7316  /**
7317   * @return statistics about the current load of the region
7318   */
7319  public ClientProtos.RegionLoadStats getLoadStatistics() {
7320    if (!regionStatsEnabled) {
7321      return null;
7322    }
7323    ClientProtos.RegionLoadStats.Builder stats = ClientProtos.RegionLoadStats.newBuilder();
7324    stats.setMemStoreLoad((int) (Math.min(100,
7325        (this.memStoreSizing.getMemStoreSize().getHeapSize() * 100) / this.memstoreFlushSize)));
7326    if (rsServices.getHeapMemoryManager() != null) {
7327      // the HeapMemoryManager uses -0.0 to signal a problem asking the JVM,
7328      // so we could just do the calculation below and we'll get a 0.
7329      // treating it as a special case analogous to no HMM instead so that it can be
7330      // programatically treated different from using <1% of heap.
7331      final float occupancy = rsServices.getHeapMemoryManager().getHeapOccupancyPercent();
7332      if (occupancy != HeapMemoryManager.HEAP_OCCUPANCY_ERROR_VALUE) {
7333        stats.setHeapOccupancy((int)(occupancy * 100));
7334      }
7335    }
7336    stats.setCompactionPressure((int) (rsServices.getCompactionPressure() * 100 > 100 ? 100
7337        : rsServices.getCompactionPressure() * 100));
7338    return stats.build();
7339  }
7340
7341  @Override
7342  public void processRowsWithLocks(RowProcessor<?,?> processor) throws IOException {
7343    processRowsWithLocks(processor, rowProcessorTimeout, HConstants.NO_NONCE, HConstants.NO_NONCE);
7344  }
7345
7346  @Override
7347  public void processRowsWithLocks(RowProcessor<?,?> processor, long nonceGroup, long nonce)
7348      throws IOException {
7349    processRowsWithLocks(processor, rowProcessorTimeout, nonceGroup, nonce);
7350  }
7351
7352  @Override
7353  public void processRowsWithLocks(RowProcessor<?,?> processor, long timeout,
7354      long nonceGroup, long nonce) throws IOException {
7355    for (byte[] row : processor.getRowsToLock()) {
7356      checkRow(row, "processRowsWithLocks");
7357    }
7358    if (!processor.readOnly()) {
7359      checkReadOnly();
7360    }
7361    checkResources();
7362    startRegionOperation();
7363    WALEdit walEdit = new WALEdit();
7364
7365    // STEP 1. Run pre-process hook
7366    preProcess(processor, walEdit);
7367    // Short circuit the read only case
7368    if (processor.readOnly()) {
7369      try {
7370        long now = EnvironmentEdgeManager.currentTime();
7371        doProcessRowWithTimeout(processor, now, this, null, null, timeout);
7372        processor.postProcess(this, walEdit, true);
7373      } finally {
7374        closeRegionOperation();
7375      }
7376      return;
7377    }
7378
7379    boolean locked = false;
7380    List<RowLock> acquiredRowLocks = null;
7381    List<Mutation> mutations = new ArrayList<>();
7382    Collection<byte[]> rowsToLock = processor.getRowsToLock();
7383    // This is assigned by mvcc either explicity in the below or in the guts of the WAL append
7384    // when it assigns the edit a sequencedid (A.K.A the mvcc write number).
7385    WriteEntry writeEntry = null;
7386    MemStoreSizing memstoreAccounting = new NonThreadSafeMemStoreSizing();
7387    try {
7388      boolean success = false;
7389      try {
7390        // STEP 2. Acquire the row lock(s)
7391        acquiredRowLocks = new ArrayList<>(rowsToLock.size());
7392        RowLock prevRowLock = null;
7393        for (byte[] row : rowsToLock) {
7394          // Attempt to lock all involved rows, throw if any lock times out
7395          // use a writer lock for mixed reads and writes
7396          RowLock rowLock = getRowLockInternal(row, false, prevRowLock);
7397          if (rowLock != prevRowLock) {
7398            acquiredRowLocks.add(rowLock);
7399            prevRowLock = rowLock;
7400          }
7401        }
7402        // STEP 3. Region lock
7403        lock(this.updatesLock.readLock(), acquiredRowLocks.isEmpty() ? 1 : acquiredRowLocks.size());
7404        locked = true;
7405        long now = EnvironmentEdgeManager.currentTime();
7406        // STEP 4. Let the processor scan the rows, generate mutations and add waledits
7407        doProcessRowWithTimeout(processor, now, this, mutations, walEdit, timeout);
7408        if (!mutations.isEmpty()) {
7409          writeRequestsCount.add(mutations.size());
7410          // STEP 5. Call the preBatchMutate hook
7411          processor.preBatchMutate(this, walEdit);
7412
7413          // STEP 6. Append and sync if walEdit has data to write out.
7414          if (!walEdit.isEmpty()) {
7415            writeEntry = doWALAppend(walEdit, getEffectiveDurability(processor.useDurability()),
7416                processor.getClusterIds(), now, nonceGroup, nonce);
7417          } else {
7418            // We are here if WAL is being skipped.
7419            writeEntry = this.mvcc.begin();
7420          }
7421
7422          // STEP 7. Apply to memstore
7423          long sequenceId = writeEntry.getWriteNumber();
7424          for (Mutation m : mutations) {
7425            // Handle any tag based cell features.
7426            // TODO: Do we need to call rewriteCellTags down in applyToMemStore()? Why not before
7427            // so tags go into WAL?
7428            rewriteCellTags(m.getFamilyCellMap(), m);
7429            for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
7430              Cell cell = cellScanner.current();
7431              if (walEdit.isEmpty()) {
7432                // If walEdit is empty, we put nothing in WAL. WAL stamps Cells with sequence id.
7433                // If no WAL, need to stamp it here.
7434                PrivateCellUtil.setSequenceId(cell, sequenceId);
7435              }
7436              applyToMemStore(getStore(cell), cell, memstoreAccounting);
7437            }
7438          }
7439
7440          // STEP 8. call postBatchMutate hook
7441          processor.postBatchMutate(this);
7442
7443          // STEP 9. Complete mvcc.
7444          mvcc.completeAndWait(writeEntry);
7445          writeEntry = null;
7446
7447          // STEP 10. Release region lock
7448          if (locked) {
7449            this.updatesLock.readLock().unlock();
7450            locked = false;
7451          }
7452
7453          // STEP 11. Release row lock(s)
7454          releaseRowLocks(acquiredRowLocks);
7455        }
7456        success = true;
7457      } finally {
7458        // Call complete rather than completeAndWait because we probably had error if walKey != null
7459        if (writeEntry != null) mvcc.complete(writeEntry);
7460        if (locked) {
7461          this.updatesLock.readLock().unlock();
7462        }
7463        // release locks if some were acquired but another timed out
7464        releaseRowLocks(acquiredRowLocks);
7465      }
7466
7467      // 12. Run post-process hook
7468      processor.postProcess(this, walEdit, success);
7469    } finally {
7470      closeRegionOperation();
7471      if (!mutations.isEmpty()) {
7472        this.incMemStoreSize(memstoreAccounting.getMemStoreSize());
7473        requestFlushIfNeeded();
7474      }
7475    }
7476  }
7477
7478  private void preProcess(final RowProcessor<?,?> processor, final WALEdit walEdit)
7479  throws IOException {
7480    try {
7481      processor.preProcess(this, walEdit);
7482    } catch (IOException e) {
7483      closeRegionOperation();
7484      throw e;
7485    }
7486  }
7487
7488  private void doProcessRowWithTimeout(final RowProcessor<?,?> processor,
7489                                       final long now,
7490                                       final HRegion region,
7491                                       final List<Mutation> mutations,
7492                                       final WALEdit walEdit,
7493                                       final long timeout) throws IOException {
7494    // Short circuit the no time bound case.
7495    if (timeout < 0) {
7496      try {
7497        processor.process(now, region, mutations, walEdit);
7498      } catch (IOException e) {
7499        String row = processor.getRowsToLock().isEmpty() ? "" :
7500          " on row(s):" + Bytes.toStringBinary(processor.getRowsToLock().iterator().next()) + "...";
7501        LOG.warn("RowProcessor:" + processor.getClass().getName() +
7502            " throws Exception" + row, e);
7503        throw e;
7504      }
7505      return;
7506    }
7507
7508    // Case with time bound
7509    FutureTask<Void> task = new FutureTask<>(new Callable<Void>() {
7510        @Override
7511        public Void call() throws IOException {
7512          try {
7513            processor.process(now, region, mutations, walEdit);
7514            return null;
7515          } catch (IOException e) {
7516            String row = processor.getRowsToLock().isEmpty() ? "" :
7517              " on row(s):" + Bytes.toStringBinary(processor.getRowsToLock().iterator().next()) + "...";
7518            LOG.warn("RowProcessor:" + processor.getClass().getName() +
7519                " throws Exception" + row, e);
7520            throw e;
7521          }
7522        }
7523      });
7524    rowProcessorExecutor.execute(task);
7525    try {
7526      task.get(timeout, TimeUnit.MILLISECONDS);
7527    } catch (TimeoutException te) {
7528      String row = processor.getRowsToLock().isEmpty() ? "" :
7529        " on row(s):" + Bytes.toStringBinary(processor.getRowsToLock().iterator().next()) + "...";
7530      LOG.error("RowProcessor timeout:" + timeout + " ms" + row);
7531      throw new IOException(te);
7532    } catch (Exception e) {
7533      throw new IOException(e);
7534    }
7535  }
7536
7537  @Override
7538  public Result append(Append append) throws IOException {
7539    return append(append, HConstants.NO_NONCE, HConstants.NO_NONCE);
7540  }
7541
7542  public Result append(Append mutation, long nonceGroup, long nonce) throws IOException {
7543    return doDelta(Operation.APPEND, mutation, nonceGroup, nonce, mutation.isReturnResults());
7544  }
7545
7546  @Override
7547  public Result increment(Increment increment) throws IOException {
7548    return increment(increment, HConstants.NO_NONCE, HConstants.NO_NONCE);
7549  }
7550
7551  public Result increment(Increment mutation, long nonceGroup, long nonce) throws IOException {
7552    return doDelta(Operation.INCREMENT, mutation, nonceGroup, nonce, mutation.isReturnResults());
7553  }
7554
7555  /**
7556   * Add "deltas" to Cells. Deltas are increments or appends. Switch on <code>op</code>.
7557   *
7558   * <p>If increment, add deltas to current values or if an append, then
7559   * append the deltas to the current Cell values.
7560   *
7561   * <p>Append and Increment code paths are mostly the same. They differ in just a few places.
7562   * This method does the code path for increment and append and then in key spots, switches
7563   * on the passed in <code>op</code> to do increment or append specific paths.
7564   */
7565  private Result doDelta(Operation op, Mutation mutation, long nonceGroup, long nonce,
7566      boolean returnResults) throws IOException {
7567    checkReadOnly();
7568    checkResources();
7569    checkRow(mutation.getRow(), op.toString());
7570    checkFamilies(mutation.getFamilyCellMap().keySet());
7571    this.writeRequestsCount.increment();
7572    WriteEntry writeEntry = null;
7573    startRegionOperation(op);
7574    List<Cell> results = returnResults? new ArrayList<>(mutation.size()): null;
7575    RowLock rowLock = null;
7576    MemStoreSizing memstoreAccounting = new NonThreadSafeMemStoreSizing();
7577    try {
7578      rowLock = getRowLockInternal(mutation.getRow(), false, null);
7579      lock(this.updatesLock.readLock());
7580      try {
7581        Result cpResult = doCoprocessorPreCall(op, mutation);
7582        if (cpResult != null) {
7583          // Metrics updated below in the finally block.
7584          return returnResults? cpResult: null;
7585        }
7586        Durability effectiveDurability = getEffectiveDurability(mutation.getDurability());
7587        Map<HStore, List<Cell>> forMemStore = new HashMap<>(mutation.getFamilyCellMap().size());
7588        // Reckon Cells to apply to WAL --  in returned walEdit -- and what to add to memstore and
7589        // what to return back to the client (in 'forMemStore' and 'results' respectively).
7590        WALEdit walEdit = reckonDeltas(op, mutation, effectiveDurability, forMemStore, results);
7591        // Actually write to WAL now if a walEdit to apply.
7592        if (walEdit != null && !walEdit.isEmpty()) {
7593          writeEntry = doWALAppend(walEdit, effectiveDurability, nonceGroup, nonce);
7594        } else {
7595          // If walEdits is empty, it means we skipped the WAL; update LongAdders and start an mvcc
7596          // transaction.
7597          recordMutationWithoutWal(mutation.getFamilyCellMap());
7598          writeEntry = mvcc.begin();
7599          updateSequenceId(forMemStore.values(), writeEntry.getWriteNumber());
7600        }
7601        // Now write to MemStore. Do it a column family at a time.
7602        for (Map.Entry<HStore, List<Cell>> e : forMemStore.entrySet()) {
7603          applyToMemStore(e.getKey(), e.getValue(), true, memstoreAccounting);
7604        }
7605        mvcc.completeAndWait(writeEntry);
7606        if (rsServices != null && rsServices.getNonceManager() != null) {
7607          rsServices.getNonceManager().addMvccToOperationContext(nonceGroup, nonce,
7608            writeEntry.getWriteNumber());
7609        }
7610        writeEntry = null;
7611      } finally {
7612        this.updatesLock.readLock().unlock();
7613      }
7614      // If results is null, then client asked that we not return the calculated results.
7615      return results != null && returnResults? Result.create(results): Result.EMPTY_RESULT;
7616    } finally {
7617      // Call complete always, even on success. doDelta is doing a Get READ_UNCOMMITTED when it goes
7618      // to get current value under an exclusive lock so no need so no need to wait to return to
7619      // the client. Means only way to read-your-own-increment or append is to come in with an
7620      // a 0 increment.
7621      if (writeEntry != null) mvcc.complete(writeEntry);
7622      if (rowLock != null) {
7623        rowLock.release();
7624      }
7625      // Request a cache flush if over the limit.  Do it outside update lock.
7626      incMemStoreSize(memstoreAccounting.getMemStoreSize());
7627      requestFlushIfNeeded();
7628      closeRegionOperation(op);
7629      if (this.metricsRegion != null) {
7630        switch (op) {
7631          case INCREMENT:
7632            this.metricsRegion.updateIncrement();
7633            break;
7634          case APPEND:
7635            this.metricsRegion.updateAppend();
7636            break;
7637          default:
7638            break;
7639        }
7640      }
7641    }
7642  }
7643
7644  private WriteEntry doWALAppend(WALEdit walEdit, Durability durability, long nonceGroup,
7645      long nonce)
7646  throws IOException {
7647    return doWALAppend(walEdit, durability, WALKey.EMPTY_UUIDS, System.currentTimeMillis(),
7648      nonceGroup, nonce);
7649  }
7650
7651  private WriteEntry doWALAppend(WALEdit walEdit, Durability durability, List<UUID> clusterIds,
7652      long now, long nonceGroup, long nonce) throws IOException {
7653    return doWALAppend(walEdit, durability, clusterIds, now, nonceGroup, nonce,
7654        SequenceId.NO_SEQUENCE_ID);
7655  }
7656
7657  /**
7658   * @return writeEntry associated with this append
7659   */
7660  private WriteEntry doWALAppend(WALEdit walEdit, Durability durability, List<UUID> clusterIds,
7661      long now, long nonceGroup, long nonce, long origLogSeqNum) throws IOException {
7662    Preconditions.checkArgument(walEdit != null && !walEdit.isEmpty(),
7663        "WALEdit is null or empty!");
7664    Preconditions.checkArgument(!walEdit.isReplay() || origLogSeqNum != SequenceId.NO_SEQUENCE_ID,
7665        "Invalid replay sequence Id for replay WALEdit!");
7666    // Using default cluster id, as this can only happen in the originating cluster.
7667    // A slave cluster receives the final value (not the delta) as a Put. We use HLogKey
7668    // here instead of WALKeyImpl directly to support legacy coprocessors.
7669    WALKeyImpl walKey = walEdit.isReplay()?
7670        new WALKeyImpl(this.getRegionInfo().getEncodedNameAsBytes(),
7671          this.htableDescriptor.getTableName(), SequenceId.NO_SEQUENCE_ID, now, clusterIds,
7672            nonceGroup, nonce, mvcc) :
7673        new WALKeyImpl(this.getRegionInfo().getEncodedNameAsBytes(),
7674            this.htableDescriptor.getTableName(), SequenceId.NO_SEQUENCE_ID, now, clusterIds,
7675            nonceGroup, nonce, mvcc, this.getReplicationScope());
7676    if (walEdit.isReplay()) {
7677      walKey.setOrigLogSeqNum(origLogSeqNum);
7678    }
7679    WriteEntry writeEntry = null;
7680    try {
7681      long txid = this.wal.append(this.getRegionInfo(), walKey, walEdit, true);
7682      // Call sync on our edit.
7683      if (txid != 0) {
7684        sync(txid, durability);
7685      }
7686      writeEntry = walKey.getWriteEntry();
7687    } catch (IOException ioe) {
7688      if (walKey != null && walKey.getWriteEntry() != null) {
7689        mvcc.complete(walKey.getWriteEntry());
7690      }
7691      throw ioe;
7692    }
7693    return writeEntry;
7694  }
7695
7696  /**
7697   * Do coprocessor pre-increment or pre-append call.
7698   * @return Result returned out of the coprocessor, which means bypass all further processing and
7699   *  return the proffered Result instead, or null which means proceed.
7700   */
7701  private Result doCoprocessorPreCall(final Operation op, final Mutation mutation)
7702  throws IOException {
7703    Result result = null;
7704    if (this.coprocessorHost != null) {
7705      switch(op) {
7706        case INCREMENT:
7707          result = this.coprocessorHost.preIncrementAfterRowLock((Increment)mutation);
7708          break;
7709        case APPEND:
7710          result = this.coprocessorHost.preAppendAfterRowLock((Append)mutation);
7711          break;
7712        default: throw new UnsupportedOperationException(op.toString());
7713      }
7714    }
7715    return result;
7716  }
7717
7718  /**
7719   * Reckon the Cells to apply to WAL, memstore, and to return to the Client; these Sets are not
7720   * always the same dependent on whether to write WAL or if the amount to increment is zero (in
7721   * this case we write back nothing, just return latest Cell value to the client).
7722   *
7723   * @param results Fill in here what goes back to the Client if it is non-null (if null, client
7724   *  doesn't want results).
7725   * @param forMemStore Fill in here what to apply to the MemStore (by Store).
7726   * @return A WALEdit to apply to WAL or null if we are to skip the WAL.
7727   */
7728  private WALEdit reckonDeltas(Operation op, Mutation mutation, Durability effectiveDurability,
7729      Map<HStore, List<Cell>> forMemStore, List<Cell> results) throws IOException {
7730    WALEdit walEdit = null;
7731    long now = EnvironmentEdgeManager.currentTime();
7732    final boolean writeToWAL = effectiveDurability != Durability.SKIP_WAL;
7733    // Process a Store/family at a time.
7734    for (Map.Entry<byte [], List<Cell>> entry: mutation.getFamilyCellMap().entrySet()) {
7735      final byte[] columnFamilyName = entry.getKey();
7736      List<Cell> deltas = entry.getValue();
7737      HStore store = this.stores.get(columnFamilyName);
7738      // Reckon for the Store what to apply to WAL and MemStore.
7739      List<Cell> toApply =
7740        reckonDeltasByStore(store, op, mutation, effectiveDurability, now, deltas, results);
7741      if (!toApply.isEmpty()) {
7742        forMemStore.put(store, toApply);
7743        if (writeToWAL) {
7744          if (walEdit == null) {
7745            walEdit = new WALEdit();
7746          }
7747          walEdit.getCells().addAll(toApply);
7748        }
7749      }
7750    }
7751    return walEdit;
7752  }
7753
7754  /**
7755   * Reckon the Cells to apply to WAL, memstore, and to return to the Client in passed
7756   * column family/Store.
7757   *
7758   * Does Get of current value and then adds passed in deltas for this Store returning the result.
7759   *
7760   * @param op Whether Increment or Append
7761   * @param mutation The encompassing Mutation object
7762   * @param deltas Changes to apply to this Store; either increment amount or data to append
7763   * @param results In here we accumulate all the Cells we are to return to the client; this List
7764   *  can be larger than what we return in case where delta is zero; i.e. don't write
7765   *  out new values, just return current value. If null, client doesn't want results returned.
7766   * @return Resulting Cells after <code>deltas</code> have been applied to current
7767   *  values. Side effect is our filling out of the <code>results</code> List.
7768   */
7769  private List<Cell> reckonDeltasByStore(HStore store, Operation op, Mutation mutation,
7770      Durability effectiveDurability, long now, List<Cell> deltas, List<Cell> results)
7771      throws IOException {
7772    byte[] columnFamily = store.getColumnFamilyDescriptor().getName();
7773    List<Cell> toApply = new ArrayList<>(deltas.size());
7774    // Get previous values for all columns in this family.
7775    TimeRange tr = null;
7776    switch (op) {
7777      case INCREMENT:
7778        tr = ((Increment)mutation).getTimeRange();
7779        break;
7780      case APPEND:
7781        tr = ((Append)mutation).getTimeRange();
7782        break;
7783      default:
7784        break;
7785    }
7786    List<Cell> currentValues = get(mutation, store, deltas,null, tr);
7787    // Iterate the input columns and update existing values if they were found, otherwise
7788    // add new column initialized to the delta amount
7789    int currentValuesIndex = 0;
7790    for (int i = 0; i < deltas.size(); i++) {
7791      Cell delta = deltas.get(i);
7792      Cell currentValue = null;
7793      boolean firstWrite = false;
7794      if (currentValuesIndex < currentValues.size() &&
7795          CellUtil.matchingQualifier(currentValues.get(currentValuesIndex), delta)) {
7796        currentValue = currentValues.get(currentValuesIndex);
7797        if (i < (deltas.size() - 1) && !CellUtil.matchingQualifier(delta, deltas.get(i + 1))) {
7798          currentValuesIndex++;
7799        }
7800      } else {
7801        firstWrite = true;
7802      }
7803      // Switch on whether this an increment or an append building the new Cell to apply.
7804      Cell newCell = null;
7805      MutationType mutationType = null;
7806      boolean apply = true;
7807      switch (op) {
7808        case INCREMENT:
7809          mutationType = MutationType.INCREMENT;
7810          // If delta amount to apply is 0, don't write WAL or MemStore.
7811          long deltaAmount = getLongValue(delta);
7812          // TODO: Does zero value mean reset Cell? For example, the ttl.
7813          apply = deltaAmount != 0;
7814          final long newValue = currentValue == null ? deltaAmount : getLongValue(currentValue) + deltaAmount;
7815          newCell = reckonDelta(delta, currentValue, columnFamily, now, mutation, (oldCell) -> Bytes.toBytes(newValue));
7816          break;
7817        case APPEND:
7818          mutationType = MutationType.APPEND;
7819          // Always apply Append. TODO: Does empty delta value mean reset Cell? It seems to.
7820          newCell = reckonDelta(delta, currentValue, columnFamily, now, mutation, (oldCell) ->
7821            ByteBuffer.wrap(new byte[delta.getValueLength() + oldCell.getValueLength()])
7822                    .put(oldCell.getValueArray(), oldCell.getValueOffset(), oldCell.getValueLength())
7823                    .put(delta.getValueArray(), delta.getValueOffset(), delta.getValueLength())
7824                    .array()
7825          );
7826          break;
7827        default: throw new UnsupportedOperationException(op.toString());
7828      }
7829
7830      // Give coprocessors a chance to update the new cell
7831      if (coprocessorHost != null) {
7832        newCell =
7833            coprocessorHost.postMutationBeforeWAL(mutationType, mutation, currentValue, newCell);
7834      }
7835      // If apply, we need to update memstore/WAL with new value; add it toApply.
7836      if (apply || firstWrite) {
7837        toApply.add(newCell);
7838      }
7839      // Add to results to get returned to the Client. If null, cilent does not want results.
7840      if (results != null) {
7841        results.add(newCell);
7842      }
7843    }
7844    return toApply;
7845  }
7846
7847  private static Cell reckonDelta(final Cell delta, final Cell currentCell,
7848                                  final byte[] columnFamily, final long now,
7849                                  Mutation mutation, Function<Cell, byte[]> supplier) throws IOException {
7850    // Forward any tags found on the delta.
7851    List<Tag> tags = TagUtil.carryForwardTags(delta);
7852    tags = TagUtil.carryForwardTTLTag(tags, mutation.getTTL());
7853    if (currentCell != null) {
7854      tags = TagUtil.carryForwardTags(tags, currentCell);
7855      byte[] newValue = supplier.apply(currentCell);
7856      return ExtendedCellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
7857              .setRow(mutation.getRow(), 0, mutation.getRow().length)
7858              .setFamily(columnFamily, 0, columnFamily.length)
7859              // copy the qualifier if the cell is located in shared memory.
7860              .setQualifier(CellUtil.cloneQualifier(delta))
7861              .setTimestamp(Math.max(currentCell.getTimestamp() + 1, now))
7862              .setType(KeyValue.Type.Put.getCode())
7863              .setValue(newValue, 0, newValue.length)
7864              .setTags(TagUtil.fromList(tags))
7865              .build();
7866    } else {
7867      PrivateCellUtil.updateLatestStamp(delta, now);
7868      return CollectionUtils.isEmpty(tags) ? delta : PrivateCellUtil.createCell(delta, tags);
7869    }
7870  }
7871
7872  /**
7873   * @return Get the long out of the passed in Cell
7874   */
7875  private static long getLongValue(final Cell cell) throws DoNotRetryIOException {
7876    int len = cell.getValueLength();
7877    if (len != Bytes.SIZEOF_LONG) {
7878      // throw DoNotRetryIOException instead of IllegalArgumentException
7879      throw new DoNotRetryIOException("Field is not a long, it's " + len + " bytes wide");
7880    }
7881    return PrivateCellUtil.getValueAsLong(cell);
7882  }
7883
7884  /**
7885   * Do a specific Get on passed <code>columnFamily</code> and column qualifiers.
7886   * @param mutation Mutation we are doing this Get for.
7887   * @param store Which column family on row (TODO: Go all Gets in one go)
7888   * @param coordinates Cells from <code>mutation</code> used as coordinates applied to Get.
7889   * @return Return list of Cells found.
7890   */
7891  private List<Cell> get(Mutation mutation, HStore store, List<Cell> coordinates,
7892      IsolationLevel isolation, TimeRange tr) throws IOException {
7893    // Sort the cells so that they match the order that they appear in the Get results. Otherwise,
7894    // we won't be able to find the existing values if the cells are not specified in order by the
7895    // client since cells are in an array list.
7896    // TODO: I don't get why we are sorting. St.Ack 20150107
7897    sort(coordinates, store.getComparator());
7898    Get get = new Get(mutation.getRow());
7899    if (isolation != null) {
7900      get.setIsolationLevel(isolation);
7901    }
7902    for (Cell cell: coordinates) {
7903      get.addColumn(store.getColumnFamilyDescriptor().getName(), CellUtil.cloneQualifier(cell));
7904    }
7905    // Increments carry time range. If an Increment instance, put it on the Get.
7906    if (tr != null) {
7907      get.setTimeRange(tr.getMin(), tr.getMax());
7908    }
7909    return get(get, false);
7910  }
7911
7912  /**
7913   * @return Sorted list of <code>cells</code> using <code>comparator</code>
7914   */
7915  private static List<Cell> sort(List<Cell> cells, final CellComparator comparator) {
7916    cells.sort(comparator);
7917    return cells;
7918  }
7919
7920  //
7921  // New HBASE-880 Helpers
7922  //
7923
7924  void checkFamily(final byte [] family)
7925  throws NoSuchColumnFamilyException {
7926    if (!this.htableDescriptor.hasColumnFamily(family)) {
7927      throw new NoSuchColumnFamilyException("Column family " +
7928          Bytes.toString(family) + " does not exist in region " + this
7929          + " in table " + this.htableDescriptor);
7930    }
7931  }
7932
7933  public static final long FIXED_OVERHEAD = ClassSize.align(
7934      ClassSize.OBJECT +
7935      ClassSize.ARRAY +
7936      50 * ClassSize.REFERENCE + 3 * Bytes.SIZEOF_INT +
7937      (14 * Bytes.SIZEOF_LONG) +
7938      3 * Bytes.SIZEOF_BOOLEAN);
7939
7940  // woefully out of date - currently missing:
7941  // 1 x HashMap - coprocessorServiceHandlers
7942  // 6 x LongAdder - numMutationsWithoutWAL, dataInMemoryWithoutWAL,
7943  //   checkAndMutateChecksPassed, checkAndMutateChecksFailed, readRequestsCount,
7944  //   writeRequestsCount
7945  // 1 x HRegion$WriteState - writestate
7946  // 1 x RegionCoprocessorHost - coprocessorHost
7947  // 1 x RegionSplitPolicy - splitPolicy
7948  // 1 x MetricsRegion - metricsRegion
7949  // 1 x MetricsRegionWrapperImpl - metricsRegionWrapper
7950  public static final long DEEP_OVERHEAD = FIXED_OVERHEAD +
7951      ClassSize.OBJECT + // closeLock
7952      (2 * ClassSize.ATOMIC_BOOLEAN) + // closed, closing
7953      (3 * ClassSize.ATOMIC_LONG) + // numPutsWithoutWAL, dataInMemoryWithoutWAL,
7954                                    // compactionsFailed
7955      (2 * ClassSize.CONCURRENT_HASHMAP) +  // lockedRows, scannerReadPoints
7956      WriteState.HEAP_SIZE + // writestate
7957      ClassSize.CONCURRENT_SKIPLISTMAP + ClassSize.CONCURRENT_SKIPLISTMAP_ENTRY + // stores
7958      (2 * ClassSize.REENTRANT_LOCK) + // lock, updatesLock
7959      MultiVersionConcurrencyControl.FIXED_SIZE // mvcc
7960      + 2 * ClassSize.TREEMAP // maxSeqIdInStores, replicationScopes
7961      + 2 * ClassSize.ATOMIC_INTEGER // majorInProgress, minorInProgress
7962      + ClassSize.STORE_SERVICES // store services
7963      ;
7964
7965  @Override
7966  public long heapSize() {
7967    // this does not take into account row locks, recent flushes, mvcc entries, and more
7968    return DEEP_OVERHEAD + stores.values().stream().mapToLong(HStore::heapSize).sum();
7969  }
7970
7971  /**
7972   * Registers a new protocol buffer {@link Service} subclass as a coprocessor endpoint to
7973   * be available for handling Region#execService(com.google.protobuf.RpcController,
7974   *    org.apache.hadoop.hbase.protobuf.generated.ClientProtos.CoprocessorServiceCall) calls.
7975   *
7976   * <p>
7977   * Only a single instance may be registered per region for a given {@link Service} subclass (the
7978   * instances are keyed on {@link com.google.protobuf.Descriptors.ServiceDescriptor#getFullName()}.
7979   * After the first registration, subsequent calls with the same service name will fail with
7980   * a return value of {@code false}.
7981   * </p>
7982   * @param instance the {@code Service} subclass instance to expose as a coprocessor endpoint
7983   * @return {@code true} if the registration was successful, {@code false}
7984   * otherwise
7985   */
7986  public boolean registerService(com.google.protobuf.Service instance) {
7987    /*
7988     * No stacking of instances is allowed for a single service name
7989     */
7990    com.google.protobuf.Descriptors.ServiceDescriptor serviceDesc = instance.getDescriptorForType();
7991    String serviceName = CoprocessorRpcUtils.getServiceName(serviceDesc);
7992    if (coprocessorServiceHandlers.containsKey(serviceName)) {
7993      LOG.error("Coprocessor service " + serviceName +
7994          " already registered, rejecting request from " + instance);
7995      return false;
7996    }
7997
7998    coprocessorServiceHandlers.put(serviceName, instance);
7999    if (LOG.isDebugEnabled()) {
8000      LOG.debug("Registered coprocessor service: region=" +
8001          Bytes.toStringBinary(getRegionInfo().getRegionName()) +
8002          " service=" + serviceName);
8003    }
8004    return true;
8005  }
8006
8007  /**
8008   * Executes a single protocol buffer coprocessor endpoint {@link Service} method using
8009   * the registered protocol handlers.  {@link Service} implementations must be registered via the
8010   * {@link #registerService(com.google.protobuf.Service)}
8011   * method before they are available.
8012   *
8013   * @param controller an {@code RpcContoller} implementation to pass to the invoked service
8014   * @param call a {@code CoprocessorServiceCall} instance identifying the service, method,
8015   *     and parameters for the method invocation
8016   * @return a protocol buffer {@code Message} instance containing the method's result
8017   * @throws IOException if no registered service handler is found or an error
8018   *     occurs during the invocation
8019   * @see #registerService(com.google.protobuf.Service)
8020   */
8021  public com.google.protobuf.Message execService(com.google.protobuf.RpcController controller,
8022      CoprocessorServiceCall call) throws IOException {
8023    String serviceName = call.getServiceName();
8024    com.google.protobuf.Service service = coprocessorServiceHandlers.get(serviceName);
8025    if (service == null) {
8026      throw new UnknownProtocolException(null, "No registered coprocessor service found for " +
8027          serviceName + " in region " + Bytes.toStringBinary(getRegionInfo().getRegionName()));
8028    }
8029    com.google.protobuf.Descriptors.ServiceDescriptor serviceDesc = service.getDescriptorForType();
8030
8031    String methodName = call.getMethodName();
8032    com.google.protobuf.Descriptors.MethodDescriptor methodDesc =
8033        CoprocessorRpcUtils.getMethodDescriptor(methodName, serviceDesc);
8034
8035    com.google.protobuf.Message.Builder builder =
8036        service.getRequestPrototype(methodDesc).newBuilderForType();
8037
8038    org.apache.hadoop.hbase.protobuf.ProtobufUtil.mergeFrom(builder,
8039        call.getRequest().toByteArray());
8040    com.google.protobuf.Message request =
8041        CoprocessorRpcUtils.getRequest(service, methodDesc, call.getRequest());
8042
8043    if (coprocessorHost != null) {
8044      request = coprocessorHost.preEndpointInvocation(service, methodName, request);
8045    }
8046
8047    final com.google.protobuf.Message.Builder responseBuilder =
8048        service.getResponsePrototype(methodDesc).newBuilderForType();
8049    service.callMethod(methodDesc, controller, request,
8050        new com.google.protobuf.RpcCallback<com.google.protobuf.Message>() {
8051      @Override
8052      public void run(com.google.protobuf.Message message) {
8053        if (message != null) {
8054          responseBuilder.mergeFrom(message);
8055        }
8056      }
8057    });
8058
8059    if (coprocessorHost != null) {
8060      coprocessorHost.postEndpointInvocation(service, methodName, request, responseBuilder);
8061    }
8062    IOException exception =
8063        org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils.getControllerException(controller);
8064    if (exception != null) {
8065      throw exception;
8066    }
8067
8068    return responseBuilder.build();
8069  }
8070
8071  boolean shouldForceSplit() {
8072    return this.splitRequest;
8073  }
8074
8075  byte[] getExplicitSplitPoint() {
8076    return this.explicitSplitPoint;
8077  }
8078
8079  void forceSplit(byte[] sp) {
8080    // This HRegion will go away after the forced split is successful
8081    // But if a forced split fails, we need to clear forced split.
8082    this.splitRequest = true;
8083    if (sp != null) {
8084      this.explicitSplitPoint = sp;
8085    }
8086  }
8087
8088  void clearSplit() {
8089    this.splitRequest = false;
8090    this.explicitSplitPoint = null;
8091  }
8092
8093  /**
8094   * Return the splitpoint. null indicates the region isn't splittable
8095   * If the splitpoint isn't explicitly specified, it will go over the stores
8096   * to find the best splitpoint. Currently the criteria of best splitpoint
8097   * is based on the size of the store.
8098   */
8099  public byte[] checkSplit() {
8100    // Can't split META
8101    if (this.getRegionInfo().isMetaRegion() ||
8102        TableName.NAMESPACE_TABLE_NAME.equals(this.getRegionInfo().getTable())) {
8103      if (shouldForceSplit()) {
8104        LOG.warn("Cannot split meta region in HBase 0.20 and above");
8105      }
8106      return null;
8107    }
8108
8109    // Can't split a region that is closing.
8110    if (this.isClosing()) {
8111      return null;
8112    }
8113
8114    if (!splitPolicy.shouldSplit()) {
8115      return null;
8116    }
8117
8118    byte[] ret = splitPolicy.getSplitPoint();
8119
8120    if (ret != null) {
8121      try {
8122        checkRow(ret, "calculated split");
8123      } catch (IOException e) {
8124        LOG.error("Ignoring invalid split", e);
8125        return null;
8126      }
8127    }
8128    return ret;
8129  }
8130
8131  /**
8132   * @return The priority that this region should have in the compaction queue
8133   */
8134  public int getCompactPriority() {
8135    return stores.values().stream().mapToInt(HStore::getCompactPriority).min()
8136        .orElse(Store.NO_PRIORITY);
8137  }
8138
8139  /** @return the coprocessor host */
8140  public RegionCoprocessorHost getCoprocessorHost() {
8141    return coprocessorHost;
8142  }
8143
8144  /** @param coprocessorHost the new coprocessor host */
8145  @VisibleForTesting
8146  public void setCoprocessorHost(final RegionCoprocessorHost coprocessorHost) {
8147    this.coprocessorHost = coprocessorHost;
8148  }
8149
8150  @Override
8151  public void startRegionOperation() throws IOException {
8152    startRegionOperation(Operation.ANY);
8153  }
8154
8155  @Override
8156  public void startRegionOperation(Operation op) throws IOException {
8157    switch (op) {
8158      case GET:  // read operations
8159      case SCAN:
8160        checkReadsEnabled();
8161        break;
8162      default:
8163        break;
8164    }
8165    if (op == Operation.MERGE_REGION || op == Operation.SPLIT_REGION
8166        || op == Operation.COMPACT_REGION) {
8167      // split, merge or compact region doesn't need to check the closing/closed state or lock the
8168      // region
8169      return;
8170    }
8171    if (this.closing.get()) {
8172      throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closing");
8173    }
8174    lock(lock.readLock());
8175    if (this.closed.get()) {
8176      lock.readLock().unlock();
8177      throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closed");
8178    }
8179    // The unit for snapshot is a region. So, all stores for this region must be
8180    // prepared for snapshot operation before proceeding.
8181    if (op == Operation.SNAPSHOT) {
8182      stores.values().forEach(HStore::preSnapshotOperation);
8183    }
8184    try {
8185      if (coprocessorHost != null) {
8186        coprocessorHost.postStartRegionOperation(op);
8187      }
8188    } catch (Exception e) {
8189      lock.readLock().unlock();
8190      throw new IOException(e);
8191    }
8192  }
8193
8194  @Override
8195  public void closeRegionOperation() throws IOException {
8196    closeRegionOperation(Operation.ANY);
8197  }
8198
8199  @Override
8200  public void closeRegionOperation(Operation operation) throws IOException {
8201    if (operation == Operation.SNAPSHOT) {
8202      stores.values().forEach(HStore::postSnapshotOperation);
8203    }
8204    lock.readLock().unlock();
8205    if (coprocessorHost != null) {
8206      coprocessorHost.postCloseRegionOperation(operation);
8207    }
8208  }
8209
8210  /**
8211   * This method needs to be called before any public call that reads or
8212   * modifies stores in bulk. It has to be called just before a try.
8213   * #closeBulkRegionOperation needs to be called in the try's finally block
8214   * Acquires a writelock and checks if the region is closing or closed.
8215   * @throws NotServingRegionException when the region is closing or closed
8216   * @throws RegionTooBusyException if failed to get the lock in time
8217   * @throws InterruptedIOException if interrupted while waiting for a lock
8218   */
8219  private void startBulkRegionOperation(boolean writeLockNeeded)
8220      throws NotServingRegionException, RegionTooBusyException, InterruptedIOException {
8221    if (this.closing.get()) {
8222      throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closing");
8223    }
8224    if (writeLockNeeded) lock(lock.writeLock());
8225    else lock(lock.readLock());
8226    if (this.closed.get()) {
8227      if (writeLockNeeded) lock.writeLock().unlock();
8228      else lock.readLock().unlock();
8229      throw new NotServingRegionException(getRegionInfo().getRegionNameAsString() + " is closed");
8230    }
8231  }
8232
8233  /**
8234   * Closes the lock. This needs to be called in the finally block corresponding
8235   * to the try block of #startRegionOperation
8236   */
8237  private void closeBulkRegionOperation(){
8238    if (lock.writeLock().isHeldByCurrentThread()) lock.writeLock().unlock();
8239    else lock.readLock().unlock();
8240  }
8241
8242  /**
8243   * Update LongAdders for number of puts without wal and the size of possible data loss.
8244   * These information are exposed by the region server metrics.
8245   */
8246  private void recordMutationWithoutWal(final Map<byte [], List<Cell>> familyMap) {
8247    numMutationsWithoutWAL.increment();
8248    if (numMutationsWithoutWAL.sum() <= 1) {
8249      LOG.info("writing data to region " + this +
8250               " with WAL disabled. Data may be lost in the event of a crash.");
8251    }
8252
8253    long mutationSize = 0;
8254    for (List<Cell> cells: familyMap.values()) {
8255      // Optimization: 'foreach' loop is not used. See:
8256      // HBASE-12023 HRegion.applyFamilyMapToMemstore creates too many iterator objects
8257      assert cells instanceof RandomAccess;
8258      int listSize = cells.size();
8259      for (int i=0; i < listSize; i++) {
8260        Cell cell = cells.get(i);
8261        mutationSize += KeyValueUtil.length(cell);
8262      }
8263    }
8264
8265    dataInMemoryWithoutWAL.add(mutationSize);
8266  }
8267
8268  private void lock(final Lock lock) throws RegionTooBusyException, InterruptedIOException {
8269    lock(lock, 1);
8270  }
8271
8272  /**
8273   * Try to acquire a lock.  Throw RegionTooBusyException
8274   * if failed to get the lock in time. Throw InterruptedIOException
8275   * if interrupted while waiting for the lock.
8276   */
8277  private void lock(final Lock lock, final int multiplier)
8278      throws RegionTooBusyException, InterruptedIOException {
8279    try {
8280      final long waitTime = Math.min(maxBusyWaitDuration,
8281          busyWaitDuration * Math.min(multiplier, maxBusyWaitMultiplier));
8282      if (!lock.tryLock(waitTime, TimeUnit.MILLISECONDS)) {
8283        // Don't print millis. Message is used as a key over in
8284        // RetriesExhaustedWithDetailsException processing.
8285        throw new RegionTooBusyException("Failed to obtain lock; regionName=" +
8286            (this.getRegionInfo() == null? "unknown":
8287                this.getRegionInfo().getRegionNameAsString()) +
8288            ", server=" + (this.getRegionServerServices() == null? "unknown":
8289                this.getRegionServerServices().getServerName()));
8290      }
8291    } catch (InterruptedException ie) {
8292      LOG.info("Interrupted while waiting for a lock");
8293      InterruptedIOException iie = new InterruptedIOException();
8294      iie.initCause(ie);
8295      throw iie;
8296    }
8297  }
8298
8299  /**
8300   * Calls sync with the given transaction ID
8301   * @param txid should sync up to which transaction
8302   * @throws IOException If anything goes wrong with DFS
8303   */
8304  private void sync(long txid, Durability durability) throws IOException {
8305    if (this.getRegionInfo().isMetaRegion()) {
8306      this.wal.sync(txid);
8307    } else {
8308      switch(durability) {
8309      case USE_DEFAULT:
8310        // do what table defaults to
8311        if (shouldSyncWAL()) {
8312          this.wal.sync(txid);
8313        }
8314        break;
8315      case SKIP_WAL:
8316        // nothing do to
8317        break;
8318      case ASYNC_WAL:
8319        // nothing do to
8320        break;
8321      case SYNC_WAL:
8322      case FSYNC_WAL:
8323        // sync the WAL edit (SYNC and FSYNC treated the same for now)
8324        this.wal.sync(txid);
8325        break;
8326      default:
8327        throw new RuntimeException("Unknown durability " + durability);
8328      }
8329    }
8330  }
8331
8332  /**
8333   * Check whether we should sync the wal from the table's durability settings
8334   */
8335  private boolean shouldSyncWAL() {
8336    return regionDurability.ordinal() >  Durability.ASYNC_WAL.ordinal();
8337  }
8338
8339  /**
8340   * A mocked list implementation - discards all updates.
8341   */
8342  private static final List<Cell> MOCKED_LIST = new AbstractList<Cell>() {
8343
8344    @Override
8345    public void add(int index, Cell element) {
8346      // do nothing
8347    }
8348
8349    @Override
8350    public boolean addAll(int index, Collection<? extends Cell> c) {
8351      return false; // this list is never changed as a result of an update
8352    }
8353
8354    @Override
8355    public KeyValue get(int index) {
8356      throw new UnsupportedOperationException();
8357    }
8358
8359    @Override
8360    public int size() {
8361      return 0;
8362    }
8363  };
8364
8365  /** @return the latest sequence number that was read from storage when this region was opened */
8366  public long getOpenSeqNum() {
8367    return this.openSeqNum;
8368  }
8369
8370  @Override
8371  public Map<byte[], Long> getMaxStoreSeqId() {
8372    return this.maxSeqIdInStores;
8373  }
8374
8375  public long getOldestSeqIdOfStore(byte[] familyName) {
8376    return wal.getEarliestMemStoreSeqNum(getRegionInfo().getEncodedNameAsBytes(), familyName);
8377  }
8378
8379  @Override
8380  public CompactionState getCompactionState() {
8381    boolean hasMajor = majorInProgress.get() > 0, hasMinor = minorInProgress.get() > 0;
8382    return (hasMajor ? (hasMinor ? CompactionState.MAJOR_AND_MINOR : CompactionState.MAJOR)
8383        : (hasMinor ? CompactionState.MINOR : CompactionState.NONE));
8384  }
8385
8386  public void reportCompactionRequestStart(boolean isMajor){
8387    (isMajor ? majorInProgress : minorInProgress).incrementAndGet();
8388  }
8389
8390  public void reportCompactionRequestEnd(boolean isMajor, int numFiles, long filesSizeCompacted) {
8391    int newValue = (isMajor ? majorInProgress : minorInProgress).decrementAndGet();
8392
8393    // metrics
8394    compactionsFinished.increment();
8395    compactionNumFilesCompacted.add(numFiles);
8396    compactionNumBytesCompacted.add(filesSizeCompacted);
8397
8398    assert newValue >= 0;
8399  }
8400
8401  public void reportCompactionRequestFailure() {
8402    compactionsFailed.increment();
8403  }
8404
8405  public void incrementCompactionsQueuedCount() {
8406    compactionsQueued.increment();
8407  }
8408
8409  public void decrementCompactionsQueuedCount() {
8410    compactionsQueued.decrement();
8411  }
8412
8413  public void incrementFlushesQueuedCount() {
8414    flushesQueued.increment();
8415  }
8416
8417  @VisibleForTesting
8418  public long getReadPoint() {
8419    return getReadPoint(IsolationLevel.READ_COMMITTED);
8420  }
8421
8422  /**
8423   * {@inheritDoc}
8424   */
8425  @Override
8426  public void onConfigurationChange(Configuration conf) {
8427    // Do nothing for now.
8428  }
8429
8430  /**
8431   * {@inheritDoc}
8432   */
8433  @Override
8434  public void registerChildren(ConfigurationManager manager) {
8435    configurationManager = Optional.of(manager);
8436    stores.values().forEach(manager::registerObserver);
8437  }
8438
8439  /**
8440   * {@inheritDoc}
8441   */
8442  @Override
8443  public void deregisterChildren(ConfigurationManager manager) {
8444    stores.values().forEach(configurationManager.get()::deregisterObserver);
8445  }
8446
8447  @Override
8448  public CellComparator getCellComparator() {
8449    return this.getRegionInfo().isMetaRegion() ? CellComparatorImpl.META_COMPARATOR
8450        : CellComparatorImpl.COMPARATOR;
8451  }
8452
8453  public long getMemStoreFlushSize() {
8454    return this.memstoreFlushSize;
8455  }
8456
8457
8458  //// method for debugging tests
8459  void throwException(String title, String regionName) {
8460    StringBuilder buf = new StringBuilder();
8461    buf.append(title + ", ");
8462    buf.append(getRegionInfo().toString());
8463    buf.append(getRegionInfo().isMetaRegion() ? " meta region " : " ");
8464    buf.append("stores: ");
8465    for (HStore s : stores.values()) {
8466      buf.append(s.getColumnFamilyDescriptor().getNameAsString());
8467      buf.append(" size: ");
8468      buf.append(s.getMemStoreSize().getDataSize());
8469      buf.append(" ");
8470    }
8471    buf.append("end-of-stores");
8472    buf.append(", memstore size ");
8473    buf.append(getMemStoreDataSize());
8474    if (getRegionInfo().getRegionNameAsString().startsWith(regionName)) {
8475      throw new RuntimeException(buf.toString());
8476    }
8477  }
8478
8479  @Override
8480  public void requestCompaction(String why, int priority, boolean major,
8481      CompactionLifeCycleTracker tracker) throws IOException {
8482    if (major) {
8483      stores.values().forEach(HStore::triggerMajorCompaction);
8484    }
8485    rsServices.getCompactionRequestor().requestCompaction(this, why, priority, tracker,
8486      RpcServer.getRequestUser().orElse(null));
8487  }
8488
8489  @Override
8490  public void requestCompaction(byte[] family, String why, int priority, boolean major,
8491      CompactionLifeCycleTracker tracker) throws IOException {
8492    HStore store = stores.get(family);
8493    if (store == null) {
8494      throw new NoSuchColumnFamilyException("column family " + Bytes.toString(family) +
8495          " does not exist in region " + getRegionInfo().getRegionNameAsString());
8496    }
8497    if (major) {
8498      store.triggerMajorCompaction();
8499    }
8500    rsServices.getCompactionRequestor().requestCompaction(this, store, why, priority, tracker,
8501      RpcServer.getRequestUser().orElse(null));
8502  }
8503
8504  private void requestFlushIfNeeded() throws RegionTooBusyException {
8505    if(isFlushSize(this.memStoreSizing.getMemStoreSize())) {
8506      requestFlush();
8507    }
8508  }
8509
8510  private void requestFlush() {
8511    if (this.rsServices == null) {
8512      return;
8513    }
8514    requestFlush0(FlushLifeCycleTracker.DUMMY);
8515  }
8516
8517  private void requestFlush0(FlushLifeCycleTracker tracker) {
8518    boolean shouldFlush = false;
8519    synchronized (writestate) {
8520      if (!this.writestate.isFlushRequested()) {
8521        shouldFlush = true;
8522        writestate.flushRequested = true;
8523      }
8524    }
8525    if (shouldFlush) {
8526      // Make request outside of synchronize block; HBASE-818.
8527      this.rsServices.getFlushRequester().requestFlush(this, false, tracker);
8528      if (LOG.isDebugEnabled()) {
8529        LOG.debug("Flush requested on " + this.getRegionInfo().getEncodedName());
8530      }
8531    } else {
8532      tracker.notExecuted("Flush already requested on " + this);
8533    }
8534  }
8535
8536  @Override
8537  public void requestFlush(FlushLifeCycleTracker tracker) throws IOException {
8538    requestFlush0(tracker);
8539  }
8540
8541}