net.diversionmc.async.schedule.ThreadPoolScheduler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of promise Show documentation
Show all versions of promise Show documentation
Diversion Network async evaluation Promise API.
package net.diversionmc.async.schedule;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Runtime.getRuntime;
import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;
import static java.text.MessageFormat.format;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.IntStream.range;
/**
* A scheduler that performs action in its own sustained threads.
*/
public final class ThreadPoolScheduler implements Scheduler {
/**
* Instance of this thread pool scheduler.
*/
public static final ThreadPoolScheduler SCHEDULER = new ThreadPoolScheduler();
public static final long AWAIT_TIMEOUT_MILLIS = 5000;
private final LinkedBlockingDeque tasks = new LinkedBlockingDeque<>();
private final List threads = new ArrayList<>();
private final Lock runLock = new ReentrantLock();
private final Object runLockUnlock = new Object();
private ThreadPoolScheduler() {
// Create thread that owns the runLock and unlocks it when runLockUnlock is notified
var unlockThread = new Thread(() -> {
runLock.lock();
try {
synchronized (runLockUnlock) {
runLockUnlock.wait();
}
} catch (InterruptedException ignored) {
} finally {
// The unlocked runLock will cause all repeat and delay promises to stop waiting
runLock.unlock();
}
});
unlockThread.setDaemon(true);
unlockThread.start();
// Create a shutdown hook that calls notify on runLockUnlock and waits for threads to finish
getRuntime().addShutdownHook(new Thread(() -> {
try (var out = new PrintStream(new FileOutputStream(FileDescriptor.out))) {
out.println("Shutting down promise worker thread pool...");
synchronized (runLockUnlock) {
runLockUnlock.notify();
}
long timePassed = 0;
while (true) {
ArrayList threadsCopy;
synchronized (threads) {
if (threads.isEmpty()) break;
threadsCopy = new ArrayList<>(threads);
}
out.println(format(
"Current status: {0} worker threads, {1}s passed",
threadsCopy.size(), timePassed));
if (threadsCopy.stream()
.noneMatch(Thread::isAlive)) break;
threadsCopy.stream()
.filter(Thread::isAlive)
.forEach(thread -> {
out.println("Worker thread " + thread.getName() + " is still alive:");
Arrays.stream(thread.getStackTrace())
.forEach(traceElement -> out.println("\tat " + traceElement));
});
try {
//noinspection BusyWait
sleep(AWAIT_TIMEOUT_MILLIS);
timePassed += AWAIT_TIMEOUT_MILLIS / 1000;
} catch (InterruptedException ignored) {
}
}
out.println("Promise worker thread pool has finished working.");
}
}));
}
public Lock runLock() {
return runLock;
}
private void ensureEnoughThreads() {
// 10 tasks - 10 threads = 0
// 10 tasks - 9 threads = 1 new thread
// at worst, it will create useless threads - never not enough threads
synchronized (threads) {
range(0, tasks.size() - (int) threads.stream().filter(t -> !t.busy.get()).count())
.forEach(i -> {
var t = new DNThread();
t.setDaemon(true);
t.start();
threads.add(t);
});
}
}
private class DNThread extends Thread {
private final AtomicBoolean busy = new AtomicBoolean();
public void run() {
while (!tasks.isEmpty()) try {
var task = tasks.poll(5, SECONDS);
// here can be a period, when there is a non-busy thread, but task is removed
if (task == null) break; // die if thread inactive for 5 seconds
busy.set(true);
try {
task.runnable.run();
} catch (Throwable e) { // do not break worker thread
e.printStackTrace();
}
task.quietlyComplete();
busy.set(false);
} catch (InterruptedException ignored) {
// unwanted exception, retry waiting
}
synchronized (threads) {
threads.remove(this);
}
}
}
/**
* Check if this method is called from a thread pool thread or fork join worker thread.
*
* @return True if current thread is a generated thread.
*/
public static boolean allowedAwait() {
var t = currentThread();
return t instanceof DNThread || t instanceof ForkJoinWorkerThread;
}
/**
* Adds a task to the thread pool.
*
* @param asyncAction Action to perform.
* @return Created task future.
*/
public Future> schedule(Runnable asyncAction) {
var task = new DummyTask(asyncAction);
tasks.add(task);
ensureEnoughThreads();
return task;
}
/**
* Task that always counts as completed.
*/
public static final class DummyTask extends ForkJoinTask
© 2015 - 2025 Weber Informatics LLC | Privacy Policy