JavaFXSafeRunner.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * SteelBlue: DCI User Interfaces
  5.  * http://tidalwave.it/projects/steelblue
  6.  *
  7.  * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *************************************************************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  12.  * You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  17.  * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
  18.  *
  19.  * *************************************************************************************************************************************************************
  20.  *
  21.  * git clone https://bitbucket.org/tidalwave/steelblue-src
  22.  * git clone https://github.com/tidalwave-it/steelblue-src
  23.  *
  24.  * *************************************************************************************************************************************************************
  25.  */
  26. package it.tidalwave.ui.javafx.impl.util;

  27. import jakarta.annotation.Nonnull;
  28. import java.util.concurrent.Callable;
  29. import java.util.concurrent.CountDownLatch;
  30. import java.util.concurrent.TimeUnit;
  31. import java.util.concurrent.atomic.AtomicReference;
  32. import javafx.application.Platform;
  33. import it.tidalwave.ui.javafx.impl.DefaultNodeAndDelegate;
  34. import lombok.SneakyThrows;
  35. import lombok.experimental.UtilityClass;
  36. import lombok.extern.slf4j.Slf4j;

  37. /***************************************************************************************************************************************************************
  38.  *
  39.  * @author  Fabrizio Giudici
  40.  *
  41.  **************************************************************************************************************************************************************/
  42. @UtilityClass @Slf4j
  43. public final class JavaFXSafeRunner
  44.   {
  45.     private static final String P_TIMEOUT = DefaultNodeAndDelegate.class.getName() + ".initTimeout";
  46.     private static final int INITIALIZER_TIMEOUT = Integer.getInteger(P_TIMEOUT, 10);

  47.     /***********************************************************************************************************************************************************
  48.      * {@return the value provided by the given {@link Callable}, running it in the JavaFX thread}.
  49.      * @param   callable    the callable
  50.      **********************************************************************************************************************************************************/
  51.     @SneakyThrows
  52.     public static <T> T runSafelyAndWait (@Nonnull final Callable<T> callable)
  53.           throws Exception
  54.       {
  55.         if (Platform.isFxApplicationThread())
  56.           {
  57.             return callable.call();
  58.           }

  59.         final var latch = new CountDownLatch(1);
  60.         final var result = new AtomicReference<T>();
  61.         final var exception = new AtomicReference<Throwable>();
  62.         final var timeoutMessage = String.format("Likely deadlock in the JavaFX Thread. If you need longer time with the debugger, set -D%s=<v> (current : %s)",
  63.                                                  P_TIMEOUT, INITIALIZER_TIMEOUT);
  64.         Platform.runLater(() ->
  65.           {
  66.             try
  67.               {
  68.                 result.set(callable.call());
  69.               }
  70.             catch (Throwable e)
  71.               {
  72.                 exception.set(e);
  73.               }
  74.             finally
  75.               {
  76.                 latch.countDown();
  77.               }
  78.           });

  79.         try
  80.           {
  81.             log.debug("Waiting for JavaFX thread...");

  82.             if (!latch.await(INITIALIZER_TIMEOUT, TimeUnit.SECONDS))
  83.               {
  84.                 throw new RuntimeException(timeoutMessage);
  85.               }
  86.           }
  87.         catch (InterruptedException e)
  88.           {
  89.             log.info("Interrupted");
  90.             Thread.currentThread().interrupt();
  91.             throw new RuntimeException(e);
  92.           }

  93.         if (exception.get() != null)
  94.           {
  95.             throw exception.get();
  96.           }

  97.         return result.get();
  98.       }
  99.   }