-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add a countdown latch as a startup barrier for use by the RunOnFxThreadInterceptor to avoid race conditions between background threads interacting with javafx.application.Platform thread during startup. fixes #22 Signed-off-by: Scott M Stark <[email protected]> * Updates per the comments fixes #22 Signed-off-by: Scott M Stark <[email protected]> * Updates per the comments fixes #22 Signed-off-by: Scott M Stark <[email protected]> * ClockEvents should be using synchronous events from RunOnFxThread Signed-off-by: Scott M Stark <[email protected]> * Reorganize the code so that FxApplication remains a non-CDI bean Signed-off-by: Scott M Stark <[email protected]> * wip * tests * wip * Add suggestions * Align RunOnFxThread test * 0.2.0 --------- Signed-off-by: Scott M Stark <[email protected]> Co-authored-by: Scott M Stark <[email protected]>
- Loading branch information
1 parent
feb8ab6
commit cefd200
Showing
19 changed files
with
244 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
release: | ||
current-version: "0.1.0" | ||
current-version: "0.2.0" | ||
next-version: "999-SNAPSHOT" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
deployment/src/test/java/io/quarkiverse/fx/deployment/FxStartupTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package io.quarkiverse.fx.deployment; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
|
||
import jakarta.inject.Inject; | ||
|
||
import org.jboss.shrinkwrap.api.ShrinkWrap; | ||
import org.jboss.shrinkwrap.api.spec.JavaArchive; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.Timeout; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkiverse.fx.FxStartupLatch; | ||
import io.quarkiverse.fx.QuarkusFxApplication; | ||
import io.quarkus.runtime.Quarkus; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
|
||
public class FxStartupTest { | ||
|
||
@RegisterExtension | ||
static final QuarkusUnitTest unitTest = new QuarkusUnitTest() | ||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); | ||
|
||
@Inject | ||
FxStartupLatch latch; | ||
|
||
@Test | ||
@Timeout(value = 5) | ||
void test() { | ||
|
||
Assertions.assertNotNull(this.latch); | ||
CompletableFuture.runAsync(() -> Quarkus.run(QuarkusFxApplication.class)); | ||
|
||
try { | ||
this.latch.await(); | ||
} catch (InterruptedException e) { | ||
Assertions.fail(e); | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
deployment/src/test/java/io/quarkiverse/fx/deployment/FxTestConstants.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package io.quarkiverse.fx.deployment; | ||
|
||
public final class FxTestConstants { | ||
|
||
// Allow some time between launch and FX readiness | ||
static final int LAUNCH_TIMEOUT_MS = 3_000; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 44 additions & 55 deletions
99
deployment/src/test/java/io/quarkiverse/fx/deployment/RunOnFxThreadTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,72 @@ | ||
package io.quarkiverse.fx.deployment; | ||
|
||
import static org.awaitility.Awaitility.await; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import jakarta.enterprise.context.Dependent; | ||
import jakarta.enterprise.event.Observes; | ||
|
||
import org.awaitility.Awaitility; | ||
import org.jboss.shrinkwrap.api.ShrinkWrap; | ||
import org.jboss.shrinkwrap.api.spec.JavaArchive; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Disabled; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkiverse.fx.FxApplication; | ||
import io.quarkiverse.fx.PrimaryStage; | ||
import io.quarkiverse.fx.QuarkusFxApplication; | ||
import io.quarkiverse.fx.RunOnFxThread; | ||
import io.quarkus.runtime.Quarkus; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
import javafx.application.Application; | ||
import javafx.application.Platform; | ||
import javafx.stage.Stage; | ||
|
||
@Disabled // Disabled until #9 is fixed | ||
public class RunOnFxThreadTest { | ||
|
||
private static final int LAUNCH_TIMEOUT_MS = 1_000; | ||
private static final int ASYNC_RUN_TIMEOUT_MS = 500; | ||
|
||
@RegisterExtension | ||
static final QuarkusUnitTest config = new QuarkusUnitTest() | ||
.withApplicationRoot((jar) -> jar.addClasses(Observer.class)); | ||
static final QuarkusUnitTest unitTest = new QuarkusUnitTest() | ||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); | ||
|
||
private static boolean primaryStageObserved = false; | ||
private static Boolean regularThread; | ||
private static Boolean fxThread; | ||
|
||
@Test | ||
void annotatedMethodsExecuteOnFxThread() { | ||
// launch | ||
CompletableFuture.runAsync(() -> Application.launch(FxApplication.class)); | ||
// wait for stage to be observed | ||
Awaitility.await().atMost(LAUNCH_TIMEOUT_MS, TimeUnit.MILLISECONDS).until(Observer::stageObserved); | ||
// stage should not be null | ||
Assertions.assertNotNull(Observer.OBSERVED_STAGE.get()); | ||
// | ||
Assertions.assertTrue(Observer.STAGE_OBSERVER_METHOD_THREAD_IS_FX_THREAD); | ||
Assertions.assertFalse(Observer.DO_ON_CURRENT_METHOD_THREAD_IS_FX_THREAD); | ||
Assertions.assertTrue(Observer.DO_ON_FX_METHOD_THREAD_IS_FX_THREAD); | ||
void test() { | ||
// Non-blocking JavaFX launch | ||
CompletableFuture.runAsync(() -> Quarkus.run(QuarkusFxApplication.class)); | ||
|
||
await() | ||
.atMost(FxTestConstants.LAUNCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) | ||
.until(() -> primaryStageObserved); | ||
|
||
// Synchronous regular thread run | ||
this.runOnRegularThread(); | ||
Assertions.assertTrue(regularThread); | ||
|
||
// Asynchronous FX thread run | ||
this.runOnFxThread(); | ||
Assertions.assertNull(fxThread); | ||
|
||
await().atMost(ASYNC_RUN_TIMEOUT_MS, TimeUnit.MILLISECONDS) | ||
.until(() -> fxThread != null); | ||
|
||
Assertions.assertTrue(fxThread); | ||
} | ||
|
||
@Dependent | ||
static class Observer { | ||
public static final AtomicReference<Stage> OBSERVED_STAGE = new AtomicReference<>(); | ||
public static boolean STAGE_OBSERVER_METHOD_THREAD_IS_FX_THREAD; | ||
public static boolean DO_ON_CURRENT_METHOD_THREAD_IS_FX_THREAD; | ||
public static boolean DO_ON_FX_METHOD_THREAD_IS_FX_THREAD; | ||
|
||
public void observeStage(@Observes @PrimaryStage final Stage stage) throws ExecutionException, InterruptedException { | ||
// Observe current thread | ||
STAGE_OBSERVER_METHOD_THREAD_IS_FX_THREAD = Platform.isFxApplicationThread(); | ||
// launch methods and observe threads | ||
ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
executor.submit(this::doOnCurrentThread).get(); | ||
executor.submit(this::doOnFxThread).get(); | ||
executor.shutdownNow(); | ||
// observe stage | ||
Observer.OBSERVED_STAGE.set(stage); | ||
} | ||
|
||
void doOnCurrentThread() { | ||
DO_ON_CURRENT_METHOD_THREAD_IS_FX_THREAD = Platform.isFxApplicationThread(); | ||
} | ||
|
||
@RunOnFxThread | ||
void doOnFxThread() { | ||
DO_ON_FX_METHOD_THREAD_IS_FX_THREAD = Platform.isFxApplicationThread(); | ||
} | ||
|
||
public static boolean stageObserved() { | ||
return OBSERVED_STAGE.get() != null; | ||
} | ||
void observePrimaryStage(@Observes @PrimaryStage final Stage stage) { | ||
Assertions.assertNotNull(stage); | ||
primaryStageObserved = true; | ||
} | ||
|
||
void runOnRegularThread() { | ||
regularThread = !Platform.isFxApplicationThread(); | ||
} | ||
|
||
@RunOnFxThread | ||
void runOnFxThread() { | ||
fxThread = Platform.isFxApplicationThread(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
* xref:index.adoc[Quarkus FX] | ||
* xref:run-on-fx-thread.adoc[] | ||
* xref:index.adoc[] | ||
* xref:run-on-fx-thread.adoc[] | ||
* xref:startup-synchronization.adoc[] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
:project-version: 0.1.0 | ||
:project-version: 0.2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
= Startup Latch | ||
|
||
To synchronize the application with JavaFX readiness, one can use the `FxStartupLatch` bean. | ||
|
||
It provides an `await` method that is released when JavaFX app is ready (primary `Stage` instance is available). | ||
|
||
That is used in the inner `@RunOnFxThread` to ensure app is ready. | ||
|
||
[source, java] | ||
---- | ||
@Inject | ||
FxStartupLatch startupLatch; | ||
// Blocking until FX env is ready | ||
startupLatch.await(); | ||
// FX is ready | ||
---- | ||
|
||
Alternatively, the `FxStartupEvent` can be observed. | ||
|
||
Example of a `SkipPredicated` that can be used in conjunction with a `@Scheduled` | ||
|
||
[source,java] | ||
---- | ||
@Singleton | ||
public class FxApplicationNotStarted implements SkipPredicate { | ||
private volatile boolean started; | ||
void onFxStartup(@Observes final FxStartupEvent event) { | ||
this.started = true; | ||
} | ||
@Override | ||
public boolean test(final ScheduledExecution execution) { | ||
return !this.started; | ||
} | ||
} | ||
---- |
Oops, something went wrong.