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