View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.wal;
20  
21  import java.io.EOFException;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.InterruptedIOException;
25  import java.text.ParseException;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.NavigableSet;
34  import java.util.Set;
35  import java.util.TreeMap;
36  import java.util.TreeSet;
37  import java.util.UUID;
38  import java.util.concurrent.Callable;
39  import java.util.concurrent.CompletionService;
40  import java.util.concurrent.ConcurrentHashMap;
41  import java.util.concurrent.ExecutionException;
42  import java.util.concurrent.ExecutorCompletionService;
43  import java.util.concurrent.Future;
44  import java.util.concurrent.ThreadFactory;
45  import java.util.concurrent.ThreadPoolExecutor;
46  import java.util.concurrent.TimeUnit;
47  import java.util.concurrent.atomic.AtomicBoolean;
48  import java.util.concurrent.atomic.AtomicLong;
49  import java.util.concurrent.atomic.AtomicReference;
50  import java.util.regex.Matcher;
51  import java.util.regex.Pattern;
52  
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  import org.apache.hadoop.conf.Configuration;
56  import org.apache.hadoop.fs.FileAlreadyExistsException;
57  import org.apache.hadoop.fs.FileStatus;
58  import org.apache.hadoop.fs.FileSystem;
59  import org.apache.hadoop.fs.Path;
60  import org.apache.hadoop.fs.PathFilter;
61  import org.apache.hadoop.hbase.Cell;
62  import org.apache.hadoop.hbase.CellScanner;
63  import org.apache.hadoop.hbase.CellUtil;
64  import org.apache.hadoop.hbase.CoordinatedStateException;
65  import org.apache.hadoop.hbase.CoordinatedStateManager;
66  import org.apache.hadoop.hbase.HBaseConfiguration;
67  import org.apache.hadoop.hbase.HConstants;
68  import org.apache.hadoop.hbase.HRegionInfo;
69  import org.apache.hadoop.hbase.HRegionLocation;
70  import org.apache.hadoop.hbase.RemoteExceptionHandler;
71  import org.apache.hadoop.hbase.ServerName;
72  import org.apache.hadoop.hbase.TableName;
73  import org.apache.hadoop.hbase.TableNotFoundException;
74  import org.apache.hadoop.hbase.TableStateManager;
75  import org.apache.hadoop.hbase.classification.InterfaceAudience;
76  import org.apache.hadoop.hbase.client.ConnectionUtils;
77  import org.apache.hadoop.hbase.client.Delete;
78  import org.apache.hadoop.hbase.client.Durability;
79  import org.apache.hadoop.hbase.client.HConnection;
80  import org.apache.hadoop.hbase.client.HConnectionManager;
81  import org.apache.hadoop.hbase.client.Mutation;
82  import org.apache.hadoop.hbase.client.Put;
83  import org.apache.hadoop.hbase.coordination.BaseCoordinatedStateManager;
84  import org.apache.hadoop.hbase.coordination.ZKSplitLogManagerCoordination;
85  import org.apache.hadoop.hbase.exceptions.RegionOpeningException;
86  import org.apache.hadoop.hbase.io.HeapSize;
87  import org.apache.hadoop.hbase.master.SplitLogManager;
88  import org.apache.hadoop.hbase.monitoring.MonitoredTask;
89  import org.apache.hadoop.hbase.monitoring.TaskMonitor;
90  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
91  import org.apache.hadoop.hbase.protobuf.RequestConverter;
92  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.AdminService.BlockingInterface;
93  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoRequest;
94  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoResponse;
95  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
96  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType;
97  import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.RegionStoreSequenceIds;
98  import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.StoreSequenceId;
99  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
100 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor;
101 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
102 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos.SplitLogTask.RecoveryMode;
103 import org.apache.hadoop.hbase.regionserver.HRegion;
104 import org.apache.hadoop.hbase.regionserver.LastSequenceId;
105 // imports for things that haven't moved from regionserver.wal yet.
106 import org.apache.hadoop.hbase.regionserver.wal.FSHLog;
107 import org.apache.hadoop.hbase.regionserver.wal.HLogKey;
108 import org.apache.hadoop.hbase.regionserver.wal.WALCellCodec;
109 import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
110 import org.apache.hadoop.hbase.regionserver.wal.WALEditsReplaySink;
111 import org.apache.hadoop.hbase.util.Bytes;
112 import org.apache.hadoop.hbase.util.CancelableProgressable;
113 import org.apache.hadoop.hbase.util.ClassSize;
114 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
115 import org.apache.hadoop.hbase.util.FSUtils;
116 import org.apache.hadoop.hbase.util.Pair;
117 import org.apache.hadoop.hbase.util.Threads;
118 import org.apache.hadoop.hbase.wal.WAL.Entry;
119 import org.apache.hadoop.hbase.wal.WAL.Reader;
120 import org.apache.hadoop.hbase.wal.WALProvider.Writer;
121 import org.apache.hadoop.hbase.zookeeper.ZKSplitLog;
122 import org.apache.hadoop.io.MultipleIOException;
123 
124 import com.google.common.annotations.VisibleForTesting;
125 import com.google.common.base.Preconditions;
126 import com.google.common.collect.Lists;
127 import com.google.protobuf.ServiceException;
128 import com.google.protobuf.TextFormat;
129 
130 /**
131  * This class is responsible for splitting up a bunch of regionserver commit log
132  * files that are no longer being written to, into new files, one per region for
133  * region to replay on startup. Delete the old log files when finished.
134  */
135 @InterfaceAudience.Private
136 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
137   justification="Synchronization on concurrent map is intended")
138 public class WALSplitter {
139   private static final Log LOG = LogFactory.getLog(WALSplitter.class);
140 
141   /** By default we retry errors in splitting, rather than skipping. */
142   public static final boolean SPLIT_SKIP_ERRORS_DEFAULT = false;
143 
144   // Parameters for split process
145   protected final Path walDir;
146   protected final FileSystem walFS;
147   protected final Configuration conf;
148 
149   // Major subcomponents of the split process.
150   // These are separated into inner classes to make testing easier.
151   PipelineController controller;
152   OutputSink outputSink;
153   EntryBuffers entryBuffers;
154 
155   private Set<TableName> disablingOrDisabledTables =
156       new HashSet<TableName>();
157   private BaseCoordinatedStateManager csm;
158   private final WALFactory walFactory;
159 
160   private MonitoredTask status;
161 
162   // For checking the latest flushed sequence id
163   protected final LastSequenceId sequenceIdChecker;
164 
165   protected boolean distributedLogReplay;
166 
167   private final boolean splitWriterCreationBounded;
168 
169 
170   // Map encodedRegionName -> lastFlushedSequenceId
171   protected Map<String, Long> lastFlushedSequenceIds = new ConcurrentHashMap<String, Long>();
172 
173   // Map encodedRegionName -> maxSeqIdInStores
174   protected Map<String, Map<byte[], Long>> regionMaxSeqIdInStores =
175       new ConcurrentHashMap<String, Map<byte[], Long>>();
176 
177   // Failed region server that the wal file being split belongs to
178   protected String failedServerName = "";
179 
180   // Number of writer threads
181   private final int numWriterThreads;
182 
183   // Min batch size when replay WAL edits
184   private final int minBatchSize;
185 
186   // the file being split currently
187   private FileStatus fileBeingSplit;
188 
189   public final static String SPLIT_WRITER_CREATION_BOUNDED = "hbase.split.writer.creation.bounded";
190 
191   @VisibleForTesting
192   WALSplitter(final WALFactory factory, Configuration conf, Path walDir,
193       FileSystem walFS, LastSequenceId idChecker,
194       CoordinatedStateManager csm, RecoveryMode mode) {
195     this.conf = HBaseConfiguration.create(conf);
196     String codecClassName = conf
197         .get(WALCellCodec.WAL_CELL_CODEC_CLASS_KEY, WALCellCodec.class.getName());
198     this.conf.set(HConstants.RPC_CODEC_CONF_KEY, codecClassName);
199     this.walDir = walDir;
200     this.walFS = walFS;
201     this.sequenceIdChecker = idChecker;
202     this.csm = (BaseCoordinatedStateManager)csm;
203     this.walFactory = factory;
204     this.controller = new PipelineController();
205     this.splitWriterCreationBounded = conf.getBoolean(SPLIT_WRITER_CREATION_BOUNDED, false);
206     entryBuffers = new EntryBuffers(controller,
207         this.conf.getInt("hbase.regionserver.hlog.splitlog.buffersize",
208             128*1024*1024), splitWriterCreationBounded);
209 
210     // a larger minBatchSize may slow down recovery because replay writer has to wait for
211     // enough edits before replaying them
212     this.minBatchSize = this.conf.getInt("hbase.regionserver.wal.logreplay.batch.size", 64);
213     this.distributedLogReplay = (RecoveryMode.LOG_REPLAY == mode);
214 
215     this.numWriterThreads = this.conf.getInt("hbase.regionserver.hlog.splitlog.writer.threads", 3);
216     if (csm != null && this.distributedLogReplay) {
217       outputSink = new LogReplayOutputSink(controller, entryBuffers, numWriterThreads);
218     } else {
219       if (this.distributedLogReplay) {
220         LOG.info("ZooKeeperWatcher is passed in as NULL so disable distrubitedLogRepaly.");
221       }
222       this.distributedLogReplay = false;
223       if(splitWriterCreationBounded){
224         outputSink = new BoundedLogWriterCreationOutputSink(controller,
225             entryBuffers, numWriterThreads);
226       }else {
227         outputSink = new LogRecoveredEditsOutputSink(controller, entryBuffers, numWriterThreads);
228       }
229     }
230 
231   }
232 
233   /**
234    * Splits a WAL file into region's recovered-edits directory.
235    * This is the main entry point for distributed log splitting from SplitLogWorker.
236    * <p>
237    * If the log file has N regions then N recovered.edits files will be produced.
238    * <p>
239    * @param rootDir
240    * @param logfile
241    * @param walFS FileSystem to use for WAL reading and splitting
242    * @param conf
243    * @param reporter
244    * @param idChecker
245    * @param cp coordination state manager
246    * @return false if it is interrupted by the progress-able.
247    * @throws IOException
248    */
249   public static boolean splitLogFile(Path walDir, FileStatus logfile, FileSystem walFS,
250       Configuration conf, CancelableProgressable reporter, LastSequenceId idChecker,
251       CoordinatedStateManager cp, RecoveryMode mode, final WALFactory factory) throws IOException {
252     WALSplitter s = new WALSplitter(factory, conf, walDir, walFS, idChecker, cp, mode);
253     return s.splitLogFile(logfile, reporter);
254   }
255 
256   // A wrapper to split one log folder using the method used by distributed
257   // log splitting. Used by tools and unit tests. It should be package private.
258   // It is public only because UpgradeTo96 and TestWALObserver are in different packages,
259   // which uses this method to do log splitting.
260   public static List<Path> split(Path walRootDir, Path logDir, Path oldLogDir,
261       FileSystem walFs, Configuration conf, final WALFactory factory) throws IOException {
262     final FileStatus[] logfiles = SplitLogManager.getFileList(conf,
263         Collections.singletonList(logDir), null);
264     List<Path> splits = new ArrayList<Path>();
265     if (logfiles != null && logfiles.length > 0) {
266       for (FileStatus logfile: logfiles) {
267         WALSplitter s = new WALSplitter(factory, conf, walRootDir, walFs, null, null,
268             RecoveryMode.LOG_SPLITTING);
269         if (s.splitLogFile(logfile, null)) {
270           finishSplitLogFile(walRootDir, oldLogDir, logfile.getPath(), conf);
271           if (s.outputSink.splits != null) {
272             splits.addAll(s.outputSink.splits);
273           }
274         }
275       }
276     }
277     if (!walFs.delete(logDir, true)) {
278       throw new IOException("Unable to delete src dir: " + logDir);
279     }
280     return splits;
281   }
282 
283   /**
284    * log splitting implementation, splits one log file.
285    * @param logfile should be an actual log file.
286    */
287   @VisibleForTesting
288   boolean splitLogFile(FileStatus logfile, CancelableProgressable reporter) throws IOException {
289     Preconditions.checkState(status == null);
290     Preconditions.checkArgument(logfile.isFile(),
291         "passed in file status is for something other than a regular file.");
292     boolean isCorrupted = false;
293     boolean skipErrors = conf.getBoolean("hbase.hlog.split.skip.errors",
294       SPLIT_SKIP_ERRORS_DEFAULT);
295     int interval = conf.getInt("hbase.splitlog.report.interval.loglines", 1024);
296     Path logPath = logfile.getPath();
297     boolean outputSinkStarted = false;
298     boolean progress_failed = false;
299     int editsCount = 0;
300     int editsSkipped = 0;
301 
302     status =
303         TaskMonitor.get().createStatus(
304           "Splitting log file " + logfile.getPath() + "into a temporary staging area.");
305     Reader in = null;
306     this.fileBeingSplit = logfile;
307     try {
308       long logLength = logfile.getLen();
309       LOG.info("Splitting wal: " + logPath + ", length=" + logLength);
310       LOG.info("DistributedLogReplay = " + this.distributedLogReplay);
311       status.setStatus("Opening log file");
312       if (reporter != null && !reporter.progress()) {
313         progress_failed = true;
314         return false;
315       }
316       try {
317         in = getReader(logfile, skipErrors, reporter);
318       } catch (CorruptedLogFileException e) {
319         LOG.warn("Could not get reader, corrupted log file " + logPath, e);
320         ZKSplitLog.markCorrupted(walDir, logfile.getPath().getName(), walFS);
321         isCorrupted = true;
322       }
323       if (in == null) {
324         LOG.warn("Nothing to split in log file " + logPath);
325         return true;
326       }
327       if (csm != null) {
328         try {
329           TableStateManager tsm = csm.getTableStateManager();
330           disablingOrDisabledTables = tsm.getTablesInStates(
331           ZooKeeperProtos.Table.State.DISABLED, ZooKeeperProtos.Table.State.DISABLING);
332         } catch (CoordinatedStateException e) {
333           throw new IOException("Can't get disabling/disabled tables", e);
334         }
335       }
336       int numOpenedFilesBeforeReporting = conf.getInt("hbase.splitlog.report.openedfiles", 3);
337       int numOpenedFilesLastCheck = 0;
338       outputSink.setReporter(reporter);
339       outputSink.startWriterThreads();
340       outputSinkStarted = true;
341       Entry entry;
342       Long lastFlushedSequenceId = -1L;
343       ServerName serverName = DefaultWALProvider.getServerNameFromWALDirectoryName(logPath);
344       failedServerName = (serverName == null) ? "" : serverName.getServerName();
345       while ((entry = getNextLogLine(in, logPath, skipErrors)) != null) {
346         byte[] region = entry.getKey().getEncodedRegionName();
347         String encodedRegionNameAsStr = Bytes.toString(region);
348         lastFlushedSequenceId = lastFlushedSequenceIds.get(encodedRegionNameAsStr);
349         if (lastFlushedSequenceId == null) {
350           if (this.distributedLogReplay) {
351             RegionStoreSequenceIds ids =
352                 csm.getSplitLogWorkerCoordination().getRegionFlushedSequenceId(failedServerName,
353                   encodedRegionNameAsStr);
354             if (ids != null) {
355               lastFlushedSequenceId = ids.getLastFlushedSequenceId();
356               if (LOG.isDebugEnabled()) {
357                 LOG.debug("DLR Last flushed sequenceid for " + encodedRegionNameAsStr + ": " +
358                   TextFormat.shortDebugString(ids));
359               }
360             }
361           } else if (sequenceIdChecker != null) {
362             RegionStoreSequenceIds ids = sequenceIdChecker.getLastSequenceId(region);
363             Map<byte[], Long> maxSeqIdInStores = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
364             for (StoreSequenceId storeSeqId : ids.getStoreSequenceIdList()) {
365               maxSeqIdInStores.put(storeSeqId.getFamilyName().toByteArray(),
366                 storeSeqId.getSequenceId());
367             }
368             regionMaxSeqIdInStores.put(encodedRegionNameAsStr, maxSeqIdInStores);
369             lastFlushedSequenceId = ids.getLastFlushedSequenceId();
370             if (LOG.isDebugEnabled()) {
371               LOG.debug("DLS Last flushed sequenceid for " + encodedRegionNameAsStr + ": " +
372                   TextFormat.shortDebugString(ids));
373             }
374           }
375           if (lastFlushedSequenceId == null) {
376             lastFlushedSequenceId = -1L;
377           }
378           lastFlushedSequenceIds.put(encodedRegionNameAsStr, lastFlushedSequenceId);
379         }
380         if (lastFlushedSequenceId >= entry.getKey().getLogSeqNum()) {
381           editsSkipped++;
382           continue;
383         }
384         // Don't send Compaction/Close/Open region events to recovered edit type sinks.
385         if (entry.getEdit().isMetaEdit() && !outputSink.keepRegionEvent(entry)) {
386           editsSkipped++;
387           continue;
388         }
389         entryBuffers.appendEntry(entry);
390         editsCount++;
391         int moreWritersFromLastCheck = this.getNumOpenWriters() - numOpenedFilesLastCheck;
392         // If sufficient edits have passed, check if we should report progress.
393         if (editsCount % interval == 0
394             || moreWritersFromLastCheck > numOpenedFilesBeforeReporting) {
395           numOpenedFilesLastCheck = this.getNumOpenWriters();
396           String countsStr = (editsCount - (editsSkipped + outputSink.getSkippedEdits()))
397               + " edits, skipped " + editsSkipped + " edits.";
398           status.setStatus("Split " + countsStr);
399           if (reporter != null && !reporter.progress()) {
400             progress_failed = true;
401             return false;
402           }
403         }
404       }
405     } catch (InterruptedException ie) {
406       IOException iie = new InterruptedIOException();
407       iie.initCause(ie);
408       throw iie;
409     } catch (CorruptedLogFileException e) {
410       LOG.warn("Could not parse, corrupted log file " + logPath, e);
411       csm.getSplitLogWorkerCoordination().markCorrupted(walDir,
412         logfile.getPath().getName(), walFS);
413       isCorrupted = true;
414     } catch (IOException e) {
415       e = RemoteExceptionHandler.checkIOException(e);
416       throw e;
417     } finally {
418       LOG.debug("Finishing writing output logs and closing down.");
419       try {
420         if (null != in) {
421           in.close();
422         }
423       } catch (IOException exception) {
424         LOG.warn("Could not close wal reader: " + exception.getMessage());
425         LOG.debug("exception details", exception);
426       }
427       try {
428         if (outputSinkStarted) {
429           // Set progress_failed to true as the immediate following statement will reset its value
430           // when finishWritingAndClose() throws exception, progress_failed has the right value
431           progress_failed = true;
432           progress_failed = outputSink.finishWritingAndClose() == null;
433         }
434       } finally {
435         String msg =
436             "Processed " + editsCount + " edits across " + outputSink.getNumberOfRecoveredRegions()
437                 + " regions; edits skipped=" + editsSkipped + "; log file=" + logPath +
438                 ", length=" + logfile.getLen() + // See if length got updated post lease recovery
439                 ", corrupted=" + isCorrupted + ", progress failed=" + progress_failed;
440         LOG.info(msg);
441         status.markComplete(msg);
442       }
443     }
444     return !progress_failed;
445   }
446 
447   /**
448    * Completes the work done by splitLogFile by archiving logs
449    * <p>
450    * It is invoked by SplitLogManager once it knows that one of the
451    * SplitLogWorkers have completed the splitLogFile() part. If the master
452    * crashes then this function might get called multiple times.
453    * <p>
454    * @param logfile
455    * @param conf
456    * @throws IOException
457    */
458   public static void finishSplitLogFile(String logfile,
459       Configuration conf)  throws IOException {
460     Path walDir = FSUtils.getWALRootDir(conf);
461     Path oldLogDir = new Path(walDir, HConstants.HREGION_OLDLOGDIR_NAME);
462     Path logPath;
463     if (FSUtils.isStartingWithPath(walDir, logfile)) {
464       logPath = new Path(logfile);
465     } else {
466       logPath = new Path(walDir, logfile);
467     }
468     finishSplitLogFile(walDir, oldLogDir, logPath, conf);
469   }
470 
471   private static void finishSplitLogFile(Path walDir, Path oldLogDir,
472       Path logPath, Configuration conf) throws IOException {
473     List<Path> processedLogs = new ArrayList<Path>();
474     List<Path> corruptedLogs = new ArrayList<Path>();
475     FileSystem walFS = walDir.getFileSystem(conf);
476     if (ZKSplitLog.isCorrupted(walDir, logPath.getName(), walFS)) {
477       corruptedLogs.add(logPath);
478     } else {
479       processedLogs.add(logPath);
480     }
481     archiveLogs(corruptedLogs, processedLogs, oldLogDir, walFS, conf);
482     Path stagingDir = ZKSplitLog.getSplitLogDir(walDir, logPath.getName());
483     walFS.delete(stagingDir, true);
484   }
485 
486   /**
487    * Moves processed logs to a oldLogDir after successful processing Moves
488    * corrupted logs (any log that couldn't be successfully parsed to corruptDir
489    * (.corrupt) for later investigation
490    *
491    * @param corruptedLogs
492    * @param processedLogs
493    * @param oldLogDir
494    * @param walFS FileSystem to use for WAL archival
495    * @param conf
496    * @throws IOException
497    */
498   private static void archiveLogs(
499       final List<Path> corruptedLogs,
500       final List<Path> processedLogs, final Path oldLogDir,
501       final FileSystem walFS, final Configuration conf) throws IOException {
502     final Path corruptDir = new Path(FSUtils.getWALRootDir(conf), conf.get(
503         "hbase.regionserver.hlog.splitlog.corrupt.dir",  HConstants.CORRUPT_DIR_NAME));
504 
505     if (!walFS.mkdirs(corruptDir)) {
506       LOG.info("Unable to mkdir " + corruptDir);
507     }
508     walFS.mkdirs(oldLogDir);
509 
510     // this method can get restarted or called multiple times for archiving
511     // the same log files.
512     for (Path corrupted : corruptedLogs) {
513       Path p = new Path(corruptDir, corrupted.getName());
514       if (walFS.exists(corrupted)) {
515         if (!walFS.rename(corrupted, p)) {
516           LOG.warn("Unable to move corrupted log " + corrupted + " to " + p);
517         } else {
518           LOG.warn("Moved corrupted log " + corrupted + " to " + p);
519         }
520       }
521     }
522 
523     for (Path p : processedLogs) {
524       Path newPath = FSHLog.getWALArchivePath(oldLogDir, p);
525       if (walFS.exists(p)) {
526         if (!FSUtils.renameAndSetModifyTime(walFS, p, newPath)) {
527           LOG.warn("Unable to move  " + p + " to " + newPath);
528         } else {
529           LOG.info("Archived processed log " + p + " to " + newPath);
530         }
531       }
532     }
533   }
534 
535   /**
536    * Path to a file under RECOVERED_EDITS_DIR directory of the region found in
537    * <code>logEntry</code> named for the sequenceid in the passed
538    * <code>logEntry</code>: e.g. /hbase/some_table/2323432434/recovered.edits/2332.
539    * This method also ensures existence of RECOVERED_EDITS_DIR under the region
540    * creating it if necessary.
541    * @param logEntry
542    * @param fileNameBeingSplit the file being split currently. Used to generate tmp file name.
543    * @param tmpDirName of the directory used to sideline old recovered edits file
544    * @param conf
545    * @return Path to file into which to dump split log edits.
546    * @throws IOException
547    */
548   @SuppressWarnings("deprecation")
549   @VisibleForTesting
550   static Path getRegionSplitEditsPath(final Entry logEntry, String fileNameBeingSplit,
551       String tmpDirName, Configuration conf) throws IOException {
552     FileSystem walFS = FSUtils.getWALFileSystem(conf);
553     Path tableDir = FSUtils.getWALTableDir(conf, logEntry.getKey().getTablename());
554     String encodedRegionName = Bytes.toString(logEntry.getKey().getEncodedRegionName());
555     Path regionDir = HRegion.getRegionDir(tableDir, encodedRegionName);
556     Path dir = getRegionDirRecoveredEditsDir(regionDir);
557 
558     if (walFS.exists(dir) && walFS.isFile(dir)) {
559       Path tmp = new Path(tmpDirName);
560       if (!walFS.exists(tmp)) {
561         walFS.mkdirs(tmp);
562       }
563       tmp = new Path(tmp,
564         HConstants.RECOVERED_EDITS_DIR + "_" + encodedRegionName);
565       LOG.warn("Found existing old file: " + dir + ". It could be some "
566         + "leftover of an old installation. It should be a folder instead. "
567         + "So moving it to " + tmp);
568       if (!walFS.rename(dir, tmp)) {
569         LOG.warn("Failed to sideline old file " + dir);
570       }
571     }
572 
573     if (!walFS.exists(dir) && !walFS.mkdirs(dir)) {
574       LOG.warn("mkdir failed on " + dir);
575     }
576     // Append fileBeingSplit to prevent name conflict since we may have duplicate wal entries now.
577     // Append file name ends with RECOVERED_LOG_TMPFILE_SUFFIX to ensure
578     // region's replayRecoveredEdits will not delete it
579     String fileName = formatRecoveredEditsFileName(logEntry.getKey().getLogSeqNum());
580     fileName = getTmpRecoveredEditsFileName(fileName + "-" + fileNameBeingSplit);
581     return new Path(dir, fileName);
582   }
583 
584   private static String getTmpRecoveredEditsFileName(String fileName) {
585     return fileName + RECOVERED_LOG_TMPFILE_SUFFIX;
586   }
587 
588   /**
589    * Get the completed recovered edits file path, renaming it to be by last edit
590    * in the file from its first edit. Then we could use the name to skip
591    * recovered edits when doing {@link HRegion#replayRecoveredEditsIfAny}.
592    * @param srcPath
593    * @param maximumEditLogSeqNum
594    * @return dstPath take file's last edit log seq num as the name
595    */
596   private static Path getCompletedRecoveredEditsFilePath(Path srcPath,
597       long maximumEditLogSeqNum) {
598     String fileName = formatRecoveredEditsFileName(maximumEditLogSeqNum);
599     return new Path(srcPath.getParent(), fileName);
600   }
601 
602   @VisibleForTesting
603   static String formatRecoveredEditsFileName(final long seqid) {
604     return String.format("%019d", seqid);
605   }
606 
607   private static final Pattern EDITFILES_NAME_PATTERN = Pattern.compile("-?[0-9]+");
608   private static final String RECOVERED_LOG_TMPFILE_SUFFIX = ".temp";
609 
610   /**
611    * @param regionDir
612    *          This regions directory in the filesystem.
613    * @return The directory that holds recovered edits files for the region
614    *         <code>regionDir</code>
615    */
616   public static Path getRegionDirRecoveredEditsDir(final Path regionDir) {
617     return new Path(regionDir, HConstants.RECOVERED_EDITS_DIR);
618   }
619 
620   /**
621    * Returns sorted set of edit files made by splitter, excluding files
622    * with '.temp' suffix.
623    *
624    * @param walFS FileSystem to use for reading Recovered edits files
625    * @param regionDir Directory where Recovered edits should reside
626    * @return Files in passed <code>regionDir</code> as a sorted set.
627    * @throws IOException
628    */
629   public static NavigableSet<Path> getSplitEditFilesSorted(final FileSystem walFS,
630       final Path regionDir) throws IOException {
631     NavigableSet<Path> filesSorted = new TreeSet<Path>();
632     Path editsdir = getRegionDirRecoveredEditsDir(regionDir);
633     if (!walFS.exists(editsdir)) {
634       return filesSorted;
635     }
636     FileStatus[] files = FSUtils.listStatus(walFS, editsdir, new PathFilter() {
637       @Override
638       public boolean accept(Path p) {
639         boolean result = false;
640         try {
641           // Return files and only files that match the editfile names pattern.
642           // There can be other files in this directory other than edit files.
643           // In particular, on error, we'll move aside the bad edit file giving
644           // it a timestamp suffix. See moveAsideBadEditsFile.
645           Matcher m = EDITFILES_NAME_PATTERN.matcher(p.getName());
646           result = walFS.isFile(p) && m.matches();
647           // Skip the file whose name ends with RECOVERED_LOG_TMPFILE_SUFFIX,
648           // because it means splitwal thread is writting this file.
649           if (p.getName().endsWith(RECOVERED_LOG_TMPFILE_SUFFIX)) {
650             result = false;
651           }
652           // Skip SeqId Files
653           if (isSequenceIdFile(p)) {
654             result = false;
655           }
656         } catch (IOException e) {
657           LOG.warn("Failed isFile check on " + p);
658         }
659         return result;
660       }
661     });
662     if (files == null) {
663       return filesSorted;
664     }
665     for (FileStatus status : files) {
666       filesSorted.add(status.getPath());
667     }
668     return filesSorted;
669   }
670 
671   /**
672    * Move aside a bad edits file.
673    *
674    * @param walFS FileSystem to use for WAL operations
675    * @param edits
676    *          Edits file to move aside.
677    * @return The name of the moved aside file.
678    * @throws IOException
679    */
680   public static Path moveAsideBadEditsFile(final FileSystem walFS, final Path edits)
681       throws IOException {
682     Path moveAsideName = new Path(edits.getParent(), edits.getName() + "."
683         + System.currentTimeMillis());
684     if (!walFS.rename(edits, moveAsideName)) {
685       LOG.warn("Rename failed from " + edits + " to " + moveAsideName);
686     }
687     return moveAsideName;
688   }
689 
690   private static final String SEQUENCE_ID_FILE_SUFFIX = ".seqid";
691   private static final String OLD_SEQUENCE_ID_FILE_SUFFIX = "_seqid";
692   private static final int SEQUENCE_ID_FILE_SUFFIX_LENGTH = SEQUENCE_ID_FILE_SUFFIX.length();
693 
694   /**
695    * Is the given file a region open sequence id file.
696    */
697   @VisibleForTesting
698   public static boolean isSequenceIdFile(final Path file) {
699     return file.getName().endsWith(SEQUENCE_ID_FILE_SUFFIX)
700         || file.getName().endsWith(OLD_SEQUENCE_ID_FILE_SUFFIX);
701   }
702 
703   /**
704    * Create a file with name as region open sequence id
705    * @param walFS FileSystem to write Sequence file to
706    * @param regionDir WALRegionDir used to determine where to write edits files
707    * @param newSeqId
708    * @param saftyBumper
709    * @return long new sequence Id value
710    * @throws IOException
711    */
712   public static long writeRegionSequenceIdFile(final FileSystem walFS, final Path regionDir,
713       long newSeqId, long saftyBumper) throws IOException {
714 
715     Path editsdir = WALSplitter.getRegionDirRecoveredEditsDir(regionDir);
716     long maxSeqId = 0;
717     FileStatus[] files = null;
718     if (walFS.exists(editsdir)) {
719       files = FSUtils.listStatus(walFS, editsdir, new PathFilter() {
720         @Override
721         public boolean accept(Path p) {
722           return isSequenceIdFile(p);
723         }
724       });
725       if (files != null) {
726         for (FileStatus status : files) {
727           String fileName = status.getPath().getName();
728           try {
729             Long tmpSeqId = Long.parseLong(fileName.substring(0, fileName.length()
730                 - SEQUENCE_ID_FILE_SUFFIX_LENGTH));
731             maxSeqId = Math.max(tmpSeqId, maxSeqId);
732           } catch (NumberFormatException ex) {
733             LOG.warn("Invalid SeqId File Name=" + fileName);
734           }
735         }
736       }
737     }
738     if (maxSeqId > newSeqId) {
739       newSeqId = maxSeqId;
740     }
741     newSeqId += saftyBumper; // bump up SeqId
742 
743     // write a new seqId file
744     Path newSeqIdFile = new Path(editsdir, newSeqId + SEQUENCE_ID_FILE_SUFFIX);
745     if (newSeqId != maxSeqId) {
746       try {
747         if (!walFS.createNewFile(newSeqIdFile) && !walFS.exists(newSeqIdFile)) {
748           throw new IOException("Failed to create SeqId file:" + newSeqIdFile);
749         }
750         if (LOG.isDebugEnabled()) {
751           LOG.debug("Wrote region seqId=" + newSeqIdFile + " to file, newSeqId=" + newSeqId
752               + ", maxSeqId=" + maxSeqId);
753         }
754       } catch (FileAlreadyExistsException ignored) {
755         // latest hdfs throws this exception. it's all right if newSeqIdFile already exists
756       }
757     }
758     // remove old ones
759     if (files != null) {
760       for (FileStatus status : files) {
761         if (newSeqIdFile.equals(status.getPath())) {
762           continue;
763         }
764         walFS.delete(status.getPath(), false);
765       }
766     }
767     return newSeqId;
768   }
769 
770   /**
771    * Create a new {@link Reader} for reading logs to split.
772    *
773    * @param file
774    * @return A new Reader instance, caller should close
775    * @throws IOException
776    * @throws CorruptedLogFileException
777    */
778   protected Reader getReader(FileStatus file, boolean skipErrors, CancelableProgressable reporter)
779       throws IOException, CorruptedLogFileException {
780     Path path = file.getPath();
781     long length = file.getLen();
782     Reader in;
783 
784     // Check for possibly empty file. With appends, currently Hadoop reports a
785     // zero length even if the file has been sync'd. Revisit if HDFS-376 or
786     // HDFS-878 is committed.
787     if (length <= 0) {
788       LOG.warn("File " + path + " might be still open, length is 0");
789     }
790 
791     try {
792       FSUtils.getInstance(walFS, conf).recoverFileLease(walFS, path, conf, reporter);
793       try {
794         in = getReader(path, reporter);
795       } catch (EOFException e) {
796         if (length <= 0) {
797           // TODO should we ignore an empty, not-last log file if skip.errors
798           // is false? Either way, the caller should decide what to do. E.g.
799           // ignore if this is the last log in sequence.
800           // TODO is this scenario still possible if the log has been
801           // recovered (i.e. closed)
802           LOG.warn("Could not open " + path + " for reading. File is empty", e);
803           return null;
804         } else {
805           // EOFException being ignored
806           return null;
807         }
808       }
809     } catch (IOException e) {
810       if (e instanceof FileNotFoundException) {
811         // A wal file may not exist anymore. Nothing can be recovered so move on
812         LOG.warn("File " + path + " doesn't exist anymore.", e);
813         return null;
814       }
815       if (!skipErrors || e instanceof InterruptedIOException) {
816         throw e; // Don't mark the file corrupted if interrupted, or not skipErrors
817       }
818       CorruptedLogFileException t =
819         new CorruptedLogFileException("skipErrors=true Could not open wal " +
820             path + " ignoring");
821       t.initCause(e);
822       throw t;
823     }
824     return in;
825   }
826 
827   static private Entry getNextLogLine(Reader in, Path path, boolean skipErrors)
828   throws CorruptedLogFileException, IOException {
829     try {
830       return in.next();
831     } catch (EOFException eof) {
832       // truncated files are expected if a RS crashes (see HBASE-2643)
833       LOG.info("EOF from wal " + path + ".  continuing");
834       return null;
835     } catch (IOException e) {
836       // If the IOE resulted from bad file format,
837       // then this problem is idempotent and retrying won't help
838       if (e.getCause() != null &&
839           (e.getCause() instanceof ParseException ||
840            e.getCause() instanceof org.apache.hadoop.fs.ChecksumException)) {
841         LOG.warn("Parse exception " + e.getCause().toString() + " from wal "
842            + path + ".  continuing");
843         return null;
844       }
845       if (!skipErrors) {
846         throw e;
847       }
848       CorruptedLogFileException t =
849         new CorruptedLogFileException("skipErrors=true Ignoring exception" +
850             " while parsing wal " + path + ". Marking as corrupted");
851       t.initCause(e);
852       throw t;
853     }
854   }
855 
856   /**
857    * Create a new {@link Writer} for writing log splits.
858    * @return a new Writer instance, caller should close
859    */
860   protected Writer createWriter(Path logfile)
861       throws IOException {
862     return walFactory.createRecoveredEditsWriter(walFS, logfile);
863   }
864 
865   /**
866    * Create a new {@link Reader} for reading logs to split.
867    * @return new Reader instance, caller should close
868    */
869   protected Reader getReader(Path curLogFile, CancelableProgressable reporter) throws IOException {
870     return walFactory.createReader(walFS, curLogFile, reporter);
871   }
872 
873   /**
874    * Get current open writers
875    */
876   private int getNumOpenWriters() {
877     int result = 0;
878     if (this.outputSink != null) {
879       result += this.outputSink.getNumOpenWriters();
880     }
881     return result;
882   }
883 
884   /**
885    * Contains some methods to control WAL-entries producer / consumer interactions
886    */
887   public static class PipelineController {
888     // If an exception is thrown by one of the other threads, it will be
889     // stored here.
890     AtomicReference<Throwable> thrown = new AtomicReference<Throwable>();
891 
892     // Wait/notify for when data has been produced by the writer thread,
893     // consumed by the reader thread, or an exception occurred
894     public final Object dataAvailable = new Object();
895 
896     void writerThreadError(Throwable t) {
897       thrown.compareAndSet(null, t);
898     }
899 
900     /**
901      * Check for errors in the writer threads. If any is found, rethrow it.
902      */
903     void checkForErrors() throws IOException {
904       Throwable thrown = this.thrown.get();
905       if (thrown == null) return;
906       if (thrown instanceof IOException) {
907         throw new IOException(thrown);
908       } else {
909         throw new RuntimeException(thrown);
910       }
911     }
912   }
913 
914   /**
915    * Class which accumulates edits and separates them into a buffer per region
916    * while simultaneously accounting RAM usage. Blocks if the RAM usage crosses
917    * a predefined threshold.
918    *
919    * Writer threads then pull region-specific buffers from this class.
920    */
921   public static class EntryBuffers {
922     PipelineController controller;
923 
924     Map<byte[], RegionEntryBuffer> buffers =
925       new TreeMap<byte[], RegionEntryBuffer>(Bytes.BYTES_COMPARATOR);
926 
927     /* Track which regions are currently in the middle of writing. We don't allow
928        an IO thread to pick up bytes from a region if we're already writing
929        data for that region in a different IO thread. */
930     Set<byte[]> currentlyWriting = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
931 
932     long totalBuffered = 0;
933     final long maxHeapUsage;
934     boolean splitWriterCreationBounded;
935 
936 
937     public EntryBuffers(PipelineController controller, long maxHeapUsage) {
938       this(controller, maxHeapUsage, false);
939     }
940 
941     public EntryBuffers(PipelineController controller, long maxHeapUsage,
942         boolean splitWriterCreationBounded) {
943       this.controller = controller;
944       this.maxHeapUsage = maxHeapUsage;
945       this.splitWriterCreationBounded = splitWriterCreationBounded;
946     }
947 
948     /**
949      * Append a log entry into the corresponding region buffer.
950      * Blocks if the total heap usage has crossed the specified threshold.
951      *
952      * @throws InterruptedException
953      * @throws IOException
954      */
955     public void appendEntry(Entry entry) throws InterruptedException, IOException {
956       WALKey key = entry.getKey();
957 
958       RegionEntryBuffer buffer;
959       long incrHeap;
960       synchronized (this) {
961         buffer = buffers.get(key.getEncodedRegionName());
962         if (buffer == null) {
963           buffer = new RegionEntryBuffer(key.getTablename(), key.getEncodedRegionName());
964           buffers.put(key.getEncodedRegionName(), buffer);
965         }
966         incrHeap= buffer.appendEntry(entry);
967       }
968 
969       // If we crossed the chunk threshold, wait for more space to be available
970       synchronized (controller.dataAvailable) {
971         totalBuffered += incrHeap;
972         while (totalBuffered > maxHeapUsage && controller.thrown.get() == null) {
973           LOG.debug("Used " + totalBuffered + " bytes of buffered edits, waiting for IO threads...");
974           controller.dataAvailable.wait(2000);
975         }
976         controller.dataAvailable.notifyAll();
977       }
978       controller.checkForErrors();
979     }
980 
981     /**
982      * @return RegionEntryBuffer a buffer of edits to be written or replayed.
983      */
984     synchronized RegionEntryBuffer getChunkToWrite() {
985       // The core part of limiting opening writers is it doesn't return chunk only if the heap size
986       // is over maxHeapUsage. Thus it doesn't need to create a writer for each region
987       // during splitting. It will flush all the logs in the buffer after splitting through a
988       // threadpool, which means the number of writers it created is under control
989       if(splitWriterCreationBounded && totalBuffered < maxHeapUsage){
990         return null;
991       }
992 
993       long biggestSize = 0;
994       byte[] biggestBufferKey = null;
995 
996       for (Map.Entry<byte[], RegionEntryBuffer> entry : buffers.entrySet()) {
997         long size = entry.getValue().heapSize();
998         if (size > biggestSize && (!currentlyWriting.contains(entry.getKey()))) {
999           biggestSize = size;
1000           biggestBufferKey = entry.getKey();
1001         }
1002       }
1003       if (biggestBufferKey == null) {
1004         return null;
1005       }
1006 
1007       RegionEntryBuffer buffer = buffers.remove(biggestBufferKey);
1008       currentlyWriting.add(biggestBufferKey);
1009       return buffer;
1010     }
1011 
1012     void doneWriting(RegionEntryBuffer buffer) {
1013       synchronized (this) {
1014         boolean removed = currentlyWriting.remove(buffer.encodedRegionName);
1015         assert removed;
1016       }
1017       long size = buffer.heapSize();
1018 
1019       synchronized (controller.dataAvailable) {
1020         totalBuffered -= size;
1021         // We may unblock writers
1022         controller.dataAvailable.notifyAll();
1023       }
1024     }
1025 
1026     synchronized boolean isRegionCurrentlyWriting(byte[] region) {
1027       return currentlyWriting.contains(region);
1028     }
1029 
1030     public void waitUntilDrained() {
1031       synchronized (controller.dataAvailable) {
1032         while (totalBuffered > 0) {
1033           try {
1034             controller.dataAvailable.wait(2000);
1035           } catch (InterruptedException e) {
1036             LOG.warn("Got interrupted while waiting for EntryBuffers is drained");
1037             Thread.interrupted();
1038             break;
1039           }
1040         }
1041       }
1042     }
1043   }
1044 
1045   /**
1046    * A buffer of some number of edits for a given region.
1047    * This accumulates edits and also provides a memory optimization in order to
1048    * share a single byte array instance for the table and region name.
1049    * Also tracks memory usage of the accumulated edits.
1050    */
1051   public static class RegionEntryBuffer implements HeapSize {
1052     long heapInBuffer = 0;
1053     List<Entry> entryBuffer;
1054     TableName tableName;
1055     byte[] encodedRegionName;
1056 
1057     RegionEntryBuffer(TableName tableName, byte[] region) {
1058       this.tableName = tableName;
1059       this.encodedRegionName = region;
1060       this.entryBuffer = new LinkedList<Entry>();
1061     }
1062 
1063     long appendEntry(Entry entry) {
1064       internify(entry);
1065       entryBuffer.add(entry);
1066       long incrHeap = entry.getEdit().heapSize() +
1067         ClassSize.align(2 * ClassSize.REFERENCE) + // WALKey pointers
1068         0; // TODO linkedlist entry
1069       heapInBuffer += incrHeap;
1070       return incrHeap;
1071     }
1072 
1073     private void internify(Entry entry) {
1074       WALKey k = entry.getKey();
1075       k.internTableName(this.tableName);
1076       k.internEncodedRegionName(this.encodedRegionName);
1077     }
1078 
1079     @Override
1080     public long heapSize() {
1081       return heapInBuffer;
1082     }
1083 
1084     public byte[] getEncodedRegionName() {
1085       return encodedRegionName;
1086     }
1087 
1088     public List<Entry> getEntryBuffer() {
1089       return entryBuffer;
1090     }
1091 
1092     public TableName getTableName() {
1093       return tableName;
1094     }
1095   }
1096 
1097   public static class WriterThread extends Thread {
1098     private volatile boolean shouldStop = false;
1099     private PipelineController controller;
1100     private EntryBuffers entryBuffers;
1101     private OutputSink outputSink = null;
1102 
1103     WriterThread(PipelineController controller, EntryBuffers entryBuffers, OutputSink sink, int i){
1104       super(Thread.currentThread().getName() + "-Writer-" + i);
1105       this.controller = controller;
1106       this.entryBuffers = entryBuffers;
1107       outputSink = sink;
1108     }
1109 
1110     @Override
1111     public void run()  {
1112       try {
1113         doRun();
1114       } catch (Throwable t) {
1115         LOG.error("Exiting thread", t);
1116         controller.writerThreadError(t);
1117       }
1118     }
1119 
1120     private void doRun() throws IOException {
1121       if (LOG.isTraceEnabled()) LOG.trace("Writer thread starting");
1122       while (true) {
1123         RegionEntryBuffer buffer = entryBuffers.getChunkToWrite();
1124         if (buffer == null) {
1125           // No data currently available, wait on some more to show up
1126           synchronized (controller.dataAvailable) {
1127             if (shouldStop && !this.outputSink.flush()) {
1128               return;
1129             }
1130             try {
1131               controller.dataAvailable.wait(500);
1132             } catch (InterruptedException ie) {
1133               if (!shouldStop) {
1134                 throw new RuntimeException(ie);
1135               }
1136             }
1137           }
1138           continue;
1139         }
1140 
1141         assert buffer != null;
1142         try {
1143           writeBuffer(buffer);
1144         } finally {
1145           entryBuffers.doneWriting(buffer);
1146         }
1147       }
1148     }
1149 
1150     private void writeBuffer(RegionEntryBuffer buffer) throws IOException {
1151       outputSink.append(buffer);
1152     }
1153 
1154     void finish() {
1155       synchronized (controller.dataAvailable) {
1156         shouldStop = true;
1157         controller.dataAvailable.notifyAll();
1158       }
1159     }
1160   }
1161 
1162   /**
1163    * The following class is an abstraction class to provide a common interface to support both
1164    * existing recovered edits file sink and region server WAL edits replay sink
1165    */
1166   public static abstract class OutputSink {
1167 
1168     protected PipelineController controller;
1169     protected EntryBuffers entryBuffers;
1170 
1171     protected ConcurrentHashMap<String, SinkWriter> writers = new ConcurrentHashMap<>();
1172     protected ConcurrentHashMap<String, Long> regionMaximumEditLogSeqNum =
1173         new ConcurrentHashMap<>();
1174 
1175     protected final List<WriterThread> writerThreads = Lists.newArrayList();
1176 
1177     /* Set of regions which we've decided should not output edits */
1178     protected final Set<byte[]> blacklistedRegions = Collections
1179         .synchronizedSet(new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR));
1180 
1181     protected boolean closeAndCleanCompleted = false;
1182 
1183     protected boolean writersClosed = false;
1184 
1185     protected final int numThreads;
1186 
1187     protected CancelableProgressable reporter = null;
1188 
1189     protected AtomicLong skippedEdits = new AtomicLong();
1190 
1191     protected List<Path> splits = null;
1192 
1193     public OutputSink(PipelineController controller, EntryBuffers entryBuffers, int numWriters) {
1194       numThreads = numWriters;
1195       this.controller = controller;
1196       this.entryBuffers = entryBuffers;
1197     }
1198 
1199     void setReporter(CancelableProgressable reporter) {
1200       this.reporter = reporter;
1201     }
1202 
1203     /**
1204      * Start the threads that will pump data from the entryBuffers to the output files.
1205      */
1206     public synchronized void startWriterThreads() {
1207       for (int i = 0; i < numThreads; i++) {
1208         WriterThread t = new WriterThread(controller, entryBuffers, this, i);
1209         t.start();
1210         writerThreads.add(t);
1211       }
1212     }
1213 
1214     /**
1215      *
1216      * Update region's maximum edit log SeqNum.
1217      */
1218     void updateRegionMaximumEditLogSeqNum(Entry entry) {
1219 
1220       synchronized (regionMaximumEditLogSeqNum) {
1221         String encodedRegionName = Bytes.toString(entry.getKey().getEncodedRegionName());
1222         Long currentMaxSeqNum = regionMaximumEditLogSeqNum.get(encodedRegionName);
1223         if (currentMaxSeqNum == null || entry.getKey().getLogSeqNum() > currentMaxSeqNum) {
1224           regionMaximumEditLogSeqNum.put(encodedRegionName, entry.getKey()
1225               .getLogSeqNum());
1226         }
1227       }
1228     }
1229 
1230     Long getRegionMaximumEditLogSeqNum(byte[] region) {
1231       return regionMaximumEditLogSeqNum.get(Bytes.toString(region));
1232     }
1233 
1234     /**
1235      * @return the number of currently opened writers
1236      */
1237     int getNumOpenWriters() {
1238       return this.writers.size();
1239     }
1240 
1241     long getSkippedEdits() {
1242       return this.skippedEdits.get();
1243     }
1244 
1245     /**
1246      * Wait for writer threads to dump all info to the sink
1247      * @return true when there is no error
1248      * @throws IOException
1249      */
1250     protected boolean finishWriting(boolean interrupt) throws IOException {
1251       LOG.debug("Waiting for split writer threads to finish");
1252       boolean progress_failed = false;
1253       for (WriterThread t : writerThreads) {
1254         t.finish();
1255       }
1256       if (interrupt) {
1257         for (WriterThread t : writerThreads) {
1258           t.interrupt(); // interrupt the writer threads. We are stopping now.
1259         }
1260       }
1261 
1262       for (WriterThread t : writerThreads) {
1263         if (!progress_failed && reporter != null && !reporter.progress()) {
1264           progress_failed = true;
1265         }
1266         try {
1267           t.join();
1268         } catch (InterruptedException ie) {
1269           IOException iie = new InterruptedIOException();
1270           iie.initCause(ie);
1271           throw iie;
1272         }
1273       }
1274       controller.checkForErrors();
1275       LOG.info(this.writerThreads.size() + " split writers finished; closing...");
1276       return (!progress_failed);
1277     }
1278 
1279     public abstract List<Path> finishWritingAndClose() throws IOException;
1280 
1281     /**
1282      * @return a map from encoded region ID to the number of edits written out for that region.
1283      */
1284     public abstract Map<byte[], Long> getOutputCounts();
1285 
1286     /**
1287      * @return number of regions we've recovered
1288      */
1289     public abstract int getNumberOfRecoveredRegions();
1290 
1291     /**
1292      * @param buffer A WAL Edit Entry
1293      * @throws IOException
1294      */
1295     public abstract void append(RegionEntryBuffer buffer) throws IOException;
1296 
1297     /**
1298      * WriterThread call this function to help flush internal remaining edits in buffer before close
1299      * @return true when underlying sink has something to flush
1300      */
1301     public boolean flush() throws IOException {
1302       return false;
1303     }
1304 
1305     /**
1306      * Some WALEdit's contain only KV's for account on what happened to a region.
1307      * Not all sinks will want to get all of those edits.
1308      *
1309      * @return Return true if this sink wants to accept this region-level WALEdit.
1310      */
1311     public abstract boolean keepRegionEvent(Entry entry);
1312   }
1313 
1314   /**
1315    * Class that manages the output streams from the log splitting process.
1316    */
1317   class LogRecoveredEditsOutputSink extends OutputSink {
1318 
1319     public LogRecoveredEditsOutputSink(PipelineController controller, EntryBuffers entryBuffers,
1320         int numWriters) {
1321       // More threads could potentially write faster at the expense
1322       // of causing more disk seeks as the logs are split.
1323       // 3. After a certain setting (probably around 3) the
1324       // process will be bound on the reader in the current
1325       // implementation anyway.
1326       super(controller, entryBuffers, numWriters);
1327     }
1328 
1329     /**
1330      * @return null if failed to report progress
1331      * @throws IOException
1332      */
1333     @Override
1334     public List<Path> finishWritingAndClose() throws IOException {
1335       boolean isSuccessful = false;
1336       List<Path> result = null;
1337       try {
1338         isSuccessful = finishWriting(false);
1339       } finally {
1340         result = close();
1341         List<IOException> thrown = closeLogWriters(null);
1342         if (thrown != null && !thrown.isEmpty()) {
1343           throw MultipleIOException.createIOException(thrown);
1344         }
1345       }
1346       if (isSuccessful) {
1347         splits = result;
1348       }
1349       return splits;
1350     }
1351 
1352     // delete the one with fewer wal entries
1353     void deleteOneWithFewerEntries(WriterAndPath wap, Path dst)
1354         throws IOException {
1355       long dstMinLogSeqNum = -1L;
1356       try (WAL.Reader reader = walFactory.createReader(walFS, dst)) {
1357         WAL.Entry entry = reader.next();
1358         if (entry != null) {
1359           dstMinLogSeqNum = entry.getKey().getLogSeqNum();
1360         }
1361       } catch (EOFException e) {
1362         if (LOG.isDebugEnabled()) {
1363           LOG.debug(
1364             "Got EOF when reading first WAL entry from " + dst + ", an empty or broken WAL file?",
1365             e);
1366         }
1367       }
1368       if (wap.minLogSeqNum < dstMinLogSeqNum) {
1369         LOG.warn("Found existing old edits file. It could be the result of a previous failed"
1370             + " split attempt or we have duplicated wal entries. Deleting " + dst + ", length="
1371             + walFS.getFileStatus(dst).getLen());
1372         if (!walFS.delete(dst, false)) {
1373           LOG.warn("Failed deleting of old " + dst);
1374           throw new IOException("Failed deleting of old " + dst);
1375         }
1376       } else {
1377         LOG.warn("Found existing old edits file and we have less entries. Deleting " + wap.p
1378             + ", length=" + walFS.getFileStatus(wap.p).getLen());
1379         if (!walFS.delete(wap.p, false)) {
1380           LOG.warn("Failed deleting of " + wap.p);
1381           throw new IOException("Failed deleting of " + wap.p);
1382         }
1383       }
1384     }
1385 
1386     /**
1387      * Close all of the output streams.
1388      * @return the list of paths written.
1389      */
1390     List<Path> close() throws IOException {
1391       Preconditions.checkState(!closeAndCleanCompleted);
1392 
1393       final List<Path> paths = new ArrayList<Path>();
1394       final List<IOException> thrown = Lists.newArrayList();
1395       ThreadPoolExecutor closeThreadPool = Threads.getBoundedCachedThreadPool(numThreads, 30L,
1396         TimeUnit.SECONDS, new ThreadFactory() {
1397           private int count = 1;
1398 
1399           @Override
1400           public Thread newThread(Runnable r) {
1401             Thread t = new Thread(r, "split-log-closeStream-" + count++);
1402             return t;
1403           }
1404         });
1405       CompletionService<Void> completionService =
1406         new ExecutorCompletionService<Void>(closeThreadPool);
1407       boolean progress_failed;
1408       try{
1409         progress_failed = executeCloseTask(completionService, thrown, paths);
1410       } catch (InterruptedException e) {
1411         IOException iie = new InterruptedIOException();
1412         iie.initCause(e);
1413         throw iie;
1414       } catch (ExecutionException e) {
1415         throw new IOException(e.getCause());
1416       } finally {
1417         closeThreadPool.shutdownNow();
1418       }
1419 
1420       if (!thrown.isEmpty()) {
1421         throw MultipleIOException.createIOException(thrown);
1422       }
1423       writersClosed = true;
1424       closeAndCleanCompleted = true;
1425       if (progress_failed) {
1426         return null;
1427       }
1428       return paths;
1429     }
1430 
1431     boolean executeCloseTask(CompletionService<Void> completionService,
1432         final List<IOException> thrown, final List<Path> paths)
1433         throws InterruptedException, ExecutionException {
1434       for (final Map.Entry<String, SinkWriter> writersEntry : writers.entrySet()) {
1435         if (LOG.isTraceEnabled()) {
1436           LOG.trace("Submitting close of " + ((WriterAndPath) writersEntry.getValue()).p);
1437         }
1438         completionService.submit(new Callable<Void>() {
1439           @Override public Void call() throws Exception {
1440             WriterAndPath wap = (WriterAndPath) writersEntry.getValue();
1441             Path dst = closeWriter(writersEntry.getKey(), wap, thrown);
1442             paths.add(dst);
1443             return null;
1444           }
1445         });
1446       }
1447       boolean progress_failed = false;
1448       for (int i = 0, n = this.writers.size(); i < n; i++) {
1449         Future<Void> future = completionService.take();
1450         future.get();
1451         if (!progress_failed && reporter != null && !reporter.progress()) {
1452           progress_failed = true;
1453         }
1454       }
1455       return progress_failed;
1456     }
1457 
1458     Path closeWriter(String encodedRegionName, WriterAndPath wap, List<IOException> thrown)
1459         throws IOException {
1460       if (LOG.isTraceEnabled()) {
1461         LOG.trace("Closing " + wap.p);
1462       }
1463 
1464       try {
1465         wap.w.close();
1466       } catch (IOException ioe) {
1467         LOG.error("Couldn't close log at " + wap.p, ioe);
1468         thrown.add(ioe);
1469         return null;
1470       }
1471       if (LOG.isDebugEnabled()) {
1472         LOG.debug("Closed wap " + wap.p + " (wrote " + wap.editsWritten
1473             + " edits, skipped " + wap.editsSkipped + " edits in "
1474             + (wap.nanosSpent / 1000 / 1000) + "ms");
1475       }
1476       if (wap.editsWritten == 0) {
1477         // just remove the empty recovered.edits file
1478         if (walFS.exists(wap.p) && !walFS.delete(wap.p, false)) {
1479           LOG.warn("Failed deleting empty " + wap.p);
1480           throw new IOException("Failed deleting empty  " + wap.p);
1481         }
1482         return null;
1483       }
1484 
1485       Path dst = getCompletedRecoveredEditsFilePath(wap.p,
1486           regionMaximumEditLogSeqNum.get(encodedRegionName));
1487       try {
1488         if (!dst.equals(wap.p) && walFS.exists(dst)) {
1489           deleteOneWithFewerEntries(wap, dst);
1490         }
1491         // Skip the unit tests which create a splitter that reads and
1492         // writes the data without touching disk.
1493         // TestHLogSplit#testThreading is an example.
1494         if (walFS.exists(wap.p)) {
1495           if (!walFS.rename(wap.p, dst)) {
1496             throw new IOException("Failed renaming " + wap.p + " to " + dst);
1497           }
1498           LOG.info("Rename " + wap.p + " to " + dst);
1499         }
1500       } catch (IOException ioe) {
1501         LOG.error("Couldn't rename " + wap.p + " to " + dst, ioe);
1502         thrown.add(ioe);
1503         return null;
1504       }
1505       return dst;
1506     }
1507 
1508 
1509     private List<IOException> closeLogWriters(List<IOException> thrown) throws IOException {
1510       if (writersClosed) {
1511         return thrown;
1512       }
1513 
1514       if (thrown == null) {
1515         thrown = Lists.newArrayList();
1516       }
1517       try {
1518         for (WriterThread t : writerThreads) {
1519           while (t.isAlive()) {
1520             t.shouldStop = true;
1521             t.interrupt();
1522             try {
1523               t.join(10);
1524             } catch (InterruptedException e) {
1525               IOException iie = new InterruptedIOException();
1526               iie.initCause(e);
1527               throw iie;
1528             }
1529           }
1530         }
1531       } finally {
1532         WriterAndPath wap = null;
1533         for (SinkWriter tmpWAP : writers.values()) {
1534           try {
1535             wap = (WriterAndPath) tmpWAP;
1536             wap.w.close();
1537           } catch (IOException ioe) {
1538             LOG.error("Couldn't close log at " + wap.p, ioe);
1539             thrown.add(ioe);
1540             continue;
1541           }
1542           LOG.info(
1543               "Closed log " + wap.p + " (wrote " + wap.editsWritten + " edits in " + (wap.nanosSpent
1544                   / 1000 / 1000) + "ms)");
1545         }
1546         writersClosed = true;
1547       }
1548 
1549       return thrown;
1550     }
1551 
1552     /**
1553      * Get a writer and path for a log starting at the given entry. This function is threadsafe so
1554      * long as multiple threads are always acting on different regions.
1555      * @return null if this region shouldn't output any logs
1556      */
1557     WriterAndPath getWriterAndPath(Entry entry, boolean reusable) throws IOException {
1558       byte region[] = entry.getKey().getEncodedRegionName();
1559       String regionName = Bytes.toString(region);
1560       WriterAndPath ret = (WriterAndPath) writers.get(regionName);
1561       if (ret != null) {
1562         return ret;
1563       }
1564       // If we already decided that this region doesn't get any output
1565       // we don't need to check again.
1566       if (blacklistedRegions.contains(region)) {
1567         return null;
1568       }
1569       ret = createWAP(region, entry);
1570       if (ret == null) {
1571         blacklistedRegions.add(region);
1572         return null;
1573       }
1574 
1575       if(reusable) {
1576         writers.put(regionName, ret);
1577       }
1578       return ret;
1579     }
1580 
1581     /**
1582      * @return a path with a write for that path. caller should close.
1583      */
1584     WriterAndPath createWAP(byte[] region, Entry entry) throws IOException {
1585       String tmpDirName = conf.get(HConstants.TEMPORARY_FS_DIRECTORY_KEY,
1586         HConstants.DEFAULT_TEMPORARY_HDFS_DIRECTORY);
1587       Path regionedits = getRegionSplitEditsPath(entry,
1588           fileBeingSplit.getPath().getName(), tmpDirName, conf);
1589       if (regionedits == null) {
1590         return null;
1591       }
1592       if (walFS.exists(regionedits)) {
1593         LOG.warn("Found old edits file. It could be the "
1594             + "result of a previous failed split attempt. Deleting " + regionedits + ", length="
1595             + walFS.getFileStatus(regionedits).getLen());
1596         if (!walFS.delete(regionedits, false)) {
1597           LOG.warn("Failed delete of old " + regionedits);
1598         }
1599       }
1600       Writer w = createWriter(regionedits);
1601       LOG.debug("Creating writer path=" + regionedits);
1602       return new WriterAndPath(regionedits, w, entry.getKey().getLogSeqNum());
1603     }
1604 
1605     void filterCellByStore(Entry logEntry) {
1606       Map<byte[], Long> maxSeqIdInStores =
1607           regionMaxSeqIdInStores.get(Bytes.toString(logEntry.getKey().getEncodedRegionName()));
1608       if (maxSeqIdInStores == null || maxSeqIdInStores.isEmpty()) {
1609         return;
1610       }
1611       // Create the array list for the cells that aren't filtered.
1612       // We make the assumption that most cells will be kept.
1613       ArrayList<Cell> keptCells = new ArrayList<Cell>(logEntry.getEdit().getCells().size());
1614       for (Cell cell : logEntry.getEdit().getCells()) {
1615         if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) {
1616           keptCells.add(cell);
1617         } else {
1618           byte[] family = CellUtil.cloneFamily(cell);
1619           Long maxSeqId = maxSeqIdInStores.get(family);
1620           // Do not skip cell even if maxSeqId is null. Maybe we are in a rolling upgrade,
1621           // or the master was crashed before and we can not get the information.
1622           if (maxSeqId == null || maxSeqId.longValue() < logEntry.getKey().getLogSeqNum()) {
1623             keptCells.add(cell);
1624           }
1625         }
1626       }
1627 
1628       // Anything in the keptCells array list is still live.
1629       // So rather than removing the cells from the array list
1630       // which would be an O(n^2) operation, we just replace the list
1631       logEntry.getEdit().setCells(keptCells);
1632     }
1633 
1634     @Override
1635     public void append(RegionEntryBuffer buffer) throws IOException {
1636       appendBuffer(buffer, true);
1637     }
1638 
1639 
1640     WriterAndPath appendBuffer(RegionEntryBuffer buffer, boolean reusable) throws IOException{
1641       List<Entry> entries = buffer.entryBuffer;
1642       if (entries.isEmpty()) {
1643         LOG.warn("got an empty buffer, skipping");
1644         return null;
1645       }
1646       WriterAndPath wap = null;
1647 
1648       long startTime = System.nanoTime();
1649       try {
1650         int editsCount = 0;
1651 
1652         for (Entry logEntry : entries) {
1653           if (wap == null) {
1654             wap = getWriterAndPath(logEntry, reusable);
1655             if (wap == null) {
1656               if (LOG.isDebugEnabled()) {
1657                 LOG.debug("getWriterAndPath decided we don't need to write edits for " + logEntry);
1658               }
1659               return null;
1660             }
1661           }
1662           filterCellByStore(logEntry);
1663           if (!logEntry.getEdit().isEmpty()) {
1664             wap.w.append(logEntry);
1665             this.updateRegionMaximumEditLogSeqNum(logEntry);
1666             editsCount++;
1667           } else {
1668             wap.incrementSkippedEdits(1);
1669           }
1670         }
1671         // Pass along summary statistics
1672         wap.incrementEdits(editsCount);
1673         wap.incrementNanoTime(System.nanoTime() - startTime);
1674       } catch (IOException e) {
1675         e = RemoteExceptionHandler.checkIOException(e);
1676         LOG.fatal(" Got while writing log entry to log", e);
1677         throw e;
1678       }
1679       return wap;
1680     }
1681 
1682     @Override
1683     public boolean keepRegionEvent(Entry entry) {
1684       ArrayList<Cell> cells = entry.getEdit().getCells();
1685       for (int i = 0; i < cells.size(); i++) {
1686         if (WALEdit.isCompactionMarker(cells.get(i))) {
1687           return true;
1688         }
1689       }
1690       return false;
1691     }
1692 
1693     /**
1694      * @return a map from encoded region ID to the number of edits written out for that region.
1695      */
1696     @Override
1697     public Map<byte[], Long> getOutputCounts() {
1698       TreeMap<byte[], Long> ret = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
1699       synchronized (writers) {
1700         for (Map.Entry<String, SinkWriter> entry : writers.entrySet()) {
1701           ret.put(Bytes.toBytes(entry.getKey()), entry.getValue().editsWritten);
1702         }
1703       }
1704       return ret;
1705     }
1706 
1707     @Override
1708     public int getNumberOfRecoveredRegions() {
1709       return writers.size();
1710     }
1711   }
1712 
1713   /**
1714    * Class that will limit the number of hdfs writers we create to split the logs
1715    */
1716   class BoundedLogWriterCreationOutputSink extends LogRecoveredEditsOutputSink{
1717 
1718     ConcurrentHashMap<String, Long> regionRecoverStatMap = new ConcurrentHashMap<>();
1719 
1720 
1721     public BoundedLogWriterCreationOutputSink(PipelineController controller,
1722         EntryBuffers entryBuffers, int numWriters){
1723       super(controller, entryBuffers, numWriters);
1724     }
1725 
1726     @Override
1727     public List<Path> finishWritingAndClose() throws IOException {
1728       boolean isSuccessful;
1729       List<Path> result;
1730       try {
1731         isSuccessful = finishWriting(false);
1732       } finally {
1733         result = close();
1734       }
1735       if (isSuccessful) {
1736         splits = result;
1737       }
1738       return splits;
1739     }
1740 
1741     @Override
1742     boolean executeCloseTask(CompletionService<Void> closeCompletionService,
1743         final List<IOException> thrown, final List<Path> paths)
1744         throws InterruptedException, ExecutionException {
1745       for (final Map.Entry<byte[], RegionEntryBuffer> buffer : entryBuffers.buffers.entrySet()) {
1746         LOG.info("Submitting write then close of " +
1747             Bytes.toString(buffer.getValue().encodedRegionName));
1748         closeCompletionService.submit(new Callable<Void>() {
1749           public Void call() throws Exception {
1750             Path dst = writeThenClose(buffer.getValue());
1751             paths.add(dst);
1752             return null;
1753           }
1754         });
1755       }
1756 
1757       boolean progress_failed = false;
1758       for (int i = 0, n = entryBuffers.buffers.size(); i < n; i++) {
1759         Future<Void> future = closeCompletionService.take();
1760         future.get();
1761         if (!progress_failed && reporter != null && !reporter.progress()) {
1762           progress_failed = true;
1763         }
1764       }
1765       return progress_failed;
1766     }
1767 
1768     @Override
1769     public Map<byte[], Long> getOutputCounts() {
1770       Map<byte[], Long> regionRecoverStatMapResult = new HashMap<>();
1771       for(Map.Entry<String, Long> entry: regionRecoverStatMap.entrySet()){
1772         regionRecoverStatMapResult.put(Bytes.toBytes(entry.getKey()), entry.getValue());
1773       }
1774       return regionRecoverStatMapResult;
1775     }
1776 
1777     @Override
1778     public int getNumberOfRecoveredRegions() {
1779       return regionRecoverStatMap.size();
1780     }
1781 
1782     @Override
1783     public void append(RegionEntryBuffer buffer) throws IOException {
1784       writeThenClose(buffer);
1785     }
1786 
1787     private Path writeThenClose(RegionEntryBuffer buffer) throws IOException {
1788       WriterAndPath wap = appendBuffer(buffer, false);
1789       Path dst = null;
1790       if(wap != null){
1791         String encodedRegionName = Bytes.toString(buffer.encodedRegionName);
1792         Long value = regionRecoverStatMap.putIfAbsent(encodedRegionName, wap.editsWritten);
1793         if(value != null){
1794           Long newValue = regionRecoverStatMap.get(encodedRegionName) + wap.editsWritten;
1795           regionRecoverStatMap.put(encodedRegionName, newValue);
1796         }
1797       }
1798 
1799       List<IOException> thrown = new ArrayList<>();
1800       if(wap != null) {
1801         dst = closeWriter(Bytes.toString(buffer.encodedRegionName), wap, thrown);
1802       }
1803 
1804       if(!thrown.isEmpty()){
1805         throw MultipleIOException.createIOException(thrown);
1806       }
1807       return dst;
1808     }
1809   }
1810 
1811 
1812   /**
1813    * Class wraps the actual writer which writes data out and related statistics
1814    */
1815   public abstract static class SinkWriter {
1816     /* Count of edits written to this path */
1817     long editsWritten = 0;
1818     /* Count of edits skipped to this path */
1819     long editsSkipped = 0;
1820     /* Number of nanos spent writing to this log */
1821     long nanosSpent = 0;
1822 
1823     void incrementEdits(int edits) {
1824       editsWritten += edits;
1825     }
1826 
1827     void incrementSkippedEdits(int skipped) {
1828       editsSkipped += skipped;
1829     }
1830 
1831     void incrementNanoTime(long nanos) {
1832       nanosSpent += nanos;
1833     }
1834   }
1835 
1836   /**
1837    * Private data structure that wraps a Writer and its Path, also collecting statistics about the
1838    * data written to this output.
1839    */
1840   private final static class WriterAndPath extends SinkWriter {
1841     final Path p;
1842     final Writer w;
1843     final long minLogSeqNum;
1844 
1845     WriterAndPath(final Path p, final Writer w, final long minLogSeqNum) {
1846       this.p = p;
1847       this.w = w;
1848       this.minLogSeqNum = minLogSeqNum;
1849     }
1850   }
1851 
1852   /**
1853    * Class that manages to replay edits from WAL files directly to assigned fail over region servers
1854    */
1855   class LogReplayOutputSink extends OutputSink {
1856     private static final double BUFFER_THRESHOLD = 0.35;
1857     private static final String KEY_DELIMITER = "#";
1858 
1859     private long waitRegionOnlineTimeOut;
1860     private final Set<String> recoveredRegions = Collections.synchronizedSet(new HashSet<String>());
1861     private final Map<String, RegionServerWriter> rsWriters =
1862         new ConcurrentHashMap<String, RegionServerWriter>();
1863     // online encoded region name -> region location map
1864     private final Map<String, HRegionLocation> onlineRegions =
1865         new ConcurrentHashMap<String, HRegionLocation>();
1866 
1867     private final Map<TableName, HConnection> tableNameToHConnectionMap = Collections
1868         .synchronizedMap(new TreeMap<TableName, HConnection>());
1869     /**
1870      * Map key -> value layout
1871      * servername:table name -> Queue(Row)
1872      */
1873     private final Map<String, List<Pair<HRegionLocation, Entry>>> serverToBufferQueueMap =
1874         new ConcurrentHashMap<String, List<Pair<HRegionLocation, Entry>>>();
1875     private final List<Throwable> thrown = new ArrayList<Throwable>();
1876 
1877     // The following sink is used in distrubitedLogReplay mode for entries of regions in a disabling
1878     // table. It's a limitation of distributedLogReplay. Because log replay needs a region is
1879     // assigned and online before it can replay wal edits while regions of disabling/disabled table
1880     // won't be assigned by AM. We can retire this code after HBASE-8234.
1881     private LogRecoveredEditsOutputSink logRecoveredEditsOutputSink;
1882     private boolean hasEditsInDisablingOrDisabledTables = false;
1883 
1884     public LogReplayOutputSink(PipelineController controller, EntryBuffers entryBuffers,
1885         int numWriters) {
1886       super(controller, entryBuffers, numWriters);
1887       this.waitRegionOnlineTimeOut = conf.getInt(HConstants.HBASE_SPLITLOG_MANAGER_TIMEOUT,
1888           ZKSplitLogManagerCoordination.DEFAULT_TIMEOUT);
1889       this.logRecoveredEditsOutputSink = new LogRecoveredEditsOutputSink(controller,
1890         entryBuffers, numWriters);
1891       this.logRecoveredEditsOutputSink.setReporter(reporter);
1892     }
1893 
1894     @Override
1895     public void append(RegionEntryBuffer buffer) throws IOException {
1896       List<Entry> entries = buffer.entryBuffer;
1897       if (entries.isEmpty()) {
1898         LOG.warn("got an empty buffer, skipping");
1899         return;
1900       }
1901 
1902       // check if current region in a disabling or disabled table
1903       if (disablingOrDisabledTables.contains(buffer.tableName)) {
1904         // need fall back to old way
1905         logRecoveredEditsOutputSink.append(buffer);
1906         hasEditsInDisablingOrDisabledTables = true;
1907         // store regions we have recovered so far
1908         addToRecoveredRegions(Bytes.toString(buffer.encodedRegionName));
1909         return;
1910       }
1911 
1912       // group entries by region servers
1913       groupEditsByServer(entries);
1914 
1915       // process workitems
1916       String maxLocKey = null;
1917       int maxSize = 0;
1918       List<Pair<HRegionLocation, Entry>> maxQueue = null;
1919       synchronized (this.serverToBufferQueueMap) {
1920         for (Map.Entry<String, List<Pair<HRegionLocation, Entry>>> entry:
1921               serverToBufferQueueMap.entrySet()) {
1922           List<Pair<HRegionLocation, Entry>> curQueue = entry.getValue();
1923           if (curQueue.size() > maxSize) {
1924             maxSize = curQueue.size();
1925             maxQueue = curQueue;
1926             maxLocKey = entry.getKey();
1927           }
1928         }
1929         if (maxSize < minBatchSize
1930             && entryBuffers.totalBuffered < BUFFER_THRESHOLD * entryBuffers.maxHeapUsage) {
1931           // buffer more to process
1932           return;
1933         } else if (maxSize > 0) {
1934           this.serverToBufferQueueMap.remove(maxLocKey);
1935         }
1936       }
1937 
1938       if (maxSize > 0) {
1939         processWorkItems(maxLocKey, maxQueue);
1940       }
1941     }
1942 
1943     private void addToRecoveredRegions(String encodedRegionName) {
1944       if (!recoveredRegions.contains(encodedRegionName)) {
1945         recoveredRegions.add(encodedRegionName);
1946       }
1947     }
1948 
1949     /**
1950      * Helper function to group WALEntries to individual region servers
1951      * @throws IOException
1952      */
1953     private void groupEditsByServer(List<Entry> entries) throws IOException {
1954       Set<TableName> nonExistentTables = null;
1955       Long cachedLastFlushedSequenceId = -1l;
1956       for (Entry entry : entries) {
1957         WALEdit edit = entry.getEdit();
1958         TableName table = entry.getKey().getTablename();
1959         // clear scopes which isn't needed for recovery
1960         entry.getKey().setScopes(null);
1961         String encodeRegionNameStr = Bytes.toString(entry.getKey().getEncodedRegionName());
1962         // skip edits of non-existent tables
1963         if (nonExistentTables != null && nonExistentTables.contains(table)) {
1964           this.skippedEdits.incrementAndGet();
1965           continue;
1966         }
1967 
1968         Map<byte[], Long> maxStoreSequenceIds = null;
1969         boolean needSkip = false;
1970         HRegionLocation loc = null;
1971         String locKey = null;
1972         List<Cell> cells = edit.getCells();
1973         List<Cell> skippedCells = new ArrayList<Cell>();
1974         HConnection hconn = this.getConnectionByTableName(table);
1975 
1976         for (Cell cell : cells) {
1977           byte[] row = CellUtil.cloneRow(cell);
1978           byte[] family = CellUtil.cloneFamily(cell);
1979           boolean isCompactionEntry = false;
1980           if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) {
1981             CompactionDescriptor compaction = WALEdit.getCompaction(cell);
1982             if (compaction != null && compaction.hasRegionName()) {
1983               try {
1984                 byte[][] regionName = HRegionInfo.parseRegionName(compaction.getRegionName()
1985                   .toByteArray());
1986                 row = regionName[1]; // startKey of the region
1987                 family = compaction.getFamilyName().toByteArray();
1988                 isCompactionEntry = true;
1989               } catch (Exception ex) {
1990                 LOG.warn("Unexpected exception received, ignoring " + ex);
1991                 skippedCells.add(cell);
1992                 continue;
1993               }
1994             } else {
1995               skippedCells.add(cell);
1996               continue;
1997             }
1998           }
1999 
2000           try {
2001             loc =
2002                 locateRegionAndRefreshLastFlushedSequenceId(hconn, table, row,
2003                   encodeRegionNameStr);
2004             // skip replaying the compaction if the region is gone
2005             if (isCompactionEntry && !encodeRegionNameStr.equalsIgnoreCase(
2006               loc.getRegionInfo().getEncodedName())) {
2007               LOG.info("Not replaying a compaction marker for an older region: "
2008                   + encodeRegionNameStr);
2009               needSkip = true;
2010             }
2011           } catch (TableNotFoundException ex) {
2012             // table has been deleted so skip edits of the table
2013             LOG.info("Table " + table + " doesn't exist. Skip log replay for region "
2014                 + encodeRegionNameStr);
2015             lastFlushedSequenceIds.put(encodeRegionNameStr, Long.MAX_VALUE);
2016             if (nonExistentTables == null) {
2017               nonExistentTables = new TreeSet<TableName>();
2018             }
2019             nonExistentTables.add(table);
2020             this.skippedEdits.incrementAndGet();
2021             needSkip = true;
2022             break;
2023           }
2024 
2025           cachedLastFlushedSequenceId =
2026               lastFlushedSequenceIds.get(loc.getRegionInfo().getEncodedName());
2027           if (cachedLastFlushedSequenceId != null
2028               && cachedLastFlushedSequenceId >= entry.getKey().getLogSeqNum()) {
2029             // skip the whole WAL entry
2030             this.skippedEdits.incrementAndGet();
2031             needSkip = true;
2032             break;
2033           } else {
2034             if (maxStoreSequenceIds == null) {
2035               maxStoreSequenceIds =
2036                   regionMaxSeqIdInStores.get(loc.getRegionInfo().getEncodedName());
2037             }
2038             if (maxStoreSequenceIds != null) {
2039               Long maxStoreSeqId = maxStoreSequenceIds.get(family);
2040               if (maxStoreSeqId == null || maxStoreSeqId >= entry.getKey().getLogSeqNum()) {
2041                 // skip current kv if column family doesn't exist anymore or already flushed
2042                 skippedCells.add(cell);
2043                 continue;
2044               }
2045             }
2046           }
2047         }
2048 
2049         // skip the edit
2050         if (loc == null || needSkip) continue;
2051 
2052         if (!skippedCells.isEmpty()) {
2053           cells.removeAll(skippedCells);
2054         }
2055 
2056         synchronized (serverToBufferQueueMap) {
2057           locKey = loc.getHostnamePort() + KEY_DELIMITER + table;
2058           List<Pair<HRegionLocation, Entry>> queue = serverToBufferQueueMap.get(locKey);
2059           if (queue == null) {
2060             queue =
2061                 Collections.synchronizedList(new ArrayList<Pair<HRegionLocation, Entry>>());
2062             serverToBufferQueueMap.put(locKey, queue);
2063           }
2064           queue.add(new Pair<HRegionLocation, Entry>(loc, entry));
2065         }
2066         // store regions we have recovered so far
2067         addToRecoveredRegions(loc.getRegionInfo().getEncodedName());
2068       }
2069     }
2070 
2071     /**
2072      * Locate destination region based on table name & row. This function also makes sure the
2073      * destination region is online for replay.
2074      * @throws IOException
2075      */
2076     private HRegionLocation locateRegionAndRefreshLastFlushedSequenceId(HConnection hconn,
2077         TableName table, byte[] row, String originalEncodedRegionName) throws IOException {
2078       // fetch location from cache
2079       HRegionLocation loc = onlineRegions.get(originalEncodedRegionName);
2080       if(loc != null) return loc;
2081       // fetch location from hbase:meta directly without using cache to avoid hit old dead server
2082       loc = hconn.getRegionLocation(table, row, true);
2083       if (loc == null) {
2084         throw new IOException("Can't locate location for row:" + Bytes.toString(row)
2085             + " of table:" + table);
2086       }
2087       // check if current row moves to a different region due to region merge/split
2088       if (!originalEncodedRegionName.equalsIgnoreCase(loc.getRegionInfo().getEncodedName())) {
2089         // originalEncodedRegionName should have already flushed
2090         lastFlushedSequenceIds.put(originalEncodedRegionName, Long.MAX_VALUE);
2091         HRegionLocation tmpLoc = onlineRegions.get(loc.getRegionInfo().getEncodedName());
2092         if (tmpLoc != null) return tmpLoc;
2093       }
2094 
2095       Long lastFlushedSequenceId = -1l;
2096       AtomicBoolean isRecovering = new AtomicBoolean(true);
2097       loc = waitUntilRegionOnline(loc, row, this.waitRegionOnlineTimeOut, isRecovering);
2098       if (!isRecovering.get()) {
2099         // region isn't in recovering at all because WAL file may contain a region that has
2100         // been moved to somewhere before hosting RS fails
2101         lastFlushedSequenceIds.put(loc.getRegionInfo().getEncodedName(), Long.MAX_VALUE);
2102         LOG.info("logReplay skip region: " + loc.getRegionInfo().getEncodedName()
2103             + " because it's not in recovering.");
2104       } else {
2105         Long cachedLastFlushedSequenceId =
2106             lastFlushedSequenceIds.get(loc.getRegionInfo().getEncodedName());
2107 
2108         // retrieve last flushed sequence Id from ZK. Because region postOpenDeployTasks will
2109         // update the value for the region
2110         RegionStoreSequenceIds ids =
2111             csm.getSplitLogWorkerCoordination().getRegionFlushedSequenceId(failedServerName,
2112               loc.getRegionInfo().getEncodedName());
2113         if (ids != null) {
2114           lastFlushedSequenceId = ids.getLastFlushedSequenceId();
2115           Map<byte[], Long> storeIds = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
2116           List<StoreSequenceId> maxSeqIdInStores = ids.getStoreSequenceIdList();
2117           for (StoreSequenceId id : maxSeqIdInStores) {
2118             storeIds.put(id.getFamilyName().toByteArray(), id.getSequenceId());
2119           }
2120           regionMaxSeqIdInStores.put(loc.getRegionInfo().getEncodedName(), storeIds);
2121         }
2122 
2123         if (cachedLastFlushedSequenceId == null
2124             || lastFlushedSequenceId > cachedLastFlushedSequenceId) {
2125           lastFlushedSequenceIds.put(loc.getRegionInfo().getEncodedName(), lastFlushedSequenceId);
2126         }
2127       }
2128 
2129       onlineRegions.put(loc.getRegionInfo().getEncodedName(), loc);
2130       return loc;
2131     }
2132 
2133     private void processWorkItems(String key, List<Pair<HRegionLocation, Entry>> actions)
2134         throws IOException {
2135       RegionServerWriter rsw = null;
2136 
2137       long startTime = System.nanoTime();
2138       try {
2139         rsw = getRegionServerWriter(key);
2140         rsw.sink.replayEntries(actions);
2141 
2142         // Pass along summary statistics
2143         rsw.incrementEdits(actions.size());
2144         rsw.incrementNanoTime(System.nanoTime() - startTime);
2145       } catch (IOException e) {
2146         e = RemoteExceptionHandler.checkIOException(e);
2147         LOG.fatal(" Got while writing log entry to log", e);
2148         throw e;
2149       }
2150     }
2151 
2152     /**
2153      * Wait until region is online on the destination region server
2154      * @param loc
2155      * @param row
2156      * @param timeout How long to wait
2157      * @param isRecovering Recovering state of the region interested on destination region server.
2158      * @return True when region is online on the destination region server
2159      * @throws InterruptedException
2160      */
2161     private HRegionLocation waitUntilRegionOnline(HRegionLocation loc, byte[] row,
2162         final long timeout, AtomicBoolean isRecovering)
2163         throws IOException {
2164       final long endTime = EnvironmentEdgeManager.currentTime() + timeout;
2165       final long pause = conf.getLong(HConstants.HBASE_CLIENT_PAUSE,
2166         HConstants.DEFAULT_HBASE_CLIENT_PAUSE);
2167       boolean reloadLocation = false;
2168       TableName tableName = loc.getRegionInfo().getTable();
2169       int tries = 0;
2170       Throwable cause = null;
2171       while (endTime > EnvironmentEdgeManager.currentTime()) {
2172         try {
2173           // Try and get regioninfo from the hosting server.
2174           HConnection hconn = getConnectionByTableName(tableName);
2175           if(reloadLocation) {
2176             loc = hconn.getRegionLocation(tableName, row, true);
2177           }
2178           BlockingInterface remoteSvr = hconn.getAdmin(loc.getServerName());
2179           HRegionInfo region = loc.getRegionInfo();
2180           try {
2181             GetRegionInfoRequest request =
2182                 RequestConverter.buildGetRegionInfoRequest(region.getRegionName());
2183             GetRegionInfoResponse response = remoteSvr.getRegionInfo(null, request);
2184             if (HRegionInfo.convert(response.getRegionInfo()) != null) {
2185               isRecovering.set((response.hasIsRecovering()) ? response.getIsRecovering() : true);
2186               return loc;
2187             }
2188           } catch (ServiceException se) {
2189             throw ProtobufUtil.getRemoteException(se);
2190           }
2191         } catch (IOException e) {
2192           cause = e.getCause();
2193           if(!(cause instanceof RegionOpeningException)) {
2194             reloadLocation = true;
2195           }
2196         }
2197         long expectedSleep = ConnectionUtils.getPauseTime(pause, tries);
2198         try {
2199           Thread.sleep(expectedSleep);
2200         } catch (InterruptedException e) {
2201           throw new IOException("Interrupted when waiting region " +
2202               loc.getRegionInfo().getEncodedName() + " online.", e);
2203         }
2204         tries++;
2205       }
2206 
2207       throw new IOException("Timeout when waiting region " + loc.getRegionInfo().getEncodedName() +
2208         " online for " + timeout + " milliseconds.", cause);
2209     }
2210 
2211     @Override
2212     public boolean flush() throws IOException {
2213       String curLoc = null;
2214       int curSize = 0;
2215       List<Pair<HRegionLocation, Entry>> curQueue = null;
2216       synchronized (this.serverToBufferQueueMap) {
2217         for (Map.Entry<String, List<Pair<HRegionLocation, Entry>>> entry :
2218                 serverToBufferQueueMap.entrySet()) {
2219           String locationKey = entry.getKey();
2220           curQueue = entry.getValue();
2221           if (!curQueue.isEmpty()) {
2222             curSize = curQueue.size();
2223             curLoc = locationKey;
2224             break;
2225           }
2226         }
2227         if (curSize > 0) {
2228           this.serverToBufferQueueMap.remove(curLoc);
2229         }
2230       }
2231 
2232       if (curSize > 0) {
2233         this.processWorkItems(curLoc, curQueue);
2234         // We should already have control of the monitor; ensure this is the case.
2235         synchronized(controller.dataAvailable) {
2236           controller.dataAvailable.notifyAll();
2237         }
2238         return true;
2239       }
2240       return false;
2241     }
2242 
2243     @Override
2244     public boolean keepRegionEvent(Entry entry) {
2245       return true;
2246     }
2247 
2248     void addWriterError(Throwable t) {
2249       thrown.add(t);
2250     }
2251 
2252     @Override
2253     public List<Path> finishWritingAndClose() throws IOException {
2254       try {
2255         if (!finishWriting(false)) {
2256           return null;
2257         }
2258         if (hasEditsInDisablingOrDisabledTables) {
2259           splits = logRecoveredEditsOutputSink.finishWritingAndClose();
2260         } else {
2261           splits = new ArrayList<Path>();
2262         }
2263         // returns an empty array in order to keep interface same as old way
2264         return splits;
2265       } finally {
2266         List<IOException> thrown = closeRegionServerWriters();
2267         if (thrown != null && !thrown.isEmpty()) {
2268           throw MultipleIOException.createIOException(thrown);
2269         }
2270       }
2271     }
2272 
2273     @Override
2274     int getNumOpenWriters() {
2275       return this.rsWriters.size() + this.logRecoveredEditsOutputSink.getNumOpenWriters();
2276     }
2277 
2278     private List<IOException> closeRegionServerWriters() throws IOException {
2279       List<IOException> result = null;
2280       if (!writersClosed) {
2281         result = Lists.newArrayList();
2282         try {
2283           for (WriterThread t : writerThreads) {
2284             while (t.isAlive()) {
2285               t.shouldStop = true;
2286               t.interrupt();
2287               try {
2288                 t.join(10);
2289               } catch (InterruptedException e) {
2290                 IOException iie = new InterruptedIOException();
2291                 iie.initCause(e);
2292                 throw iie;
2293               }
2294             }
2295           }
2296         } finally {
2297           synchronized (rsWriters) {
2298             for (Map.Entry<String, RegionServerWriter> entry : rsWriters.entrySet()) {
2299               String locationKey = entry.getKey();
2300               RegionServerWriter tmpW = entry.getValue();
2301               try {
2302                 tmpW.close();
2303               } catch (IOException ioe) {
2304                 LOG.error("Couldn't close writer for region server:" + locationKey, ioe);
2305                 result.add(ioe);
2306               }
2307             }
2308           }
2309 
2310           // close connections
2311           synchronized (this.tableNameToHConnectionMap) {
2312             for (Map.Entry<TableName, HConnection> entry :
2313                   tableNameToHConnectionMap.entrySet()) {
2314               TableName tableName = entry.getKey();
2315               HConnection hconn = entry.getValue();
2316               try {
2317                 hconn.clearRegionCache();
2318                 hconn.close();
2319               } catch (IOException ioe) {
2320                 result.add(ioe);
2321               }
2322             }
2323           }
2324           writersClosed = true;
2325         }
2326       }
2327       return result;
2328     }
2329 
2330     @Override
2331     public Map<byte[], Long> getOutputCounts() {
2332       TreeMap<byte[], Long> ret = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
2333       synchronized (rsWriters) {
2334         for (Map.Entry<String, RegionServerWriter> entry : rsWriters.entrySet()) {
2335           ret.put(Bytes.toBytes(entry.getKey()), entry.getValue().editsWritten);
2336         }
2337       }
2338       return ret;
2339     }
2340 
2341     @Override
2342     public int getNumberOfRecoveredRegions() {
2343       return this.recoveredRegions.size();
2344     }
2345 
2346     /**
2347      * Get a writer and path for a log starting at the given entry. This function is threadsafe so
2348      * long as multiple threads are always acting on different regions.
2349      * @return null if this region shouldn't output any logs
2350      */
2351     private RegionServerWriter getRegionServerWriter(String loc) throws IOException {
2352       RegionServerWriter ret = rsWriters.get(loc);
2353       if (ret != null) {
2354         return ret;
2355       }
2356 
2357       TableName tableName = getTableFromLocationStr(loc);
2358       if(tableName == null){
2359         throw new IOException("Invalid location string:" + loc + " found. Replay aborted.");
2360       }
2361 
2362       HConnection hconn = getConnectionByTableName(tableName);
2363       synchronized (rsWriters) {
2364         ret = rsWriters.get(loc);
2365         if (ret == null) {
2366           ret = new RegionServerWriter(conf, tableName, hconn);
2367           rsWriters.put(loc, ret);
2368         }
2369       }
2370       return ret;
2371     }
2372 
2373     private HConnection getConnectionByTableName(final TableName tableName) throws IOException {
2374       HConnection hconn = this.tableNameToHConnectionMap.get(tableName);
2375       if (hconn == null) {
2376         synchronized (this.tableNameToHConnectionMap) {
2377           hconn = this.tableNameToHConnectionMap.get(tableName);
2378           if (hconn == null) {
2379             hconn = HConnectionManager.getConnection(conf);
2380             this.tableNameToHConnectionMap.put(tableName, hconn);
2381           }
2382         }
2383       }
2384       return hconn;
2385     }
2386     private TableName getTableFromLocationStr(String loc) {
2387       /**
2388        * location key is in format <server name:port>#<table name>
2389        */
2390       String[] splits = loc.split(KEY_DELIMITER);
2391       if (splits.length != 2) {
2392         return null;
2393       }
2394       return TableName.valueOf(splits[1]);
2395     }
2396   }
2397 
2398   /**
2399    * Private data structure that wraps a receiving RS and collecting statistics about the data
2400    * written to this newly assigned RS.
2401    */
2402   private final static class RegionServerWriter extends SinkWriter {
2403     final WALEditsReplaySink sink;
2404 
2405     RegionServerWriter(final Configuration conf, final TableName tableName, final HConnection conn)
2406         throws IOException {
2407       this.sink = new WALEditsReplaySink(conf, tableName, conn);
2408     }
2409 
2410     void close() throws IOException {
2411     }
2412   }
2413 
2414   static class CorruptedLogFileException extends Exception {
2415     private static final long serialVersionUID = 1L;
2416 
2417     CorruptedLogFileException(String s) {
2418       super(s);
2419     }
2420   }
2421 
2422   /** A struct used by getMutationsFromWALEntry */
2423   public static class MutationReplay implements Comparable<MutationReplay> {
2424     public MutationReplay(MutationType type, Mutation mutation, long nonceGroup, long nonce) {
2425       this.type = type;
2426       this.mutation = mutation;
2427       if(this.mutation.getDurability() != Durability.SKIP_WAL) {
2428         // using ASYNC_WAL for relay
2429         this.mutation.setDurability(Durability.ASYNC_WAL);
2430       }
2431       this.nonceGroup = nonceGroup;
2432       this.nonce = nonce;
2433     }
2434 
2435     public final MutationType type;
2436     public final Mutation mutation;
2437     public final long nonceGroup;
2438     public final long nonce;
2439 
2440     @Override
2441     public int compareTo(final MutationReplay d) {
2442       return this.mutation.compareTo(d.mutation);
2443     }
2444 
2445     @Override
2446     public boolean equals(Object obj) {
2447       if(!(obj instanceof MutationReplay)) {
2448         return false;
2449       } else {
2450         return this.compareTo((MutationReplay)obj) == 0;
2451       }
2452     }
2453 
2454     @Override
2455     public int hashCode() {
2456       return this.mutation.hashCode();
2457     }
2458   }
2459 
2460   /**
2461    * This function is used to construct mutations from a WALEntry. It also
2462    * reconstructs WALKey &amp; WALEdit from the passed in WALEntry
2463    * @param entry
2464    * @param cells
2465    * @param logEntry pair of WALKey and WALEdit instance stores WALKey and WALEdit instances
2466    *          extracted from the passed in WALEntry.
2467    * @param durability
2468    * @return list of Pair&lt;MutationType, Mutation&gt; to be replayed
2469    * @throws IOException
2470    */
2471   public static List<MutationReplay> getMutationsFromWALEntry(WALEntry entry, CellScanner cells,
2472       Pair<WALKey, WALEdit> logEntry, Durability durability)
2473           throws IOException {
2474     if (entry == null) {
2475       // return an empty array
2476       return new ArrayList<MutationReplay>();
2477     }
2478 
2479     long replaySeqId = (entry.getKey().hasOrigSequenceNumber()) ?
2480       entry.getKey().getOrigSequenceNumber() : entry.getKey().getLogSequenceNumber();
2481     int count = entry.getAssociatedCellCount();
2482     List<MutationReplay> mutations = new ArrayList<MutationReplay>();
2483     Cell previousCell = null;
2484     Mutation m = null;
2485     WALKey key = null;
2486     WALEdit val = null;
2487     if (logEntry != null) val = new WALEdit();
2488 
2489     for (int i = 0; i < count; i++) {
2490       // Throw index out of bounds if our cell count is off
2491       if (!cells.advance()) {
2492         throw new ArrayIndexOutOfBoundsException("Expected=" + count + ", index=" + i);
2493       }
2494       Cell cell = cells.current();
2495       if (val != null) val.add(cell);
2496 
2497       boolean isNewRowOrType =
2498           previousCell == null || previousCell.getTypeByte() != cell.getTypeByte()
2499               || !CellUtil.matchingRow(previousCell, cell);
2500       if (isNewRowOrType) {
2501         // Create new mutation
2502         if (CellUtil.isDelete(cell)) {
2503           m = new Delete(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
2504           // Deletes don't have nonces.
2505           mutations.add(new MutationReplay(
2506               MutationType.DELETE, m, HConstants.NO_NONCE, HConstants.NO_NONCE));
2507         } else {
2508           m = new Put(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
2509           // Puts might come from increment or append, thus we need nonces.
2510           long nonceGroup = entry.getKey().hasNonceGroup()
2511               ? entry.getKey().getNonceGroup() : HConstants.NO_NONCE;
2512           long nonce = entry.getKey().hasNonce() ? entry.getKey().getNonce() : HConstants.NO_NONCE;
2513           mutations.add(new MutationReplay(MutationType.PUT, m, nonceGroup, nonce));
2514         }
2515       }
2516       if (CellUtil.isDelete(cell)) {
2517         ((Delete) m).addDeleteMarker(cell);
2518       } else {
2519         ((Put) m).add(cell);
2520       }
2521       m.setDurability(durability);
2522       previousCell = cell;
2523     }
2524 
2525     // reconstruct WALKey
2526     if (logEntry != null) {
2527       org.apache.hadoop.hbase.protobuf.generated.WALProtos.WALKey walKeyProto = entry.getKey();
2528       List<UUID> clusterIds = new ArrayList<UUID>(walKeyProto.getClusterIdsCount());
2529       for (HBaseProtos.UUID uuid : entry.getKey().getClusterIdsList()) {
2530         clusterIds.add(new UUID(uuid.getMostSigBits(), uuid.getLeastSigBits()));
2531       }
2532       // we use HLogKey here instead of WALKey directly to support legacy coprocessors.
2533       key = new HLogKey(walKeyProto.getEncodedRegionName().toByteArray(), TableName.valueOf(
2534               walKeyProto.getTableName().toByteArray()), replaySeqId, walKeyProto.getWriteTime(),
2535               clusterIds, walKeyProto.getNonceGroup(), walKeyProto.getNonce(), null);
2536       logEntry.setFirst(key);
2537       logEntry.setSecond(val);
2538     }
2539 
2540     return mutations;
2541   }
2542 }