diff --git a/community/common/src/main/java/org/neo4j/kernel/lifecycle/SafeLifecycle.java b/community/common/src/main/java/org/neo4j/kernel/lifecycle/SafeLifecycle.java
new file mode 100644
index 0000000000000..fba244e5e65d1
--- /dev/null
+++ b/community/common/src/main/java/org/neo4j/kernel/lifecycle/SafeLifecycle.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2002-2018 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.kernel.lifecycle;
+
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.HALT;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.IDLE;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.PRE;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.RUN;
+
+/**
+ * A safer lifecycle adapter with strict semantics and as
+ * a result simpler error handling and reasoning. If the
+ * semantics of this class is not consistent with what you
+ * are after, then it is probably better to create a new
+ * adapter with the specific semantics required.
+ *
+ * Guide
+ *
+ * *: No-op (will not invoke operation)
+ * -: Error (will throw IllegalStateException)
+ *
+ * P: PRE ("pre init")
+ * I: IDLE ("initialized" or "stopped")
+ * R: RUN ("started")
+ * H: HALT ("shutdown")
+ *
+ * A successful operation is an operation not throwing an exception.
+ *
+ * End states on a successful operation.
+ *
+ * ---------------------------------------------------
+ * | FROM \ op | init() start() stop() shutdown() |
+ * ---------------------------------------------------
+ * | PRE | IDLE - - HALT(*) |
+ * | IDLE | - RUN IDLE(*) HALT |
+ * | RUN | - - IDLE - |
+ * | HALT | - - - - |
+ * ---------------------------------------------------
+ *
+ * End states on a failed operation.
+ *
+ * ---------------------------------------------------
+ * | FROM \ op | init() start() stop() shutdown() |
+ * ---------------------------------------------------
+ * | PRE | PRE - - HALT(*) |
+ * | IDLE | - IDLE IDLE(*) HALT |
+ * | RUN | - - IDLE - |
+ * | HALT | - - - - |
+ * ---------------------------------------------------
+ *
+ * A few notes:
+ *
+ * - will not invoke stop0() if start0() wasn't successful
+ * - will not invoke shutdown0() if init0() wasn't successful
+ *
+ * The expectation with regards to error handling and cleanup is that
+ * an unclean start() is cleaned up by the start0() method and thus
+ * the component is left in IDLE. The same goes for issues happening
+ * during init0(), which leaves the component in PRE.
+ *
+ * Because of the way that {@link LifeSupport} operates today, this
+ * class will ignore stop() calls made while in IDLE. Similarly, calls
+ * to shutdown() will be ignored while in PRE. This allows this class
+ * to be managed by a {@link LifeSupport} without throwing
+ * {@link IllegalStateException} on those state transitions, which
+ * otherwise would have been disallowed and handled in the same way
+ * as other illegal state transitions.
+ *
+ * This adapter will not allow a shutdown lifecycle to be reinitialized
+ * and started again.
+ */
+public abstract class SafeLifecycle implements Lifecycle
+{
+ private State state;
+
+ protected SafeLifecycle()
+ {
+ this( PRE );
+ }
+
+ SafeLifecycle( State state )
+ {
+ this.state = state;
+ }
+
+ /**
+ * @param expected The expected from state.
+ * @param to The to state.
+ * @param op The state transition operation.
+ * @param force Causes the state to be updated regardless of if the operation throws.
+ * @throws Throwable Issues.
+ */
+ private void transition( State expected, State to, Operation op, boolean force ) throws Throwable
+ {
+ if ( state != expected )
+ {
+ throw new IllegalStateException( String.format( "Expected %s but was %s", expected, state ) );
+ }
+
+ if ( force )
+ {
+ state = to;
+ op.run();
+ }
+ else
+ {
+ op.run();
+ state = to;
+ }
+ }
+
+ @Override
+ public final synchronized void init() throws Throwable
+ {
+ transition( PRE, IDLE, this::init0, false );
+ }
+
+ @Override
+ public final synchronized void start() throws Throwable
+ {
+ transition( IDLE, RUN, this::start0, false );
+ }
+
+ @Override
+ public final synchronized void stop() throws Throwable
+ {
+ if ( state == IDLE )
+ {
+ return;
+ }
+ transition( RUN, IDLE, this::stop0, true );
+ }
+
+ @Override
+ public final synchronized void shutdown() throws Throwable
+ {
+ if ( state == PRE )
+ {
+ state = HALT;
+ return;
+ }
+ transition( IDLE, HALT, this::shutdown0, true );
+ }
+
+ public void init0() throws Throwable
+ {
+ }
+
+ public void start0() throws Throwable
+ {
+ }
+
+ public void stop0() throws Throwable
+ {
+ }
+
+ public void shutdown0() throws Throwable
+ {
+ }
+
+ public State state()
+ {
+ return state;
+ }
+
+ enum State
+ {
+ PRE,
+ IDLE,
+ RUN,
+ HALT
+ }
+
+ interface Operation
+ {
+ void run() throws Throwable;
+ }
+}
diff --git a/community/common/src/test/java/org/neo4j/kernel/lifecycle/SafeLifecycleTest.java b/community/common/src/test/java/org/neo4j/kernel/lifecycle/SafeLifecycleTest.java
new file mode 100644
index 0000000000000..b8d9a0e1ae2a6
--- /dev/null
+++ b/community/common/src/test/java/org/neo4j/kernel/lifecycle/SafeLifecycleTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2002-2018 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.neo4j.kernel.lifecycle;
+
+import org.junit.Test;
+
+import org.neo4j.function.ThrowingConsumer;
+import org.neo4j.kernel.lifecycle.SafeLifecycle.State;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.HALT;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.IDLE;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.PRE;
+import static org.neo4j.kernel.lifecycle.SafeLifecycle.State.RUN;
+
+public class SafeLifecycleTest
+{
+ private ThrowingConsumer init = Lifecycle::init;
+ private ThrowingConsumer start = Lifecycle::start;
+ private ThrowingConsumer stop = Lifecycle::stop;
+ private ThrowingConsumer shutdown = Lifecycle::shutdown;
+ @SuppressWarnings( "unchecked" )
+ private ThrowingConsumer[] ops = new ThrowingConsumer[]{init, start, stop, shutdown};
+
+ private Object[][] onSuccess = new Object[][]{
+ // init() start() stop() shutdown()
+ new State[] { IDLE, null, null, HALT }, // from PRE
+ new State[] { null, RUN, IDLE, HALT }, // from IDLE
+ new State[] { null, null, IDLE, null }, // from RUN
+ new State[] { null, null, null, null }, // from HALT
+ };
+
+ private Object[][] onFailed = new Object[][]{
+ // init() start() stop() shutdown()
+ new State[] { PRE, null, null, HALT }, // from PRE
+ new State[] { null, IDLE, IDLE, HALT }, // from IDLE
+ new State[] { null, null, IDLE, null }, // from RUN
+ new State[] { null, null, null, null }, // from HALT
+ };
+
+ private Boolean[][] ignored = new Boolean[][]{
+ // init() start() stop() shutdown()
+ new Boolean[] { false, false, false, true }, // from PRE
+ new Boolean[] { false, false, true, false }, // from IDLE
+ new Boolean[] { false, false, false, false }, // from RUN
+ new Boolean[] { false, false, false, false }, // from HALT
+ };
+
+ @Test
+ public void shouldPerformSuccessfulTransitionsCorrectly() throws Throwable
+ {
+ for ( int state = 0; state < State.values().length; state++ )
+ {
+ for ( int op = 0; op < ops.length; op++ )
+ {
+ MySafeAndSuccessfulLife sf = new MySafeAndSuccessfulLife( State.values()[state] );
+ boolean caughtIllegalTransition = false;
+ try
+ {
+ ops[op].accept( sf );
+ }
+ catch ( IllegalStateException e )
+ {
+ caughtIllegalTransition = true;
+ }
+
+ if ( onSuccess[state][op] == null )
+ {
+ assertTrue( caughtIllegalTransition );
+ assertEquals( State.values()[state], sf.state() );
+ }
+ else
+ {
+ assertFalse( caughtIllegalTransition );
+ assertEquals( onSuccess[state][op], sf.state() );
+ int expectedOpCode = ignored[state][op] ? -1 : op;
+ assertEquals( expectedOpCode, sf.opCode );
+ }
+ }
+ }
+ }
+
+ @Test
+ public void shouldPerformFailedTransitionsCorrectly() throws Throwable
+ {
+ for ( int state = 0; state < State.values().length; state++ )
+ {
+ for ( int op = 0; op < ops.length; op++ )
+ {
+ MyFailedLife sf = new MyFailedLife( State.values()[state] );
+ boolean caughtIllegalTransition = false;
+ boolean failedOperation = false;
+ try
+ {
+ ops[op].accept( sf );
+ }
+ catch ( IllegalStateException e )
+ {
+ caughtIllegalTransition = true;
+ }
+ catch ( UnsupportedOperationException e )
+ {
+ failedOperation = true;
+ }
+
+ if ( onFailed[state][op] == null )
+ {
+ assertTrue( caughtIllegalTransition );
+ assertEquals( State.values()[state], sf.state() );
+ }
+ else
+ {
+ assertFalse( caughtIllegalTransition );
+ assertEquals( onFailed[state][op], sf.state() );
+
+ if ( ignored[state][op] )
+ {
+ assertEquals( -1, sf.opCode );
+ assertFalse( failedOperation );
+ }
+ else
+ {
+ assertEquals( op, sf.opCode );
+ assertTrue( failedOperation );
+ }
+ }
+ }
+ }
+ }
+
+ private static class MySafeAndSuccessfulLife extends SafeLifecycle
+ {
+ int opCode;
+
+ MySafeAndSuccessfulLife( State state )
+ {
+ super( state );
+ opCode = -1;
+ }
+
+ @Override
+ public void init0()
+ {
+ invoke( 0 );
+ }
+
+ @Override
+ public void start0()
+ {
+ invoke( 1 );
+ }
+
+ @Override
+ public void stop0()
+ {
+ invoke( 2 );
+ }
+
+ @Override
+ public void shutdown0()
+ {
+ invoke( 3 );
+ }
+
+ private void invoke( int opCode )
+ {
+ if ( this.opCode == -1 )
+ {
+ this.opCode = opCode;
+ }
+ else
+ {
+ throw new RuntimeException( "Double invocation" );
+ }
+ }
+ }
+
+ private static class MyFailedLife extends SafeLifecycle
+ {
+ int opCode;
+
+ MyFailedLife( State state )
+ {
+ super( state );
+ opCode = -1;
+ }
+
+ @Override
+ public void init0()
+ {
+ invoke( 0 );
+ }
+
+ @Override
+ public void start0()
+ {
+ invoke( 1 );
+ }
+
+ @Override
+ public void stop0()
+ {
+ invoke( 2 );
+ }
+
+ @Override
+ public void shutdown0()
+ {
+ invoke( 3 );
+ }
+
+ private void invoke( int opCode )
+ {
+ if ( this.opCode == -1 )
+ {
+ this.opCode = opCode;
+ throw new UnsupportedOperationException( "I made a bo-bo" );
+ }
+ else
+ {
+ throw new RuntimeException( "Double invocation" );
+ }
+ }
+ }
+}
diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/state/CoreLife.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/state/CoreLife.java
index 0059b9042c578..746b083848d72 100644
--- a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/state/CoreLife.java
+++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/state/CoreLife.java
@@ -26,9 +26,9 @@
import org.neo4j.causalclustering.identity.BoundState;
import org.neo4j.causalclustering.identity.ClusterBinder;
import org.neo4j.causalclustering.messaging.LifecycleMessageHandler;
-import org.neo4j.kernel.lifecycle.Lifecycle;
+import org.neo4j.kernel.lifecycle.SafeLifecycle;
-public class CoreLife implements Lifecycle
+public class CoreLife extends SafeLifecycle
{
private final RaftMachine raftMachine;
private final LocalDatabase localDatabase;
@@ -57,13 +57,13 @@ public CoreLife( RaftMachine raftMachine,
}
@Override
- public synchronized void init() throws Throwable
+ public void init0() throws Throwable
{
localDatabase.init();
}
@Override
- public synchronized void start() throws Throwable
+ public void start0() throws Throwable
{
BoundState boundState = clusterBinder.bindToCluster();
raftMessageHandler.start( boundState.clusterId() );
@@ -86,7 +86,7 @@ public synchronized void start() throws Throwable
}
@Override
- public synchronized void stop() throws Throwable
+ public void stop0() throws Throwable
{
raftMachine.stopTimers();
raftMessageHandler.stop();
@@ -95,7 +95,7 @@ public synchronized void stop() throws Throwable
}
@Override
- public synchronized void shutdown() throws Throwable
+ public void shutdown0() throws Throwable
{
localDatabase.shutdown();
}