/*
* Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
* 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 de.ks.executor;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.inject.Vetoed;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@Vetoed
public class JavaFXExecutorService extends AbstractExecutorService {
private static final Logger log = LoggerFactory.getLogger(JavaFXExecutorService.class);
final ExecutorService mock;
protected final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
protected volatile boolean shutdown = false;
protected final AtomicReference<QueueListener> queueListener = new AtomicReference<>();
public JavaFXExecutorService() {
this(null);
}
public JavaFXExecutorService(ExecutorService mock) {
this.mock = mock;
}
@Override
public void shutdown() {
shutdown = true;
}
@Override
public List<Runnable> shutdownNow() {
shutdown = true;
return new ArrayList<>(queue);
}
@Override
public boolean isShutdown() {
return shutdown;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void execute(Runnable command) {
if (isCurrentThread()) {
command.run();
} else {
queue.add(command);
triggerQueueReading();
}
}
private boolean isCurrentThread() {
if (mock != null) {
return false;
} else {
return Platform.isFxApplicationThread();
}
}
protected void triggerQueueReading() {
if (isShutdown()) {
return;//no more triggering
}
QueueListener listener = queueListener.get();
if (listener == null || !listener.isRunning()) {
listener = queueListener.updateAndGet(l -> l != null && l.isRunning() ? l : new QueueListener(this));
if (mock != null) {
mock.execute(listener);
} else {
Platform.runLater(listener);
}
}
}
public <T> T invokeInJavaFXThread(Callable<T> callable) {
try {
return submit(callable).get();
} catch (InterruptedException | ExecutionException e) {
log.error("Could not get value from {}", callable, e);
throw new RuntimeException(e);
}
}
public void waitForAllTasksDone() {
waitInternal(false);
}
public void waitInternal(boolean interrupt) {
if (queueListener.get() == null) {
return;
}
if (isCurrentThread()) {
return;
}
long MAX_TIMEOUT = 1000 * 10;
long start = System.currentTimeMillis();
while (queueListener.get().isRunning()) {
try {
TimeUnit.MILLISECONDS.sleep(100);
Thread thread = queueListener.get().getThread();
if (interrupt) {
if (thread != null) {
thread.interrupt();
}
} else if (System.currentTimeMillis() - start > MAX_TIMEOUT) {
if (thread != null) {
String msg = "Waited for " + MAX_TIMEOUT + "ms, will now interrupt the thread";
log.warn(msg);
thread.interrupt();
throw new RuntimeException(msg);
}
}
} catch (InterruptedException e) {
log.trace("Got Interrupted while waiting for tasks.", e);
}
}
}
public Queue<Runnable> getQueue() {
return queue;
}
public int getActiveCount() {
return queue.size();
}
protected static class QueueListener implements Runnable {
private static final Logger log = LoggerFactory.getLogger(QueueListener.class);
protected final JavaFXExecutorService service;
protected final Queue<Runnable> queue;
protected volatile boolean running = true;
protected volatile Thread thread;
public QueueListener(JavaFXExecutorService service) {
this.service = service;
queue = service.getQueue();
}
@Override
public void run() {
log.trace("Start queue processing.");
thread = Thread.currentThread();
if (queue.isEmpty()) {
running = false;
return;
}
if (isCurrentThread()) {
try {
executeQueuedRunnables();
} finally {
running = false;
}
} else {
log.warn("Not in FX application thread. Will not execute runnables");
}
}
public void executeQueuedRunnables() {
long millis = System.currentTimeMillis();
int count = 1;
Runnable runnable = queue.peek();
while (runnable != null && shouldResume(count, millis)) {
log.trace("Executing runnable #{}", count);
try {
runnable.run();
} catch (Throwable e) {
log.error("Could not execute runnable #{}", count, e);
} finally {
queue.poll();
count++;
}
runnable = queue.peek();
}
}
private boolean isCurrentThread() {
if (service.mock != null) {
return true;
} else {
return Platform.isFxApplicationThread();
}
}
protected boolean shouldResume(int count, long startTime) {
return serviceIsRunning();
}
private boolean serviceIsRunning() {
return !service.isShutdown();
}
public void reschedule() {
Platform.runLater(this);
}
public boolean isRunning() {
return running;
}
public Thread getThread() {
return thread;
}
}
}