SystemTray/src/dorkbox/systemTray/util/EventDispatch.java

118 lines
4.3 KiB
Java

/*
* Copyright 2021 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.util;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.NamedThreadFactory;
/**
* Adds events to a single thread event dispatch, so that regardless of OS, all event callbacks happen on the same thread -- which is NOT
* the GTK/AWT/SWING event dispatch thread. There can be ODD peculiarities across on GTK with how AWT/SWING/JavaFX react with the GTK Event
* Dispatch Thread.
*/
public
class EventDispatch {
/**
* Specifies the thread priority used by the SystemTray event dispatch. By default, the "normal priority" is used.
*/
private static int THREAD_PRIORITY = Thread.NORM_PRIORITY;
private static ExecutorService eventDispatchExecutor = null;
private static volatile CountDownLatch shutdownLatch = null;
private static volatile boolean insideDispatch = false;
/**
* Schedule an event to occur sometime in the future. We do not want to WAIT for a `runnable` to finish, because it is POSSIBLE that
* this runnable wants to perform actions on the SAME dispatch thread that called this, resulting in a deadlock. Because we cannot
* guarantee that this will never happen, we must always run this "in the future" as a queue - so that FIFO is obeyed.
*/
public static
void runLater(final Runnable runnable) {
synchronized(EventDispatch.class) {
if (eventDispatchExecutor == null) {
if (insideDispatch) {
SystemTray.logger.error("Unable to create a new event dispatch, while executing within the same context.");
return;
}
shutdownLatch = new CountDownLatch(1);
eventDispatchExecutor = Executors.newSingleThreadExecutor(
new NamedThreadFactory("SystemTrayEventDispatch",
Thread.currentThread().getThreadGroup(), THREAD_PRIORITY, true));
}
}
eventDispatchExecutor.execute(()->{
insideDispatch = true;
runnable.run();
insideDispatch = false;
});
}
/**
* Shutdown the event dispatch at the end of our current dispatch queue
*/
public static
void shutdown() {
// we have to make sure we shut down on our own thread (and not the JavaFX/SWT/AWT/etc thread)
runLater(()->{
ExecutorService executorService = null;
synchronized (EventDispatch.class) {
executorService = eventDispatchExecutor;
eventDispatchExecutor = null;
}
if (executorService != null) {
final List<Runnable> runnables = executorService.shutdownNow();
for (int i = 0; i < runnables.size(); i++) {
try {
runnables.get(i)
.run();
} catch (Exception e) {
SystemTray.logger.error("Error shutting down EventDispatch", e);
}
}
shutdownLatch.countDown();
}
});
}
/**
* Waits for the event dispatch to finish shutting down
*/
public static void waitForShutdown() {
CountDownLatch latch = null;
synchronized (EventDispatch.class) {
latch = shutdownLatch;
}
if (latch != null) {
try {
latch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}
}
}