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.regionserver;
20  
21  import java.io.IOException;
22  import java.io.InterruptedIOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.ListIterator;
26  import java.util.Map;
27  import java.util.concurrent.Callable;
28  import java.util.concurrent.ExecutionException;
29  import java.util.concurrent.Executors;
30  import java.util.concurrent.Future;
31  import java.util.concurrent.ThreadFactory;
32  import java.util.concurrent.ThreadPoolExecutor;
33  import java.util.concurrent.TimeUnit;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.hbase.classification.InterfaceAudience;
38  import org.apache.hadoop.hbase.HConstants;
39  import org.apache.hadoop.hbase.HRegionInfo;
40  import org.apache.hadoop.hbase.Server;
41  import org.apache.hadoop.hbase.ServerName;
42  import org.apache.hadoop.hbase.client.Mutation;
43  import org.apache.hadoop.hbase.client.Put;
44  import org.apache.hadoop.hbase.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.apache.hadoop.hbase.util.CancelableProgressable;
47  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
48  import org.apache.hadoop.hbase.util.HasThread;
49  import org.apache.hadoop.hbase.util.PairOfSameType;
50  import org.apache.zookeeper.KeeperException;
51  
52  import com.google.common.util.concurrent.ThreadFactoryBuilder;
53  
54  /**
55   * Executes region split as a "transaction".  Call {@link #prepare()} to setup
56   * the transaction, {@link #execute(Server, RegionServerServices)} to run the
57   * transaction and {@link #rollback(Server, RegionServerServices)} to cleanup if execute fails.
58   *
59   * <p>Here is an example of how you would use this class:
60   * <pre>
61   *  SplitTransaction st = new SplitTransaction(this.conf, parent, midKey)
62   *  if (!st.prepare()) return;
63   *  try {
64   *    st.execute(server, services);
65   *  } catch (IOException ioe) {
66   *    try {
67   *      st.rollback(server, services);
68   *      return;
69   *    } catch (RuntimeException e) {
70   *      myAbortable.abort("Failed split, abort");
71   *    }
72   *  }
73   * </Pre>
74   * <p>This class is not thread safe.  Caller needs ensure split is run by
75   * one thread only.
76   */
77  @InterfaceAudience.Private
78  public class SplitTransaction {
79    private static final Log LOG = LogFactory.getLog(SplitTransaction.class);
80  
81    /*
82     * Region to split
83     */
84    private final HRegion parent;
85    private HRegionInfo hri_a;
86    private HRegionInfo hri_b;
87    private long fileSplitTimeout = 30000;
88  
89    /*
90     * Row to split around
91     */
92    private final byte [] splitrow;
93  
94    /**
95     * Types to add to the transaction journal.
96     * Each enum is a step in the split transaction. Used to figure how much
97     * we need to rollback.
98     */
99    static enum JournalEntryType {
100     /**
101      * Started
102      */
103     STARTED,
104     /**
105      * Prepared (after table lock)
106      */
107     PREPARED,
108     /**
109      * Before preSplit coprocessor hook
110      */
111     BEFORE_PRE_SPLIT_HOOK,
112     /**
113      * After preSplit coprocessor hook
114      */
115     AFTER_PRE_SPLIT_HOOK,
116     /**
117      * Set region as in transition, set it into SPLITTING state.
118      */
119     SET_SPLITTING,
120     /**
121      * We created the temporary split data directory.
122      */
123     CREATE_SPLIT_DIR,
124     /**
125      * Closed the parent region.
126      */
127     CLOSED_PARENT_REGION,
128     /**
129      * The parent has been taken out of the server's online regions list.
130      */
131     OFFLINED_PARENT,
132     /**
133      * Started in on creation of the first daughter region.
134      */
135     STARTED_REGION_A_CREATION,
136     /**
137      * Started in on the creation of the second daughter region.
138      */
139     STARTED_REGION_B_CREATION,
140     /**
141      * Opened the first daughter region
142      */
143     OPENED_REGION_A,
144     /**
145      * Opened the second daughter region
146      */
147     OPENED_REGION_B,
148     /**
149      * Before postSplit coprocessor hook
150      */
151     BEFORE_POST_SPLIT_HOOK,
152     /**
153      * After postSplit coprocessor hook
154      */
155     AFTER_POST_SPLIT_HOOK,
156     /**
157      * Point of no return.
158      * If we got here, then transaction is not recoverable other than by
159      * crashing out the regionserver.
160      */
161     PONR
162   }
163 
164   static class JournalEntry {
165     public JournalEntryType type;
166     public long timestamp;
167 
168     public JournalEntry(JournalEntryType type) {
169       this(type, EnvironmentEdgeManager.currentTime());
170     }
171 
172     public JournalEntry(JournalEntryType type, long timestamp) {
173       this.type = type;
174       this.timestamp = timestamp;
175     }
176 
177     @Override
178     public String toString() {
179       StringBuilder sb = new StringBuilder();
180       sb.append(type);
181       sb.append(" at ");
182       sb.append(timestamp);
183       return sb.toString();
184     }
185   }
186 
187   /*
188    * Journal of how far the split transaction has progressed.
189    */
190   private final List<JournalEntry> journal = new ArrayList<JournalEntry>();
191 
192   /**
193    * Constructor
194    * @param r Region to split
195    * @param splitrow Row to split around
196    */
197   public SplitTransaction(final HRegion r, final byte [] splitrow) {
198     this.parent = r;
199     this.splitrow = splitrow;
200     this.journal.add(new JournalEntry(JournalEntryType.STARTED));
201   }
202 
203   /**
204    * Does checks on split inputs.
205    * @return <code>true</code> if the region is splittable else
206    * <code>false</code> if it is not (e.g. its already closed, etc.).
207    */
208   public boolean prepare() {
209     if (!this.parent.isSplittable()) return false;
210     // Split key can be null if this region is unsplittable; i.e. has refs.
211     if (this.splitrow == null) return false;
212     HRegionInfo hri = this.parent.getRegionInfo();
213     parent.prepareToSplit();
214     // Check splitrow.
215     byte [] startKey = hri.getStartKey();
216     byte [] endKey = hri.getEndKey();
217     if (Bytes.equals(startKey, splitrow) ||
218         !this.parent.getRegionInfo().containsRow(splitrow)) {
219       LOG.info("Split row is not inside region key range or is equal to " +
220           "startkey: " + Bytes.toStringBinary(this.splitrow));
221       return false;
222     }
223     long rid = getDaughterRegionIdTimestamp(hri);
224     this.hri_a = new HRegionInfo(hri.getTable(), startKey, this.splitrow, false, rid);
225     this.hri_b = new HRegionInfo(hri.getTable(), this.splitrow, endKey, false, rid);
226     this.journal.add(new JournalEntry(JournalEntryType.PREPARED));
227     return true;
228   }
229 
230   /**
231    * Calculate daughter regionid to use.
232    * @param hri Parent {@link HRegionInfo}
233    * @return Daughter region id (timestamp) to use.
234    */
235   private static long getDaughterRegionIdTimestamp(final HRegionInfo hri) {
236     long rid = EnvironmentEdgeManager.currentTime();
237     // Regionid is timestamp.  Can't be less than that of parent else will insert
238     // at wrong location in hbase:meta (See HBASE-710).
239     if (rid < hri.getRegionId()) {
240       LOG.warn("Clock skew; parent regions id is " + hri.getRegionId() +
241         " but current time here is " + rid);
242       rid = hri.getRegionId() + 1;
243     }
244     return rid;
245   }
246 
247   private static IOException closedByOtherException = new IOException(
248       "Failed to close region: already closed by another thread");
249 
250   /**
251    * Prepare the regions and region files.
252    * @param server Hosting server instance.  Can be null when testing (won't try
253    * and update in zk if a null server)
254    * @param services Used to online/offline regions.
255    * @throws IOException If thrown, transaction failed.
256    *    Call {@link #rollback(Server, RegionServerServices)}
257    * @return Regions created
258    */
259   /* package */PairOfSameType<HRegion> createDaughters(final Server server,
260       final RegionServerServices services) throws IOException {
261     LOG.info("Starting split of region " + this.parent);
262     if ((server != null && server.isStopped()) ||
263         (services != null && services.isStopping())) {
264       throw new IOException("Server is stopped or stopping");
265     }
266     assert !this.parent.lock.writeLock().isHeldByCurrentThread():
267       "Unsafe to hold write lock while performing RPCs";
268 
269     journal.add(new JournalEntry(JournalEntryType.BEFORE_PRE_SPLIT_HOOK));
270 
271     // Coprocessor callback
272     if (this.parent.getCoprocessorHost() != null) {
273       // TODO: Remove one of these
274       this.parent.getCoprocessorHost().preSplit();
275       this.parent.getCoprocessorHost().preSplit(this.splitrow);
276     }
277 
278     journal.add(new JournalEntry(JournalEntryType.AFTER_PRE_SPLIT_HOOK));
279 
280     // If true, no cluster to write meta edits to or to update znodes in.
281     boolean testing = server == null? true:
282         server.getConfiguration().getBoolean("hbase.testing.nocluster", false);
283     this.fileSplitTimeout = testing ? this.fileSplitTimeout :
284         server.getConfiguration().getLong("hbase.regionserver.fileSplitTimeout",
285           this.fileSplitTimeout);
286 
287     PairOfSameType<HRegion> daughterRegions = stepsBeforePONR(server, services, testing);
288 
289     List<Mutation> metaEntries = new ArrayList<Mutation>();
290     if (this.parent.getCoprocessorHost() != null) {
291       if (this.parent.getCoprocessorHost().
292           preSplitBeforePONR(this.splitrow, metaEntries)) {
293         throw new IOException("Coprocessor bypassing region "
294             + this.parent.getRegionNameAsString() + " split.");
295       }
296       try {
297         for (Mutation p : metaEntries) {
298           HRegionInfo.parseRegionName(p.getRow());
299         }
300       } catch (IOException e) {
301         LOG.error("Row key of mutation from coprossor is not parsable as region name."
302             + "Mutations from coprocessor should only for hbase:meta table.");
303         throw e;
304       }
305     }
306 
307     // This is the point of no return.  Adding subsequent edits to .META. as we
308     // do below when we do the daughter opens adding each to .META. can fail in
309     // various interesting ways the most interesting of which is a timeout
310     // BUT the edits all go through (See HBASE-3872).  IF we reach the PONR
311     // then subsequent failures need to crash out this regionserver; the
312     // server shutdown processing should be able to fix-up the incomplete split.
313     // The offlined parent will have the daughters as extra columns.  If
314     // we leave the daughter regions in place and do not remove them when we
315     // crash out, then they will have their references to the parent in place
316     // still and the server shutdown fixup of .META. will point to these
317     // regions.
318     // We should add PONR JournalEntry before offlineParentInMeta,so even if
319     // OfflineParentInMeta timeout,this will cause regionserver exit,and then
320     // master ServerShutdownHandler will fix daughter & avoid data loss. (See
321     // HBase-4562).
322     this.journal.add(new JournalEntry(JournalEntryType.PONR));
323 
324     // Edit parent in meta.  Offlines parent region and adds splita and splitb
325     // as an atomic update. See HBASE-7721. This update to META makes the region
326     // will determine whether the region is split or not in case of failures.
327     // If it is successful, master will roll-forward, if not, master will rollback
328     // and assign the parent region.
329     if (services != null && !services.reportRegionStateTransition(TransitionCode.SPLIT_PONR,
330         parent.getRegionInfo(), hri_a, hri_b)) {
331       // Passed PONR, let SSH clean it up
332       throw new IOException("Failed to notify master that split passed PONR: "
333         + parent.getRegionInfo().getRegionNameAsString());
334     }
335     return daughterRegions;
336   }
337 
338   public PairOfSameType<HRegion> stepsBeforePONR(final Server server,
339       final RegionServerServices services, boolean testing) throws IOException {
340     if (services != null && !services.reportRegionStateTransition(TransitionCode.READY_TO_SPLIT,
341         parent.getRegionInfo(), hri_a, hri_b)) {
342       throw new IOException("Failed to get ok from master to split "
343         + parent.getRegionNameAsString());
344     }
345     this.journal.add(new JournalEntry(JournalEntryType.SET_SPLITTING));
346 
347     this.parent.getRegionFileSystem().createSplitsDir();
348     this.journal.add(new JournalEntry(JournalEntryType.CREATE_SPLIT_DIR));
349 
350     Map<byte[], List<StoreFile>> hstoreFilesToSplit = null;
351     Exception exceptionToThrow = null;
352     try{
353       hstoreFilesToSplit = this.parent.close(false);
354     } catch (Exception e) {
355       exceptionToThrow = e;
356     }
357     if (exceptionToThrow == null && hstoreFilesToSplit == null) {
358       // The region was closed by a concurrent thread.  We can't continue
359       // with the split, instead we must just abandon the split.  If we
360       // reopen or split this could cause problems because the region has
361       // probably already been moved to a different server, or is in the
362       // process of moving to a different server.
363       exceptionToThrow = closedByOtherException;
364     }
365     if (exceptionToThrow != closedByOtherException) {
366       this.journal.add(new JournalEntry(JournalEntryType.CLOSED_PARENT_REGION));
367     }
368     if (exceptionToThrow != null) {
369       if (exceptionToThrow instanceof IOException) throw (IOException)exceptionToThrow;
370       throw new IOException(exceptionToThrow);
371     }
372     if (!testing) {
373       services.removeFromOnlineRegions(this.parent, null);
374     }
375     this.journal.add(new JournalEntry(JournalEntryType.OFFLINED_PARENT));
376 
377     // TODO: If splitStoreFiles were multithreaded would we complete steps in
378     // less elapsed time?  St.Ack 20100920
379     //
380     // splitStoreFiles creates daughter region dirs under the parent splits dir
381     // Nothing to unroll here if failure -- clean up of CREATE_SPLIT_DIR will
382     // clean this up.
383     splitStoreFiles(hstoreFilesToSplit);
384 
385     // Log to the journal that we are creating region A, the first daughter
386     // region.  We could fail halfway through.  If we do, we could have left
387     // stuff in fs that needs cleanup -- a storefile or two.  Thats why we
388     // add entry to journal BEFORE rather than AFTER the change.
389     this.journal.add(new JournalEntry(JournalEntryType.STARTED_REGION_A_CREATION));
390     HRegion a = this.parent.createDaughterRegionFromSplits(this.hri_a);
391 
392     // Ditto
393     this.journal.add(new JournalEntry(JournalEntryType.STARTED_REGION_B_CREATION));
394     HRegion b = this.parent.createDaughterRegionFromSplits(this.hri_b);
395     return new PairOfSameType<HRegion>(a, b);
396   }
397 
398   /**
399    * Perform time consuming opening of the daughter regions.
400    * @param server Hosting server instance.  Can be null when testing
401    * @param services Used to online/offline regions.
402    * @param a first daughter region
403    * @param a second daughter region
404    * @throws IOException If thrown, transaction failed.
405    *          Call {@link #rollback(Server, RegionServerServices)}
406    */
407   /* package */void openDaughters(final Server server,
408       final RegionServerServices services, HRegion a, HRegion b)
409       throws IOException {
410     boolean stopped = server != null && server.isStopped();
411     boolean stopping = services != null && services.isStopping();
412     // TODO: Is this check needed here?
413     if (stopped || stopping) {
414       LOG.info("Not opening daughters " +
415           b.getRegionInfo().getRegionNameAsString() +
416           " and " +
417           a.getRegionInfo().getRegionNameAsString() +
418           " because stopping=" + stopping + ", stopped=" + stopped);
419     } else {
420       // Open daughters in parallel.
421       DaughterOpener aOpener = new DaughterOpener(server, a);
422       DaughterOpener bOpener = new DaughterOpener(server, b);
423       aOpener.start();
424       bOpener.start();
425       try {
426         aOpener.join();
427         if (aOpener.getException() == null) {
428           journal.add(new JournalEntry(JournalEntryType.OPENED_REGION_A));
429         }
430         bOpener.join();
431         if (bOpener.getException() == null) {
432           journal.add(new JournalEntry(JournalEntryType.OPENED_REGION_B));
433         }
434       } catch (InterruptedException e) {
435         throw (InterruptedIOException)new InterruptedIOException().initCause(e);
436       }
437       if (aOpener.getException() != null) {
438         throw new IOException("Failed " +
439           aOpener.getName(), aOpener.getException());
440       }
441       if (bOpener.getException() != null) {
442         throw new IOException("Failed " +
443           bOpener.getName(), bOpener.getException());
444       }
445       if (services != null) {
446         if (!services.reportRegionStateTransition(TransitionCode.SPLIT,
447             parent.getRegionInfo(), hri_a, hri_b)) {
448           throw new IOException("Failed to report split region to master: "
449             + parent.getRegionInfo().getShortNameToLog());
450         }
451         // Should add it to OnlineRegions
452         services.addToOnlineRegions(b);
453         services.addToOnlineRegions(a);
454       }
455     }
456   }
457 
458   /**
459    * Run the transaction.
460    * @param server Hosting server instance.  Can be null when testing
461    * @param services Used to online/offline regions.
462    * @throws IOException If thrown, transaction failed.
463    *          Call {@link #rollback(Server, RegionServerServices)}
464    * @return Regions created
465    * @throws IOException
466    * @see #rollback(Server, RegionServerServices)
467    */
468   public PairOfSameType<HRegion> execute(final Server server,
469       final RegionServerServices services)
470   throws IOException {
471     PairOfSameType<HRegion> regions = createDaughters(server, services);
472     if (this.parent.getCoprocessorHost() != null) {
473       this.parent.getCoprocessorHost().preSplitAfterPONR();
474     }
475     return stepsAfterPONR(server, services, regions);
476   }
477 
478   public PairOfSameType<HRegion> stepsAfterPONR(final Server server,
479       final RegionServerServices services, PairOfSameType<HRegion> regions)
480       throws IOException {
481     openDaughters(server, services, regions.getFirst(), regions.getSecond());
482     journal.add(new JournalEntry(JournalEntryType.BEFORE_POST_SPLIT_HOOK));
483     // Coprocessor callback
484     if (parent.getCoprocessorHost() != null) {
485       parent.getCoprocessorHost().postSplit(regions.getFirst(), regions.getSecond());
486     }
487     journal.add(new JournalEntry(JournalEntryType.AFTER_POST_SPLIT_HOOK));
488     return regions;
489   }
490 
491   public Put addLocation(final Put p, final ServerName sn, long openSeqNum) {
492     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER,
493       Bytes.toBytes(sn.getHostAndPort()));
494     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.STARTCODE_QUALIFIER,
495       Bytes.toBytes(sn.getStartcode()));
496     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER,
497         Bytes.toBytes(openSeqNum));
498     return p;
499   }
500 
501   /*
502    * Open daughter region in its own thread.
503    * If we fail, abort this hosting server.
504    */
505   class DaughterOpener extends HasThread {
506     private final Server server;
507     private final HRegion r;
508     private Throwable t = null;
509 
510     DaughterOpener(final Server s, final HRegion r) {
511       super((s == null? "null-services": s.getServerName()) +
512         "-daughterOpener=" + r.getRegionInfo().getEncodedName());
513       setDaemon(true);
514       this.server = s;
515       this.r = r;
516     }
517 
518     /**
519      * @return Null if open succeeded else exception that causes us fail open.
520      * Call it after this thread exits else you may get wrong view on result.
521      */
522     Throwable getException() {
523       return this.t;
524     }
525 
526     @Override
527     public void run() {
528       try {
529         openDaughterRegion(this.server, r);
530       } catch (Throwable t) {
531         this.t = t;
532       }
533     }
534   }
535 
536   /**
537    * Open daughter regions, add them to online list and update meta.
538    * @param server
539    * @param daughter
540    * @throws IOException
541    * @throws KeeperException
542    */
543   void openDaughterRegion(final Server server, final HRegion daughter)
544   throws IOException, KeeperException {
545     HRegionInfo hri = daughter.getRegionInfo();
546     LoggingProgressable reporter = server == null ? null
547         : new LoggingProgressable(hri, server.getConfiguration().getLong(
548             "hbase.regionserver.split.daughter.open.log.interval", 10000));
549     daughter.openHRegion(reporter);
550   }
551 
552   static class LoggingProgressable implements CancelableProgressable {
553     private final HRegionInfo hri;
554     private long lastLog = -1;
555     private final long interval;
556 
557     LoggingProgressable(final HRegionInfo hri, final long interval) {
558       this.hri = hri;
559       this.interval = interval;
560     }
561 
562     @Override
563     public boolean progress() {
564       long now = EnvironmentEdgeManager.currentTime();
565       if (now - lastLog > this.interval) {
566         LOG.info("Opening " + this.hri.getRegionNameAsString());
567         this.lastLog = now;
568       }
569       return true;
570     }
571   }
572 
573   private void splitStoreFiles(final Map<byte[], List<StoreFile>> hstoreFilesToSplit)
574       throws IOException {
575     if (hstoreFilesToSplit == null) {
576       // Could be null because close didn't succeed -- for now consider it fatal
577       throw new IOException("Close returned empty list of StoreFiles");
578     }
579     // The following code sets up a thread pool executor with as many slots as
580     // there's files to split. It then fires up everything, waits for
581     // completion and finally checks for any exception
582     int nbFiles = hstoreFilesToSplit.size();
583     if (nbFiles == 0) {
584       // no file needs to be splitted.
585       return;
586     }
587     ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
588     builder.setNameFormat("StoreFileSplitter-%1$d");
589     ThreadFactory factory = builder.build();
590     ThreadPoolExecutor threadPool =
591       (ThreadPoolExecutor) Executors.newFixedThreadPool(nbFiles, factory);
592     List<Future<Void>> futures = new ArrayList<Future<Void>>(nbFiles);
593 
594     // Split each store file.
595     for (Map.Entry<byte[], List<StoreFile>> entry: hstoreFilesToSplit.entrySet()) {
596       for (StoreFile sf: entry.getValue()) {
597         StoreFileSplitter sfs = new StoreFileSplitter(entry.getKey(), sf);
598         futures.add(threadPool.submit(sfs));
599       }
600     }
601     // Shutdown the pool
602     threadPool.shutdown();
603 
604     // Wait for all the tasks to finish
605     try {
606       boolean stillRunning = !threadPool.awaitTermination(
607           this.fileSplitTimeout, TimeUnit.MILLISECONDS);
608       if (stillRunning) {
609         threadPool.shutdownNow();
610         // wait for the thread to shutdown completely.
611         while (!threadPool.isTerminated()) {
612           Thread.sleep(50);
613         }
614         throw new IOException("Took too long to split the" +
615             " files and create the references, aborting split");
616       }
617     } catch (InterruptedException e) {
618       throw (InterruptedIOException)new InterruptedIOException().initCause(e);
619     }
620 
621     // Look for any exception
622     for (Future<Void> future: futures) {
623       try {
624         future.get();
625       } catch (InterruptedException e) {
626         throw (InterruptedIOException)new InterruptedIOException().initCause(e);
627       } catch (ExecutionException e) {
628         throw new IOException(e);
629       }
630     }
631   }
632 
633   private void splitStoreFile(final byte[] family, final StoreFile sf) throws IOException {
634     HRegionFileSystem fs = this.parent.getRegionFileSystem();
635     String familyName = Bytes.toString(family);
636     fs.splitStoreFile(this.hri_a, familyName, sf, this.splitrow, false);
637     fs.splitStoreFile(this.hri_b, familyName, sf, this.splitrow, true);
638   }
639 
640   /**
641    * Utility class used to do the file splitting / reference writing
642    * in parallel instead of sequentially.
643    */
644   class StoreFileSplitter implements Callable<Void> {
645     private final byte[] family;
646     private final StoreFile sf;
647 
648     /**
649      * Constructor that takes what it needs to split
650      * @param family Family that contains the store file
651      * @param sf which file
652      */
653     public StoreFileSplitter(final byte[] family, final StoreFile sf) {
654       this.sf = sf;
655       this.family = family;
656     }
657 
658     public Void call() throws IOException {
659       splitStoreFile(family, sf);
660       return null;
661     }
662   }
663 
664   /**
665    * @param server Hosting server instance (May be null when testing).
666    * @param services
667    * @throws IOException If thrown, rollback failed.  Take drastic action.
668    * @return True if we successfully rolled back, false if we got to the point
669    * of no return and so now need to abort the server to minimize damage.
670    */
671   @SuppressWarnings("deprecation")
672   public boolean rollback(final Server server, final RegionServerServices services)
673   throws IOException {
674     // Coprocessor callback
675     if (this.parent.getCoprocessorHost() != null) {
676       this.parent.getCoprocessorHost().preRollBackSplit();
677     }
678 
679     boolean result = true;
680     ListIterator<JournalEntry> iterator =
681       this.journal.listIterator(this.journal.size());
682     // Iterate in reverse.
683     while (iterator.hasPrevious()) {
684       JournalEntry je = iterator.previous();
685       switch(je.type) {
686 
687       case SET_SPLITTING:
688         if (services != null
689             && !services.reportRegionStateTransition(TransitionCode.SPLIT_REVERTED,
690                 parent.getRegionInfo(), hri_a, hri_b)) {
691           return false;
692         }
693         break;
694 
695       case CREATE_SPLIT_DIR:
696         this.parent.writestate.writesEnabled = true;
697         this.parent.getRegionFileSystem().cleanupSplitsDir();
698         break;
699 
700       case CLOSED_PARENT_REGION:
701         try {
702           // So, this returns a seqid but if we just closed and then reopened, we
703           // should be ok. On close, we flushed using sequenceid obtained from
704           // hosting regionserver so no need to propagate the sequenceid returned
705           // out of initialize below up into regionserver as we normally do.
706           // TODO: Verify.
707           this.parent.initialize();
708         } catch (IOException e) {
709           LOG.error("Failed rollbacking CLOSED_PARENT_REGION of region " +
710             this.parent.getRegionNameAsString(), e);
711           throw new RuntimeException(e);
712         }
713         break;
714 
715       case STARTED_REGION_A_CREATION:
716         this.parent.getRegionFileSystem().cleanupDaughterRegion(this.hri_a);
717         break;
718 
719       case STARTED_REGION_B_CREATION:
720         this.parent.getRegionFileSystem().cleanupDaughterRegion(this.hri_b);
721         break;
722 
723       case OFFLINED_PARENT:
724         if (services != null) services.addToOnlineRegions(this.parent);
725         break;
726 
727       case PONR:
728         // We got to the point-of-no-return so we need to just abort. Return
729         // immediately.  Do not clean up created daughter regions.  They need
730         // to be in place so we don't delete the parent region mistakenly.
731         // See HBASE-3872.
732         return false;
733 
734       // Informational only cases
735       case STARTED:
736       case PREPARED:
737       case BEFORE_PRE_SPLIT_HOOK:
738       case AFTER_PRE_SPLIT_HOOK:
739       case BEFORE_POST_SPLIT_HOOK:
740       case AFTER_POST_SPLIT_HOOK:
741       case OPENED_REGION_A:
742       case OPENED_REGION_B:
743         break;
744 
745       default:
746         throw new RuntimeException("Unhandled journal entry: " + je);
747       }
748     }
749     // Coprocessor callback
750     if (this.parent.getCoprocessorHost() != null) {
751       this.parent.getCoprocessorHost().postRollBackSplit();
752     }
753     return result;
754   }
755 
756   HRegionInfo getFirstDaughter() {
757     return hri_a;
758   }
759 
760   HRegionInfo getSecondDaughter() {
761     return hri_b;
762   }
763 
764   List<JournalEntry> getJournal() {
765     return journal;
766   }
767 }