
org.neo4j.kernel.lifecycle.LifeSupport Maven / Gradle / Ivy
Show all versions of neo4j-common Show documentation
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://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 static java.lang.String.format;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.neo4j.internal.helpers.Exceptions;
/**
* 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, LifecycleStatusProvider {
private volatile List instances = new ArrayList<>();
private volatile LifecycleStatus status = LifecycleStatus.NONE;
private final List listeners = new ArrayList<>();
private LifecycleInstance last;
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() {
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;
}
}
}
/**
* 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) {
ex = Exceptions.chain(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 {
addNewComponent(instance);
return instance;
}
public synchronized T setLast(T instance) {
if (last != null) {
throw new IllegalStateException(format(
"Lifecycle supports only one last component. Already defined component: %s, new component: %s",
last, instance));
}
last = addNewComponent(instance);
return instance;
}
private LifecycleInstance addNewComponent(T instance) {
Objects.requireNonNull(instance);
validateNotAlreadyPartOfLifecycle(instance);
LifecycleInstance newInstance = new LifecycleInstance(instance);
List tmp = new ArrayList<>(instances);
int position = last != null ? tmp.size() - 1 : tmp.size();
tmp.add(position, newInstance);
instances = tmp;
bringToState(newInstance);
return newInstance;
}
private void validateNotAlreadyPartOfLifecycle(Lifecycle instance) {
for (LifecycleInstance candidate : instances) {
if (candidate.instance == instance) {
throw new IllegalStateException(instance + " already added", candidate.addedWhere);
}
}
}
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) {
ex = Exceptions.chain(ex, e);
}
}
return ex;
}
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).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<>();
}
@Override
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();
case STOPPED -> instance.init();
default -> {}
}
}
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) {
sb.append(" ".repeat(Math.max(0, indent)));
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 {
sb.append(" ".repeat(Math.max(0, indent + 3)));
sb.append(instance).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() {
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 + ": " + currentStatus.name();
}
public boolean isInstance(Lifecycle instance) {
return this.instance == instance;
}
}
}