All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.lifecycle.LifeSupport Maven / Gradle / Ivy

There is a newer version: 2025.03.0
Show newest version
/*
 * 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; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy