
net.yudichev.jiotty.common.app.Application Maven / Gradle / Ivy
package net.yudichev.jiotty.common.app;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.inject.*;
import net.yudichev.jiotty.common.inject.LifecycleComponent;
import net.yudichev.jiotty.common.lang.MoreThrowables;
import net.yudichev.jiotty.common.lang.TypedBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
public final class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
static {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
SysOutOverSLF4J.sendSystemOutAndErrToSLF4J();
}
private final List componentsAttemptedToStart = new CopyOnWriteArrayList<>();
private final AtomicBoolean restarting = new AtomicBoolean();
private final AtomicBoolean jvmShuttingDown = new AtomicBoolean();
private final AtomicBoolean startedAllComponentsSuccessfully = new AtomicBoolean();
private final AtomicBoolean runCalled = new AtomicBoolean();
private final Injector injector;
private CountDownLatch shutdownLatch;
private Thread runThread;
private Application(Supplier moduleSupplier) {
ApplicationLifecycleControl applicationLifecycleControl = new ApplicationLifecycleControl() {
@Override
public void initiateShutdown() {
logger.info("Application requested shutdown");
initiateStop();
}
@Override
public void initiateRestart() {
checkState(!jvmShuttingDown.get(), "Cannot initiate restart while JVM is shutting down");
checkState(restarting.compareAndSet(false, true), "Already restarting");
logger.info("Application requested restart");
initiateStop();
}
};
injector = Guice.createInjector(new ApplicationSupportModule(applicationLifecycleControl), moduleSupplier.get());
}
/**
* Start all {@link LifecycleComponent}s.
*
* @throws InterruptedException if the thread was interrupted while starting
* @throws RuntimeException if one of the components failed to start; note components that are already started won't be stopped, use
* {@link #stop()} for that.
*/
public void start() throws InterruptedException {
startedAllComponentsSuccessfully.set(false);
componentsAttemptedToStart.clear();
logger.info("Initialising components");
List allComponents = injector
.findBindingsByType(new TypeLiteral() {})
.stream()
.map(lifecycleComponentBinding -> lifecycleComponentBinding.getProvider().get())
.collect(toImmutableList());
logger.info("Starting components");
for (LifecycleComponent component : allComponents) {
if (Thread.interrupted()) {
throw new InterruptedException(String.format("Interrupted while starting; components attempted to start: %s out of %s",
componentsAttemptedToStart.size(), allComponents.size()));
}
componentsAttemptedToStart.add(component);
start(component);
}
startedAllComponentsSuccessfully.set(true);
logger.info("Started");
}
public Injector getInjector() {
return injector;
}
/**
* Stop all components that have been started - must be called on same thread that called {@link #start()}.
*/
public void stop() {
logger.info("Shutting down");
stop(componentsAttemptedToStart);
componentsAttemptedToStart.clear();
logger.info("Shut down");
}
/**
* Run as a daemon: start and blocks until application initiates shutdown (or JVM is requested to shut down) and all components are stopped.
*/
public void run() {
checkState(runCalled.compareAndSet(false, true), "Application.run() can only be called once");
runThread = Thread.currentThread();
AtomicReference fullyStoppedLatchRef = new AtomicReference<>();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
jvmShuttingDown.set(true);
CountDownLatch fullyStoppedLatch = fullyStoppedLatchRef.get();
if (fullyStoppedLatch != null && fullyStoppedLatch.getCount() > 0) {
logger.info("Shutdown hook fired");
initiateStop();
MoreThrowables.asUnchecked(() -> {
if (!fullyStoppedLatch.await(1, TimeUnit.MINUTES)) {
logger.warn("Timed out waiting for partially initialised application to shut down");
}
});
}
}));
do {
logger.info("Starting");
shutdownLatch = new CountDownLatch(1);
CountDownLatch fullyStoppedLatch = new CountDownLatch(1);
fullyStoppedLatchRef.set(fullyStoppedLatch);
try {
start();
} catch (InterruptedException | RuntimeException e) {
logger.error("Unable to initialize", e);
shutdownLatch.countDown();
// intentionally clearing the interrupted flag to guarantee the immediately following latch await not to fail
//noinspection ResultOfMethodCallIgnored
Thread.interrupted();
}
MoreThrowables.asUnchecked(shutdownLatch::await);
stop();
fullyStoppedLatch.countDown();
} while (restarting.getAndSet(false));
}
public static Builder builder() {
return new Builder();
}
private void initiateStop() {
if (startedAllComponentsSuccessfully.get()) {
shutdownLatch.countDown();
} else {
logger.info("Interrupting startup sequence");
runThread.interrupt();
}
}
private static void start(LifecycleComponent lifecycleComponent) {
logger.info("Starting component {}", lifecycleComponent.name());
lifecycleComponent.start();
logger.info("Started component {}", lifecycleComponent.name());
}
private static void stop(List lifecycleComponents) {
Lists.reverse(lifecycleComponents).forEach(lifecycleComponent -> {
try {
logger.info("Stopping component {}", lifecycleComponent.name());
lifecycleComponent.stop();
logger.info("Stopped component {}", lifecycleComponent.name());
} catch (Throwable e) {
logger.error("Failed stopping component {}", lifecycleComponent.name(), e);
}
});
}
public static final class Builder implements TypedBuilder {
private final ImmutableList.Builder> moduleSupplierListBuilder = ImmutableList.builder();
public Builder addModule(Supplier moduleSupplier) {
moduleSupplierListBuilder.add(moduleSupplier);
return this;
}
@Override
public Application build() {
List> moduleSuppliers = moduleSupplierListBuilder.build();
Module module = new AbstractModule() {
@Override
protected void configure() {
moduleSuppliers.stream()
.map(Supplier::get)
.forEach(this::install);
}
};
return new Application(() -> module);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy