This is an automated email from the ASF dual-hosted git repository.

eolivelli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 114de13  Call exceptionHandler if Bookie.start fails with exception.
114de13 is described below

commit 114de135f402d2f8de045fb9413e804247841def
Author: Charan Reddy Guttapalem <[email protected]>
AuthorDate: Fri Oct 11 08:22:54 2019 -0700

    Call exceptionHandler if Bookie.start fails with exception.
    
    
    Descriptions of the changes in this PR:
    
    When main thread of Bookie/BookieServer has exited with exception while 
starting Bookie,
    Bookie process shouldn't be alive because of any non-daemon thread that has 
already
    started.
    
    
    Reviewers: Enrico Olivelli <[email protected]>, Karan Mehta 
<[email protected]>
    
    This closes #2173 from reddycharan/fixbookiestartup
---
 .../component/AbstractLifecycleComponent.java      | 16 +++-
 .../common/component/ComponentStarter.java         |  5 +-
 .../org/apache/bookkeeper/proto/BookieServer.java  |  3 +-
 .../bookkeeper/replication/AutoRecoveryMain.java   |  2 +-
 .../server/service/AutoRecoveryService.java        |  8 +-
 .../bookkeeper/server/service/BookieService.java   |  7 +-
 .../bookie/BookieInitializationTest.java           | 93 +++++++++++++++++++++-
 .../bookkeeper/bookie/BookieJournalTest.java       |  6 +-
 8 files changed, 120 insertions(+), 20 deletions(-)

diff --git 
a/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/AbstractLifecycleComponent.java
 
b/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/AbstractLifecycleComponent.java
index 015d54d..5b6f19e 100644
--- 
a/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/AbstractLifecycleComponent.java
+++ 
b/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/AbstractLifecycleComponent.java
@@ -25,6 +25,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.bookkeeper.common.conf.ComponentConfiguration;
 import org.apache.bookkeeper.stats.StatsLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A mix of {@link AbstractComponent} and {@link LifecycleComponent}.
@@ -33,6 +35,8 @@ import org.apache.bookkeeper.stats.StatsLogger;
 public abstract class AbstractLifecycleComponent<ConfT extends 
ComponentConfiguration>
     extends AbstractComponent<ConfT> implements LifecycleComponent {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(AbstractLifecycleComponent.class);
+
     protected final Lifecycle lifecycle = new Lifecycle();
     private final Set<LifecycleListener> listeners = new 
CopyOnWriteArraySet<>();
     protected final StatsLogger statsLogger;
@@ -75,7 +79,17 @@ public abstract class AbstractLifecycleComponent<ConfT 
extends ComponentConfigur
             return;
         }
         listeners.forEach(LifecycleListener::beforeStart);
-        doStart();
+        try {
+            doStart();
+        } catch (Throwable exc) {
+            LOG.error("Failed to start Component: {}", getName(), exc);
+            if (uncaughtExceptionHandler != null) {
+                LOG.error("Calling uncaughtExceptionHandler");
+                
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), exc);
+            } else {
+                throw exc;
+            }
+        }
         lifecycle.moveToStarted();
         listeners.forEach(LifecycleListener::afterStart);
     }
diff --git 
a/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/ComponentStarter.java
 
b/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/ComponentStarter.java
index e952747..acb643b 100644
--- 
a/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/ComponentStarter.java
+++ 
b/bookkeeper-common/src/main/java/org/apache/bookkeeper/common/component/ComponentStarter.java
@@ -19,6 +19,7 @@
 package org.apache.bookkeeper.common.component;
 
 import java.util.concurrent.CompletableFuture;
+
 import lombok.extern.slf4j.Slf4j;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 
