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