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