@@ -46,7 +47,7 @@ public class ComponentStarter {
                 component.close();
                 log.info("Closed component {} in shutdown hook successfully. 
Exiting.", component.getName());
                 FutureUtils.complete(future, null);
-            } catch (Exception e) {
+            } catch (Throwable e) {
                 log.error("Failed to close component {} in shutdown hook 
gracefully, Exiting anyway",
                     component.getName(), e);
                 future.completeExceptionally(e);
@@ -72,6 +73,8 @@ public class ComponentStarter {
 
         // register a component exception handler
         component.setExceptionHandler((t, e) -> {
+            log.error("Triggered exceptionHandler of Component: {} because of 
Exception in Thread: {}",
+                    component.getName(), t, e);
             // start the shutdown hook when an uncaught exception happen in 
the lifecycle component.
             shutdownHookThread.start();
         });
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
index b8c1adc..14d6393 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
@@ -39,7 +39,6 @@ import org.apache.bookkeeper.bookie.BookieCriticalThread;
 import org.apache.bookkeeper.bookie.BookieException;
 import org.apache.bookkeeper.bookie.ExitCode;
 import org.apache.bookkeeper.bookie.ReadOnlyBookie;
-import org.apache.bookkeeper.client.BKException;
 import org.apache.bookkeeper.common.allocator.ByteBufAllocatorBuilder;
 import org.apache.bookkeeper.common.util.JsonUtil.ParseJsonException;
 import org.apache.bookkeeper.conf.ServerConfiguration;
@@ -137,7 +136,7 @@ public class BookieServer {
             : new Bookie(conf, statsLogger.scope(BOOKIE_SCOPE), allocator);
     }
 
-    public void start() throws IOException, UnavailableException, 
InterruptedException, BKException {
+    public void start() throws InterruptedException {
         this.bookie.start();
         // fail fast, when bookie startup is not successful
         if (!this.bookie.isRunning()) {
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/AutoRecoveryMain.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/AutoRecoveryMain.java
index 39b41aa..303a401 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/AutoRecoveryMain.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/AutoRecoveryMain.java
@@ -116,7 +116,7 @@ public class AutoRecoveryMain {
     /*
      * Start daemons
      */
-    public void start() throws UnavailableException {
+    public void start() {
         auditorElector.start();
         replicationWorker.start();
         if (null != uncaughtExceptionHandler) {
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/AutoRecoveryService.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/AutoRecoveryService.java
index f8389df..c7305c2 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/AutoRecoveryService.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/AutoRecoveryService.java
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.lang.Thread.UncaughtExceptionHandler;
 
 import org.apache.bookkeeper.replication.AutoRecoveryMain;
-import 
org.apache.bookkeeper.replication.ReplicationException.UnavailableException;
 import org.apache.bookkeeper.server.component.ServerLifecycleComponent;
 import org.apache.bookkeeper.server.conf.BookieConfiguration;
 import org.apache.bookkeeper.stats.StatsLogger;
@@ -45,6 +44,7 @@ public class AutoRecoveryService extends 
ServerLifecycleComponent {
 
     @Override
     public void setExceptionHandler(UncaughtExceptionHandler handler) {
+        super.setExceptionHandler(handler);
         main.setExceptionHandler(handler);
     }
 
@@ -54,11 +54,7 @@ public class AutoRecoveryService extends 
ServerLifecycleComponent {
 
     @Override
     protected void doStart() {
-        try {
-            this.main.start();
-        } catch (UnavailableException e) {
-            throw new RuntimeException("Can't not start '" + NAME + "' 
component.", e);
-        }
+        this.main.start();
     }
 
     @Override
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/BookieService.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/BookieService.java
index d6837f6..645da03 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/BookieService.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/service/BookieService.java
@@ -20,9 +20,7 @@ package org.apache.bookkeeper.server.service;
 
 import java.io.IOException;
 import java.lang.Thread.UncaughtExceptionHandler;
-import org.apache.bookkeeper.client.BKException;
 import org.apache.bookkeeper.proto.BookieServer;
-import 
org.apache.bookkeeper.replication.ReplicationException.UnavailableException;
 import org.apache.bookkeeper.server.component.ServerLifecycleComponent;
 import org.apache.bookkeeper.server.conf.BookieConfiguration;
 import org.apache.bookkeeper.stats.StatsLogger;
@@ -45,6 +43,7 @@ public class BookieService extends ServerLifecycleComponent {
 
     @Override
     public void setExceptionHandler(UncaughtExceptionHandler handler) {
+        super.setExceptionHandler(handler);
         server.setExceptionHandler(handler);
     }
 
@@ -56,8 +55,8 @@ public class BookieService extends ServerLifecycleComponent {
     protected void doStart() {
         try {
             this.server.start();
-        } catch (IOException | UnavailableException | InterruptedException | 
BKException e) {
-            throw new RuntimeException("Failed to start bookie server", e);
+        } catch (InterruptedException exc) {
+            throw new RuntimeException("Failed to start bookie server", exc);
         }
     }
 
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieInitializationTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieInitializationTest.java
index 8ea63e1..77bb0bd 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieInitializationTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieInitializationTest.java
@@ -24,9 +24,13 @@ import static com.google.common.base.Charsets.UTF_8;
 import static org.apache.bookkeeper.util.BookKeeperConstants.AVAILABLE_NODE;
 import static 
org.apache.bookkeeper.util.BookKeeperConstants.BOOKIE_STATUS_FILENAME;
 import static org.apache.bookkeeper.util.TestUtils.countNumOfFiles;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,6 +39,8 @@ import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
 import io.netty.buffer.UnpooledByteBufAllocator;
 
@@ -58,6 +64,8 @@ import java.util.Random;
 
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import 
org.apache.bookkeeper.bookie.BookieException.DiskPartitionDuplicationException;
 import org.apache.bookkeeper.bookie.BookieException.MetadataStoreException;
 import org.apache.bookkeeper.bookie.Journal.LastLogMark;
@@ -95,6 +103,7 @@ import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
 import org.apache.bookkeeper.test.PortManager;
 import org.apache.bookkeeper.tls.SecurityException;
 import org.apache.bookkeeper.util.DiskChecker;
+import org.apache.bookkeeper.util.LoggerOutput;
 import org.apache.bookkeeper.zookeeper.ZooKeeperClient;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.Stat;
@@ -105,6 +114,7 @@ import org.junit.rules.TestName;
 import org.powermock.reflect.Whitebox;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.slf4j.event.LoggingEvent;
 
 /**
  * Testing bookie initialization cases.
@@ -117,6 +127,8 @@ public class BookieInitializationTest extends 
BookKeeperClusterTestCase {
 
     @Rule
     public final TestName runtime = new TestName();
+    @Rule
+    public LoggerOutput loggerOutput = new LoggerOutput();
     ZKMetadataBookieDriver driver;
 
     public BookieInitializationTest() {
@@ -640,6 +652,85 @@ public class BookieInitializationTest extends 
BookKeeperClusterTestCase {
         startFuture.get();
     }
 
+    /**
+     * Mock InterleavedLedgerStorage class where addEntry is mocked to throw
+     * OutOfMemoryError.
+     */
+    public static class MockInterleavedLedgerStorage extends 
InterleavedLedgerStorage {
+        AtomicInteger atmoicInt = new AtomicInteger(0);
+
+        @Override
+        public long addEntry(ByteBuf entry) throws IOException {
+            if (atmoicInt.incrementAndGet() == 10) {
+                throw new OutOfMemoryError("Some Injected Exception");
+            }
+            return super.addEntry(entry);
+        }
+    }
+
+    @Test
+    public void testBookieStartException() throws Exception {
+        File journalDir = createTempDir("bookie", "journal");
+        Bookie.checkDirectoryStructure(Bookie.getCurrentDirectory(journalDir));
+
+        File ledgerDir = createTempDir("bookie", "ledger");
+        Bookie.checkDirectoryStructure(Bookie.getCurrentDirectory(ledgerDir));
+
+        /*
+         * add few entries to journal file.
+         */
+        int numOfEntries = 100;
+        
BookieJournalTest.writeV5Journal(Bookie.getCurrentDirectory(journalDir), 
numOfEntries,
+                "testV5Journal".getBytes());
+
+        /*
+         * This Bookie is configured to use MockInterleavedLedgerStorage.
+         * MockInterleavedLedgerStorage throws an Error for addEntry request.
+         * This is to simulate Bookie/BookieServer/BookieService 'start' 
failure
+         * because of 'Bookie.readJournal' failure.
+         */
+        ServerConfiguration conf = 
TestBKConfiguration.newServerConfiguration();
+        int port = PortManager.nextFreePort();
+        conf.setBookiePort(port).setJournalDirName(journalDir.getPath())
+                .setLedgerDirNames(new String[] { ledgerDir.getPath() 
}).setMetadataServiceUri(metadataServiceUri)
+                
.setLedgerStorageClass(MockInterleavedLedgerStorage.class.getName());
+
+        BookieConfiguration bkConf = new BookieConfiguration(conf);
+
+        /*
+         * create cookie and write it to JournalDir/LedgerDir.
+         */
+        Cookie.Builder cookieBuilder = Cookie.generateCookie(conf);
+        Cookie cookie = cookieBuilder.build();
+        cookie.writeToDirectory(new File(journalDir, "current"));
+        cookie.writeToDirectory(new File(ledgerDir, "current"));
+
+        /*
+         * Create LifecycleComponent for BookieServer and start it.
+         */
+        LifecycleComponent server = Main.buildBookieServer(bkConf);
+        CompletableFuture<Void> startFuture = 
ComponentStarter.startComponent(server);
+
+        /*
+         * Since Bookie/BookieServer/BookieService is expected to fail, it 
would
+         * cause bookie-server component's exceptionHandler to get triggered.
+         * This exceptionHandler will make sure all of the components to get
+         * closed and then finally completes the Future.
+         */
+        startFuture.get();
+
+        /*
+         * make sure that Component's exceptionHandler is called by checking if
+         * the error message of ExceptionHandler is logged. This Log message is
+         * defined in anonymous exceptionHandler class defined in
+         * ComponentStarter.startComponent method.
+         */
+        loggerOutput.expect((List<LoggingEvent> logEvents) -> {
+            assertThat(logEvents,
+                    hasItem(hasProperty("message", containsString("Triggered 
exceptionHandler of Component:"))));
+        });
+    }
+
     @Test
     public void testAutoRecoveryServiceExceptionHandler() throws Exception {
         ServerConfiguration conf = 
TestBKConfiguration.newServerConfiguration();
@@ -889,8 +980,6 @@ public class BookieInitializationTest extends 
BookKeeperClusterTestCase {
         // are injecting no-op shutdown.
         server.shutdown();
 
-        long usableSpace = tmpDir.getUsableSpace();
-        long totalSpace = tmpDir.getTotalSpace();
         conf.setDiskUsageThreshold(0.001f)
                 
.setDiskUsageWarnThreshold(0.0f).setReadOnlyModeEnabled(true).setIsForceGCAllowWhenNoSpace(true)
                 .setMinUsableSizeForIndexFileCreation(Long.MAX_VALUE);
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieJournalTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieJournalTest.java
index 0f1b327..cbac455 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieJournalTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieJournalTest.java
@@ -111,7 +111,7 @@ public class BookieJournalTest {
     /**
      * Generate fence entry.
      */
-    private ByteBuf generateFenceEntry(long ledgerId) {
+    private static ByteBuf generateFenceEntry(long ledgerId) {
         ByteBuf bb = Unpooled.buffer();
         bb.writeLong(ledgerId);
         bb.writeLong(Bookie.METAENTRY_ID_FENCE_KEY);
@@ -121,7 +121,7 @@ public class BookieJournalTest {
     /**
      * Generate meta entry with given master key.
      */
-    private ByteBuf generateMetaEntry(long ledgerId, byte[] masterKey) {
+    private static ByteBuf generateMetaEntry(long ledgerId, byte[] masterKey) {
         ByteBuf bb = Unpooled.buffer();
         bb.writeLong(ledgerId);
         bb.writeLong(Bookie.METAENTRY_ID_LEDGER_KEY);
@@ -290,7 +290,7 @@ public class BookieJournalTest {
         return jc;
     }
 
-    private JournalChannel writeV5Journal(File journalDir, int numEntries, 
byte[] masterKey) throws Exception {
+    static JournalChannel writeV5Journal(File journalDir, int numEntries, 
byte[] masterKey) throws Exception {
         long logId = System.currentTimeMillis();
         JournalChannel jc = new JournalChannel(journalDir, logId);
 

Reply via email to