org.neo4j.kernel.lifecycle.LifeSupport Maven / Gradle / Ivy
Show all versions of ongdb-common Show documentation
/*
* Copyright (c) 2018-2020 "Graph Foundation,"
* Graph Foundation, Inc. [https://graphfoundation.org]
*
* This file is part of ONgDB.
*
* ONgDB 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 .
*/
/*
* Copyright (c) 2002-2020 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.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 java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* Support class for handling collections of Lifecycle instances. Manages the transitions from one state to another.
*
* To use this, first add instances to it that implement the Lifecycle interface. When lifecycle methods on this
* class are called it will try to invoke the same methods on the registered instances.
*
* Components that internally owns other components that has a lifecycle can use this to control them as well.
*/
public class LifeSupport
implements Lifecycle
{
private volatile List instances = new ArrayList<>();
private volatile LifecycleStatus status = LifecycleStatus.NONE;
private final List listeners = new ArrayList<>();
public LifeSupport()
{
}
/**
* Initialize all registered instances, transitioning from status NONE to STOPPED.
*
* If transition fails, then it goes to STOPPED and then SHUTDOWN, so it cannot be restarted again.
*/
@Override
public synchronized void init()
throws LifecycleException
{
if ( status == LifecycleStatus.NONE )
{
status = changedStatus( this, status, LifecycleStatus.INITIALIZING );
for ( LifecycleInstance instance : instances )
{
try
{
instance.init();
}
catch ( LifecycleException e )
{
status = changedStatus( this, status, LifecycleStatus.STOPPED );
try
{
shutdown();
}
catch ( LifecycleException shutdownErr )
{
e.addSuppressed( shutdownErr );
}
throw e;
}
}
status = changedStatus( this, status, LifecycleStatus.STOPPED );
}
}
/**
* Start all registered instances, transitioning from STOPPED to STARTED.
*
* If it was previously not initialized, it will be initialized first.
*
* If any instance fails to start, the already started instances will be stopped, so
* that the overall status is STOPPED.
*
* @throws LifecycleException
*/
@Override
public synchronized void start()
throws LifecycleException
{
init();
if ( status == LifecycleStatus.STOPPED )
{
status = changedStatus( this, status, LifecycleStatus.STARTING );
for ( LifecycleInstance instance : instances )
{
try
{
instance.start();
}
catch ( LifecycleException e )
{
// TODO perhaps reconsider chaining of exceptions coming from LifeSupports?
status = changedStatus( this, status, LifecycleStatus.STARTED );
try
{
stop();
}
catch ( LifecycleException stopErr )
{
e.addSuppressed( stopErr );
}
throw e;
}
}
status = changedStatus( this, status, LifecycleStatus.STARTED );
}
}
/**
* Stop all registered instances, transitioning from STARTED to STOPPED.
*
* If any instance fails to stop, the rest of the instances will still be stopped,
* so that the overall status is STOPPED.
*/
@Override
public synchronized void stop()
throws LifecycleException
{
if ( status == LifecycleStatus.STARTED )
{
status = changedStatus( this, status, LifecycleStatus.STOPPING );
LifecycleException ex = stopInstances( instances );
status = changedStatus( this, status, LifecycleStatus.STOPPED );
if ( ex != null )
{
throw ex;
}
}
}
private LifecycleException stopInstances( List instances )
{
LifecycleException ex = null;
for ( int i = instances.size() - 1; i >= 0; i-- )
{
LifecycleInstance lifecycleInstance = instances.get( i );
try
{
lifecycleInstance.stop();
}
catch ( LifecycleException e )
{
if ( ex != null )
{
ex.addSuppressed( e );
}
else
{
ex = e;
}
}
}
return ex;
}
/**
* Shutdown all registered instances, transitioning from either STARTED or STOPPED to SHUTDOWN.
*
* If any instance fails to shutdown, the rest of the instances will still be shut down,
* so that the overall status is SHUTDOWN.
*/
@Override
public synchronized void shutdown()
throws LifecycleException
{
LifecycleException ex = null;
try
{
stop();
}
catch ( LifecycleException e )
{
ex = e;
}
if ( status == LifecycleStatus.STOPPED )
{
status = changedStatus( this, status, LifecycleStatus.SHUTTING_DOWN );
for ( int i = instances.size() - 1; i >= 0; i-- )
{
LifecycleInstance lifecycleInstance = instances.get( i );
try
{
lifecycleInstance.shutdown();
}
catch ( LifecycleException e )
{
if ( ex != null )
{
ex.addSuppressed( e );
}
else
{
ex = e;
}
}
}
status = changedStatus( this, status, LifecycleStatus.SHUTDOWN );
if ( ex != null )
{
throw ex;
}
}
}
/**
* Add a new Lifecycle instance. It will immediately be transitioned
* to the state of this LifeSupport.
*
* @param instance the Lifecycle instance to add
* @param type of the instance
* @return the instance itself
* @throws LifecycleException if the instance could not be transitioned properly
*/
public synchronized T add( T instance )
throws LifecycleException
{
assert instance != null;
assert notAlreadyAdded( instance );
LifecycleInstance newInstance = new LifecycleInstance( instance );
List tmp = new ArrayList<>( instances );
tmp.add( newInstance );
instances = tmp;
bringToState( newInstance );
return instance;
}
private boolean notAlreadyAdded( Lifecycle instance )
{
for ( LifecycleInstance candidate : instances )
{
if ( candidate.instance == instance )
{
throw new IllegalStateException( instance + " already added", candidate.addedWhere );
}
}
return true;
}
public synchronized boolean remove( Lifecycle instance )
{
for ( int i = 0; i < instances.size(); i++ )
{
if ( instances.get( i ).isInstance( instance ) )
{
List tmp = new ArrayList<>( instances );
LifecycleInstance lifecycleInstance = tmp.remove( i );
lifecycleInstance.shutdown();
instances = tmp;
return true;
}
}
return false;
}
public List getLifecycleInstances()
{
return instances.stream().map( l -> l.instance ).collect( toList() );
}
/**
* Shutdown and throw away all the current instances. After
* this you can add new instances. This method does not change
* the status of the LifeSupport (i.e. if it was started it will remain started)
*/
public synchronized void clear()
{
for ( LifecycleInstance instance : instances )
{
instance.shutdown();
}
instances = new ArrayList<>( );
}
public LifecycleStatus getStatus()
{
return status;
}
public synchronized void addLifecycleListener( LifecycleListener listener )
{
listeners.add( listener );
}
private void bringToState( LifecycleInstance instance )
throws LifecycleException
{
switch ( status )
{
case STARTED:
instance.start();
break;
case STOPPED:
instance.init();
break;
default:
break;
}
}
private LifecycleStatus changedStatus( Lifecycle instance,
LifecycleStatus oldStatus,
LifecycleStatus newStatus
)
{
for ( LifecycleListener listener : listeners )
{
listener.notifyStatusChanged( instance, oldStatus, newStatus );
}
return newStatus;
}
public boolean isRunning()
{
return status == LifecycleStatus.STARTED;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder( );
toString( 0, sb );
return sb.toString();
}
private void toString( int indent, StringBuilder sb )
{
for ( int i = 0; i < indent; i++ )
{
sb.append( ' ' );
}
sb.append( "Lifecycle status:" + status.name() ).append( '\n' );
for ( LifecycleInstance instance : instances )
{
if ( instance.instance instanceof LifeSupport )
{
((LifeSupport) instance.instance).toString( indent + 3, sb );
}
else
{
for ( int i = 0; i < indent + 3; i++ )
{
sb.append( ' ' );
}
sb.append( instance.toString() ).append( '\n' );
}
}
}
private class LifecycleInstance
implements Lifecycle
{
Lifecycle instance;
LifecycleStatus currentStatus = LifecycleStatus.NONE;
Exception addedWhere;
private LifecycleInstance( Lifecycle instance )
{
this.instance = instance;
assert trackInstantiationStackTrace();
}
private boolean trackInstantiationStackTrace()
{
addedWhere = new Exception();
return true;
}
@Override
public void init()
throws LifecycleException
{
if ( currentStatus == LifecycleStatus.NONE )
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.INITIALIZING );
try
{
instance.init();
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.STOPPED );
}
catch ( Throwable e )
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.NONE );
try
{
instance.shutdown();
}
catch ( Throwable se )
{
LifecycleException lifecycleException = new LifecycleException( "Exception during graceful " +
"attempt to shutdown partially initialized component. Please use non suppressed" +
" exception to see original component failure.", se );
e.addSuppressed( lifecycleException );
}
if ( e instanceof LifecycleException )
{
throw (LifecycleException) e;
}
throw new LifecycleException( instance, LifecycleStatus.NONE, LifecycleStatus.STOPPED, e );
}
}
}
@Override
public void start()
throws LifecycleException
{
if ( currentStatus == LifecycleStatus.NONE )
{
init();
}
if ( currentStatus == LifecycleStatus.STOPPED )
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.STARTING );
try
{
instance.start();
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.STARTED );
}
catch ( Throwable e )
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.STOPPED );
try
{
instance.stop();
}
catch ( Throwable se )
{
LifecycleException lifecycleException = new LifecycleException( "Exception during graceful " +
"attempt to stop partially started component. Please use non suppressed" +
" exception to see original component failure.", se );
e.addSuppressed( lifecycleException );
}
if ( e instanceof LifecycleException )
{
throw (LifecycleException) e;
}
throw new LifecycleException( instance, LifecycleStatus.STOPPED, LifecycleStatus.STARTED, e );
}
}
}
@Override
public void stop()
throws LifecycleException
{
if ( currentStatus == LifecycleStatus.STARTED )
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.STOPPING );
try
{
instance.stop();
}
catch ( LifecycleException e )
{
throw e;
}
catch ( Throwable e )
{
throw new LifecycleException( instance, LifecycleStatus.STARTED, LifecycleStatus.STOPPED, e );
}
finally
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.STOPPED );
}
}
}
@Override
public void shutdown()
throws LifecycleException
{
if ( currentStatus == LifecycleStatus.STARTED )
{
stop();
}
if ( currentStatus == LifecycleStatus.STOPPED )
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.SHUTTING_DOWN );
try
{
instance.shutdown();
}
catch ( LifecycleException e )
{
throw e;
}
catch ( Throwable e )
{
throw new LifecycleException( instance, LifecycleStatus.STOPPED, LifecycleStatus.SHUTTING_DOWN, e );
}
finally
{
currentStatus = changedStatus( instance, currentStatus, LifecycleStatus.SHUTDOWN );
}
}
}
@Override
public String toString()
{
return instance.toString() + ": " + currentStatus.name();
}
public boolean isInstance( Lifecycle instance )
{
return this.instance == instance;
}
}
}