Skip to content

Commit

Permalink
Fix issue with application state notification
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas committed Apr 6, 2020
1 parent dc3d0da commit 2f35d43
Show file tree
Hide file tree
Showing 11 changed files with 55 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
import io.quarkus.dev.appstate.ApplicationStateNotification;
import io.quarkus.dev.spi.HotReplacementSetup;
import io.quarkus.runner.bootstrap.AugmentActionImpl;
import io.quarkus.runtime.ApplicationLifecycleManager;
Expand Down Expand Up @@ -120,9 +119,6 @@ public void accept(Integer integer) {
public synchronized void restartApp(Set<String> changedResources) {
restarting = true;
stop();

//this clears any old state
ApplicationStateNotification.notifyApplicationStopped();
restarting = false;
Timing.restart(curatedApplication.getAugmentClassLoader());
deploymentProblem = null;
Expand All @@ -133,7 +129,6 @@ public synchronized void restartApp(Set<String> changedResources) {
try {
StartupAction start = augmentAction.reloadExistingApplication(changedResources);
runner = start.runMainClass();
ApplicationStateNotification.waitForApplicationStart();
} catch (Throwable t) {
deploymentProblem = t;
log.error("Failed to start quarkus", t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator;
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.recording.BytecodeRecorderImpl;
import io.quarkus.dev.appstate.ApplicationStateNotification;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.ClassCreator;
Expand Down Expand Up @@ -135,6 +136,8 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
tryBlock.returnValue(null);

CatchBlockCreator cb = tryBlock.addCatch(Throwable.class);
cb.invokeStaticMethod(ofMethod(ApplicationStateNotification.class, "notifyStartupFailed", void.class, Throwable.class),
cb.getCaughtException());
cb.invokeVirtualMethod(ofMethod(StartupContext.class, "close", void.class), startupContext);
cb.throwException(RuntimeException.class, "Failed to start quarkus", cb.getCaughtException());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ public StartupActionImpl(CuratedApplication curatedApplication, BuildResult buil
* of the JVM will exit when the app stops.
*/
public RunningQuarkusApplication runMainClass(String... args) throws Exception {
//first

//first we hack around class loading in the fork join pool
ForkJoinClassLoading.setForkJoinClassLoader(runtimeClassLoader);

//this clears any old state, and gets ready to start again
ApplicationStateNotification.reset();
//we have our class loaders
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(runtimeClassLoader);
Expand All @@ -97,7 +100,10 @@ public void run() {
start.invoke(null, (Object) args);
} catch (Throwable e) {
log.error("Error running Quarkus", e);
ApplicationStateNotification.notifyStartupComplete(e.getCause());
//this can happen if we did not make it to application init
if (ApplicationStateNotification.getState() == ApplicationStateNotification.State.INITIAL) {
ApplicationStateNotification.notifyStartupFailed(e);
}
}
}
}, "Quarkus Main Thread");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,41 @@
* This should generally only be used by dev mode internals that need information about the current
* application state.
*
* This class makes not attempt to verify that an application is starting/stopping when the
* This class makes no attempt to verify that an application is starting/stopping when the
* wait methods are called, this should only be called by a client that is controlling the Quarkus
* lifecycle, and so knows what the current lifecycle state is.
*/
public class ApplicationStateNotification {

private static boolean started = false;
private static State state = State.INITIAL;
private static Throwable startupProblem;

public static synchronized void notifyStartupComplete(Throwable sp) {
started = startupProblem == null;
startupProblem = sp;
public static synchronized void reset() {
if (state == State.STARTED) {
throw new IllegalStateException("Cannot reset a started application");
}
state = State.INITIAL;
startupProblem = null;
}

public static synchronized void notifyStartupComplete() {
state = State.STARTED;
ApplicationStateNotification.class.notifyAll();
}

public static synchronized void notifyApplicationStopped() {
started = false;
startupProblem = null;
state = State.STOPPED;
ApplicationStateNotification.class.notifyAll();
}

public static synchronized void notifyStartupFailed(Throwable t) {
startupProblem = t;
state = State.STOPPED;
ApplicationStateNotification.class.notifyAll();
}

public static synchronized void waitForApplicationStart() {
while (!started && startupProblem == null) {
while (state == State.INITIAL) {
try {
ApplicationStateNotification.class.wait();
} catch (InterruptedException e) {
Expand All @@ -41,12 +53,22 @@ public static synchronized void waitForApplicationStart() {
}

public static synchronized void waitForApplicationStop() {
while (started) {
while (state != State.STOPPED) {
try {
ApplicationStateNotification.class.wait();
} catch (InterruptedException e) {
//ignore
}
}
}

public static State getState() {
return state;
}

public enum State {
INITIAL,
STARTED,
STOPPED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* This has a number of hacks to make it work, and is always going to be a bit fragile, as it is hard to make something
* that will work 100% of the time as we just don't have enough information.
* <p>
* The launcher module has all it's dependencies shaded, so it is effectively self contained. This allows deployment time
* The launcher module has all its dependencies shaded, so it is effectively self contained. This allows deployment time
* code to not leak into runtime code, as the launcher artifact is explicitly excluded from the production build via a
* hard coded exclusion.
*/
Expand Down
6 changes: 0 additions & 6 deletions core/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@
<name>Quarkus - Core - Runtime</name>

<dependencies>
<dependency>
<!-- TMP workaround for IDE problem, DO NOT MERGE-->
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ public final void start(String[] args) {
} finally {
stateLock.unlock();
}
ApplicationStateNotification.notifyStartupComplete(t);
ApplicationStateNotification.notifyStartupFailed(t);
throw t;
}
stateLock.lock();
try {
state = ST_STARTED;
stateCond.signalAll();
ApplicationStateNotification.notifyStartupComplete(null);
ApplicationStateNotification.notifyStartupComplete();
} finally {
stateLock.unlock();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
* Manages the lifecycle of a Quarkus application.
*
* The {@link Application} class is responsible for starting and stopping the application,
* but nothing else. This class can be used to run both persistent application that will run
* but nothing else. This class can be used to run both persistent applications that will run
* till they receive a signal, and command mode applications that will run until the main method
* returns. This class registers a shutdown hook to properly shut down the application, and handled
* returns. This class registers a shutdown hook to properly shut down the application, and handles
* exiting with the supplied exit code.
*
* This class should be used to run production and dev mode applications, while test use cases will
* likely want to just use {@link Application} directly.
*
* This class is static, there can only every be a single application instance running at any time.
* This class is static, there can only ever be a single application instance running at any time.
*
*/
public class ApplicationLifecycleManager {
Expand Down
2 changes: 1 addition & 1 deletion core/runtime/src/main/java/io/quarkus/runtime/Quarkus.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* If this application has already been generated then it will be run directly, otherwise it will be launched
* in dev mode and augmentation will be done automatically.
*
* If an application does not want to immediatly shut down then {@link #waitForExit()} should be called, which
* If an application does not want to immediately shut down then {@link #waitForExit()} should be called, which
* will block until shutdown is initiated, either from an external signal or by a call to one of the exit methods.
*
* If no main class is specified then one is generated automatically that will simply wait to exit after Quarkus is booted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* There are two different ways this annotation can be used. The first is to place it
* on a class with a Java main method. This main method will be the default entry point of
* the application. Note that Quarkus will not be started when this is method is called,
* the application. Note that Quarkus will not be started when this method is called,
* this method must launch Quarkus with the {@link io.quarkus.runtime.Quarkus#run(Class, String...)}
* method.
*
Expand Down
10 changes: 5 additions & 5 deletions docs/src/main/asciidoc/command-mode-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc

include::./attributes.adoc[]

This reference covers how to write application that run and then exit.
This reference covers how to write applications that run and then exit.

== Writing Command Mode Applications

Expand All @@ -25,7 +25,7 @@ The simplest possible command mode application might appear as follows:
[source,java]
----
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.DefaultMain;
import io.quarkus.runtime.annotations.QuarkusMain;
@QuarkusMain // <1>
public class HelloWorldMain implements QuarkusApplication {
Expand All @@ -36,15 +36,15 @@ public class HelloWorldMain implements QuarkusApplication {
}
}
----
<1> The `@QuarkusMain` annotation to tells Quarkus that this is the main entry point.
<1> The `@QuarkusMain` annotation tells Quarkus that this is the main entry point.
<2> The `run` method is invoked once Quarkus starts, and the application stops when it finishes.

If we want to use a Java main to run the application main it would look like:

[source,java]
----
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.DefaultMain;
import io.quarkus.runtime.annotations.QuarkusMain;
@QuarkusMain
public class JavaMain {
Expand Down Expand Up @@ -85,7 +85,7 @@ When running a command mode application the basic lifecycle is as follows:

. Start Quarkus
. Run the `QuarkusApplication` main method
. Shutdown Quarkus and exit the JVM after the main method returns
. Shut down Quarkus and exit the JVM after the main method returns

Shutdown is always initiated by the application main thread returning. If you want to run some logic on startup,
and then run like a normal application (i.e. not exit) then you should call `Quarkus.waitForExit` from the main
Expand Down

0 comments on commit 2f35d43

Please sign in to comment.