From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Mon, 21 Oct 2024 11:47:34 -0700 Subject: [PATCH] fixup! ConcurrentUtil diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java deleted file mode 100644 index 094eff418b4e3bffce020d650931b4d9e58fa9ed..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java +++ /dev/null @@ -1,149 +0,0 @@ -package ca.spottedleaf.concurrentutil.collection; - -import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -import ca.spottedleaf.concurrentutil.util.Validate; - -import java.lang.invoke.VarHandle; -import java.util.ConcurrentModificationException; - -/** - * Single reader thread single writer thread queue. The reader side of the queue is ordered by acquire semantics, - * and the writer side of the queue is ordered by release semantics. - */ -// TODO test -public class SRSWLinkedQueue { - - // always non-null - protected LinkedNode head; - - // always non-null - protected LinkedNode tail; - - /* IMPL NOTE: Leave hashCode and equals to their defaults */ - - public SRSWLinkedQueue() { - final LinkedNode dummy = new LinkedNode<>(null, null); - this.head = this.tail = dummy; - } - - /** - * Must be the reader thread. - * - *

- * Returns, without removing, the first element of this queue. - *

- * @return Returns, without removing, the first element of this queue. - */ - public E peekFirst() { - LinkedNode head = this.head; - E ret = head.getElementPlain(); - if (ret == null) { - head = head.getNextAcquire(); - if (head == null) { - // empty - return null; - } - // update head reference for next poll() call - this.head = head; - // guaranteed to be non-null - ret = head.getElementPlain(); - if (ret == null) { - throw new ConcurrentModificationException("Multiple reader threads"); - } - } - - return ret; - } - - /** - * Must be the reader thread. - * - *

- * Returns and removes the first element of this queue. - *

- * @return Returns and removes the first element of this queue. - */ - public E poll() { - LinkedNode head = this.head; - E ret = head.getElementPlain(); - if (ret == null) { - head = head.getNextAcquire(); - if (head == null) { - // empty - return null; - } - // guaranteed to be non-null - ret = head.getElementPlain(); - if (ret == null) { - throw new ConcurrentModificationException("Multiple reader threads"); - } - } - - head.setElementPlain(null); - LinkedNode next = head.getNextAcquire(); - this.head = next == null ? head : next; - - return ret; - } - - /** - * Must be the writer thread. - * - *

- * Adds the element to the end of the queue. - *

- * - * @throws NullPointerException If the provided element is null - */ - public void addLast(final E element) { - Validate.notNull(element, "Provided element cannot be null"); - final LinkedNode append = new LinkedNode<>(element, null); - - this.tail.setNextRelease(append); - this.tail = append; - } - - protected static final class LinkedNode { - - protected volatile Object element; - protected volatile LinkedNode next; - - protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class); - protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class); - - protected LinkedNode(final Object element, final LinkedNode next) { - ELEMENT_HANDLE.set(this, element); - NEXT_HANDLE.set(this, next); - } - - /* element */ - - @SuppressWarnings("unchecked") - protected final E getElementPlain() { - return (E)ELEMENT_HANDLE.get(this); - } - - protected final void setElementPlain(final E update) { - ELEMENT_HANDLE.set(this, (Object)update); - } - /* next */ - - @SuppressWarnings("unchecked") - protected final LinkedNode getNextPlain() { - return (LinkedNode)NEXT_HANDLE.get(this); - } - - @SuppressWarnings("unchecked") - protected final LinkedNode getNextAcquire() { - return (LinkedNode)NEXT_HANDLE.getAcquire(this); - } - - protected final void setNextPlain(final LinkedNode next) { - NEXT_HANDLE.set(this, next); - } - - protected final void setNextRelease(final LinkedNode next) { - NEXT_HANDLE.setRelease(this, next); - } - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java new file mode 100644 index 0000000000000000000000000000000000000000..6bad6f8ecc0944d2f406924c7de7e227ff1e70fa --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java @@ -0,0 +1,110 @@ +package ca.spottedleaf.concurrentutil.completable; + +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; +import ca.spottedleaf.concurrentutil.executor.Cancellable; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.function.BiConsumer; + +public final class CallbackCompletable { + + private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class); + + private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); + private T result; + private Throwable throwable; + private volatile boolean completed; + + public boolean isCompleted() { + return this.completed; + } + + /** + * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero + * synchronisation + */ + public T getResult() { + return this.result; + } + + /** + * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero + * synchronisation + */ + public Throwable getThrowable() { + return this.throwable; + } + + /** + * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete() + * has already been called, returns {@code null} and does not invoke the specified consumer. + * @param consumer Consumer to be executed on completion + * @throws NullPointerException If consumer is null + * @return A cancellable which will control the execution of the specified consumer + */ + public Cancellable addAsynchronousWaiter(final BiConsumer consumer) { + if (this.waiters.add(consumer)) { + return new CancellableImpl(consumer); + } + return null; + } + + private void completeAllWaiters(final T result, final Throwable throwable) { + this.completed = true; + BiConsumer waiter; + while ((waiter = this.waiters.pollOrBlockAdds()) != null) { + this.completeWaiter(waiter, result, throwable); + } + } + + private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) { + try { + consumer.accept(result, throwable); + } catch (final Throwable throwable2) { + LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2); + } + } + + /** + * Adds a waiter that will be completed asynchronously by the complete() calls. If complete() + * has already been called, then invokes the consumer synchronously with the completed result. + * @param consumer Consumer to be executed on completion + * @throws NullPointerException If consumer is null + * @return A cancellable which will control the execution of the specified consumer + */ + public Cancellable addWaiter(final BiConsumer consumer) { + if (this.waiters.add(consumer)) { + return new CancellableImpl(consumer); + } + this.completeWaiter(consumer, this.result, this.throwable); + return new CancellableImpl(consumer); + } + + public void complete(final T result) { + this.result = result; + this.completeAllWaiters(result, null); + } + + public void completeWithThrowable(final Throwable throwable) { + if (throwable == null) { + throw new NullPointerException("Throwable cannot be null"); + } + this.throwable = throwable; + this.completeAllWaiters(null, throwable); + } + + private final class CancellableImpl implements Cancellable { + + private final BiConsumer waiter; + + private CancellableImpl(final BiConsumer waiter) { + this.waiter = waiter; + } + + @Override + public boolean cancel() { + return CallbackCompletable.this.waiters.remove(this.waiter); + } + } +} \ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java index 46d1bd01542ebeeffc0006a5c585a50dbbbff907..365616439fa079017d648ed7f6ddf6950a691adf 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java @@ -1,112 +1,737 @@ package ca.spottedleaf.concurrentutil.completable; -import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -import ca.spottedleaf.concurrentutil.executor.Cancellable; import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import ca.spottedleaf.concurrentutil.util.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.invoke.VarHandle; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.locks.LockSupport; import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; public final class Completable { private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class); + private static final Function DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> { + LOGGER.error("Unhandled exception during Completable operation", thr); + return thr; + }; - private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); - private T result; - private Throwable throwable; - private volatile boolean completed; + public static Executor getDefaultExecutor() { + return ForkJoinPool.commonPool(); + } + + private static final Transform COMPLETED_STACK = new Transform<>(null, null, null, null) { + @Override + public void run() {} + }; + private volatile Transform completeStack; + private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class); + + private static final Object NULL_MASK = new Object(); + private volatile Object result; + private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class); - public boolean isCompleted() { - return this.completed; + private Object getResultPlain() { + return (Object)RESULT_HANDLE.get(this); } - /** - * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero - * synchronisation - */ - public T getResult() { - return this.result; + private Object getResultVolatile() { + return (Object)RESULT_HANDLE.getVolatile(this); } - /** - * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero - * synchronisation - */ - public Throwable getThrowable() { - return this.throwable; + private void pushStackOrRun(final Transform push) { + int failures = 0; + for (Transform curr = (Transform)COMPLETE_STACK_HANDLE.getVolatile(this);;) { + if (curr == COMPLETED_STACK) { + push.execute(); + return; + } + + push.next = curr; + + for (int i = 0; i < failures; ++i) { + ConcurrentUtil.backoff(); + } + + if (curr == (curr = (Transform)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) { + return; + } + push.next = null; + ++failures; + } } - /** - * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete() - * has already been called, returns {@code null} and does not invoke the specified consumer. - * @param consumer Consumer to be executed on completion - * @throws NullPointerException If consumer is null - * @return A cancellable which will control the execution of the specified consumer - */ - public Cancellable addAsynchronousWaiter(final BiConsumer consumer) { - if (this.waiters.add(consumer)) { - return new CancellableImpl(consumer); + private void propagateStack() { + Transform topStack = (Transform)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK); + while (topStack != null) { + topStack.execute(); + topStack = topStack.next; } - return null; } - private void completeAllWaiters(final T result, final Throwable throwable) { - this.completed = true; - BiConsumer waiter; - while ((waiter = this.waiters.pollOrBlockAdds()) != null) { - this.completeWaiter(waiter, result, throwable); + private static Object maskNull(final Object res) { + return res == null ? NULL_MASK : res; + } + + private static Object unmaskNull(final Object res) { + return res == NULL_MASK ? null : res; + } + + private static Executor checkExecutor(final Executor executor) { + return Validate.notNull(executor, "Executor may not be null"); + } + + public Completable() {} + + private Completable(final Object complete) { + COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK); + RESULT_HANDLE.setRelease(this, complete); + } + + public static Completable completed(final T value) { + return new Completable<>(maskNull(value)); + } + + public static Completable failed(final Throwable ex) { + Validate.notNull(ex, "Exception may not be null"); + + return new Completable<>(new ExceptionResult(ex)); + } + + public static Completable supplied(final Supplier supplier) { + return supplied(supplier, DEFAULT_EXCEPTION_HANDLER); + } + + public static Completable supplied(final Supplier supplier, final Function exceptionHandler) { + try { + return completed(supplier.get()); + } catch (final Throwable throwable) { + Throwable complete; + try { + complete = exceptionHandler.apply(throwable); + } catch (final Throwable thr2) { + throwable.addSuppressed(thr2); + complete = throwable; + } + return failed(complete); } } - private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) { + public static Completable suppliedAsync(final Supplier supplier, final Executor executor) { + return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public static Completable suppliedAsync(final Supplier supplier, final Executor executor, final Function exceptionHandler) { + final Completable ret = new Completable<>(); + + class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask { + @Override + public void run() { + try { + ret.complete(supplier.get()); + } catch (final Throwable throwable) { + Throwable complete; + try { + complete = exceptionHandler.apply(throwable); + } catch (final Throwable thr2) { + throwable.addSuppressed(thr2); + complete = throwable; + } + ret.completeExceptionally(complete); + } + } + } + try { - consumer.accept(result, throwable); - } catch (final ThreadDeath death) { - throw death; - } catch (final Throwable throwable2) { - LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2); + executor.execute(new AsyncSuppliedCompletable()); + } catch (final Throwable throwable) { + Throwable complete; + try { + complete = exceptionHandler.apply(throwable); + } catch (final Throwable thr2) { + throwable.addSuppressed(thr2); + complete = throwable; + } + ret.completeExceptionally(complete); + } + + return ret; + } + + private boolean completeRaw(final Object value) { + if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) { + return false; + } + + this.propagateStack(); + return true; + } + + public boolean complete(final T result) { + return this.completeRaw(maskNull(result)); + } + + public boolean completeExceptionally(final Throwable exception) { + Validate.notNull(exception, "Exception may not be null"); + + return this.completeRaw(new ExceptionResult(exception)); + } + + public boolean isDone() { + return this.getResultVolatile() != null; + } + + public boolean isNormallyComplete() { + return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult); + } + + public boolean isExceptionallyComplete() { + return this.getResultVolatile() instanceof ExceptionResult; + } + + public Throwable getException() { + final Object res = this.getResultVolatile(); + if (res == null) { + return null; + } + + if (!(res instanceof ExceptionResult exRes)) { + throw new IllegalStateException("Not completed exceptionally"); + } + + return exRes.ex; + } + + public T getNow(final T dfl) throws CompletionException { + final Object res = this.getResultVolatile(); + if (res == null) { + return dfl; + } + + if (res instanceof ExceptionResult exRes) { + throw new CompletionException(exRes.ex); + } + + return (T)unmaskNull(res); + } + + public T join() throws CompletionException { + if (this.isDone()) { + return this.getNow(null); + } + + final UnparkTransform unparkTransform = new UnparkTransform<>(this, Thread.currentThread()); + + this.pushStackOrRun(unparkTransform); + + boolean interuptted = false; + while (!unparkTransform.isReleasable()) { + try { + ForkJoinPool.managedBlock(unparkTransform); + } catch (final InterruptedException ex) { + interuptted = true; + } + } + + if (interuptted) { + Thread.currentThread().interrupt(); + } + + return this.getNow(null); + } + + public CompletableFuture toFuture() { + final Object rawResult = this.getResultVolatile(); + if (rawResult != null) { + if (rawResult instanceof ExceptionResult exRes) { + return CompletableFuture.failedFuture(exRes.ex); + } else { + return CompletableFuture.completedFuture((T)unmaskNull(rawResult)); + } + } + + final CompletableFuture ret = new CompletableFuture<>(); + + class ToFuture implements BiConsumer { + + @Override + public void accept(final T res, final Throwable ex) { + if (ex != null) { + ret.completeExceptionally(ex); + } else { + ret.complete(res); + } + } + } + + this.whenComplete(new ToFuture()); + + return ret; + } + + public static Completable fromFuture(final CompletionStage stage) { + final Completable ret = new Completable<>(); + + class FromFuture implements BiConsumer { + @Override + public void accept(final T res, final Throwable ex) { + if (ex != null) { + ret.completeExceptionally(ex); + } else { + ret.complete(res); + } + } + } + + stage.whenComplete(new FromFuture()); + + return ret; + } + + + public Completable thenApply(final Function function) { + return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenApply(final Function function, final Function exceptionHandler) { + Validate.notNull(function, "Function may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function)); + return ret; + } + + public Completable thenApplyAsync(final Function function) { + return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenApplyAsync(final Function function, final Executor executor) { + return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenApplyAsync(final Function function, final Executor executor, final Function exceptionHandler) { + Validate.notNull(function, "Function may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); + return ret; + } + + + public Completable thenAccept(final Consumer consumer) { + return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenAccept(final Consumer consumer, final Function exceptionHandler) { + Validate.notNull(consumer, "Consumer may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer)); + return ret; + } + + public Completable thenAcceptAsync(final Consumer consumer) { + return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenAcceptAsync(final Consumer consumer, final Executor executor) { + return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenAcceptAsync(final Consumer consumer, final Executor executor, final Function exceptionHandler) { + Validate.notNull(consumer, "Consumer may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); + return ret; + } + + + public Completable thenRun(final Runnable run) { + return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenRun(final Runnable run, final Function exceptionHandler) { + Validate.notNull(run, "Run may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run)); + return ret; + } + + public Completable thenRunAsync(final Runnable run) { + return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenRunAsync(final Runnable run, final Executor executor) { + return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable thenRunAsync(final Runnable run, final Executor executor, final Function exceptionHandler) { + Validate.notNull(run, "Run may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run)); + return ret; + } + + + public Completable handle(final BiFunction function) { + return this.handle(function, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable handle(final BiFunction function, + final Function exceptionHandler) { + Validate.notNull(function, "Function may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function)); + return ret; + } + + public Completable handleAsync(final BiFunction function) { + return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + + public Completable handleAsync(final BiFunction function, + final Executor executor) { + return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable handleAsync(final BiFunction function, + final Executor executor, + final Function exceptionHandler) { + Validate.notNull(function, "Function may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); + return ret; + } + + + public Completable whenComplete(final BiConsumer consumer) { + return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable whenComplete(final BiConsumer consumer, final Function exceptionHandler) { + Validate.notNull(consumer, "Consumer may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer)); + return ret; + } + + public Completable whenCompleteAsync(final BiConsumer consumer) { + return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + + public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor) { + return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable whenCompleteAsync(final BiConsumer consumer, final Executor executor, + final Function exceptionHandler) { + Validate.notNull(consumer, "Consumer may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer)); + return ret; + } + + + public Completable exceptionally(final Function function) { + return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable exceptionally(final Function function, final Function exceptionHandler) { + Validate.notNull(function, "Function may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function)); + return ret; + } + + public Completable exceptionallyAsync(final Function function) { + return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER); + } + + public Completable exceptionallyAsync(final Function function, final Executor executor) { + return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER); + } + + public Completable exceptionallyAsync(final Function function, final Executor executor, + final Function exceptionHandler) { + Validate.notNull(function, "Function may not be null"); + Validate.notNull(exceptionHandler, "Exception handler may not be null"); + + final Completable ret = new Completable<>(); + this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function)); + return ret; + } + + private static final class ExceptionResult { + public final Throwable ex; + + public ExceptionResult(final Throwable ex) { + this.ex = ex; } } - /** - * Adds a waiter that will be completed asynchronously by the complete() calls. If complete() - * has already been called, then invokes the consumer synchronously with the completed result. - * @param consumer Consumer to be executed on completion - * @throws NullPointerException If consumer is null - * @return A cancellable which will control the execution of the specified consumer - */ - public Cancellable addWaiter(final BiConsumer consumer) { - if (this.waiters.add(consumer)) { - return new CancellableImpl(consumer); + private static abstract class Transform implements Runnable, CompletableFuture.AsynchronousCompletionTask { + + private Transform next; + + private final Executor executor; + protected final Completable from; + protected final Completable to; + protected final Function exceptionHandler; + + protected Transform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler) { + this.executor = executor; + this.from = from; + this.to = to; + this.exceptionHandler = exceptionHandler; + } + + // force interface call to become virtual call + @Override + public abstract void run(); + + protected void failed(final Throwable throwable) { + Throwable complete; + try { + complete = this.exceptionHandler.apply(throwable); + } catch (final Throwable thr2) { + throwable.addSuppressed(thr2); + complete = throwable; + } + this.to.completeExceptionally(complete); + } + + public void execute() { + if (this.executor == null) { + this.run(); + return; + } + + try { + this.executor.execute(this); + } catch (final Throwable throwable) { + this.failed(throwable); + } + } + } + + private static final class ApplyTransform extends Transform { + + private final Function function; + + public ApplyTransform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler, + final Function function) { + super(executor, from, to, exceptionHandler); + this.function = function; + } + + @Override + public void run() { + final Object result = this.from.getResultPlain(); + try { + if (result instanceof ExceptionResult exRes) { + this.to.completeExceptionally(exRes.ex); + } else { + this.to.complete(this.function.apply((T)unmaskNull(result))); + } + } catch (final Throwable throwable) { + this.failed(throwable); + } } - this.completeWaiter(consumer, this.result, this.throwable); - return new CancellableImpl(consumer); } - public void complete(final T result) { - this.result = result; - this.completeAllWaiters(result, null); + private static final class AcceptTransform extends Transform { + private final Consumer consumer; + + public AcceptTransform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler, + final Consumer consumer) { + super(executor, from, to, exceptionHandler); + this.consumer = consumer; + } + + @Override + public void run() { + final Object result = this.from.getResultPlain(); + try { + if (result instanceof ExceptionResult exRes) { + this.to.completeExceptionally(exRes.ex); + } else { + this.consumer.accept((T)unmaskNull(result)); + this.to.complete(null); + } + } catch (final Throwable throwable) { + this.failed(throwable); + } + } } - public void completeWithThrowable(final Throwable throwable) { - if (throwable == null) { - throw new NullPointerException("Throwable cannot be null"); + private static final class RunTransform extends Transform { + private final Runnable run; + + public RunTransform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler, + final Runnable run) { + super(executor, from, to, exceptionHandler); + this.run = run; + } + + @Override + public void run() { + final Object result = this.from.getResultPlain(); + try { + if (result instanceof ExceptionResult exRes) { + this.to.completeExceptionally(exRes.ex); + } else { + this.run.run(); + this.to.complete(null); + } + } catch (final Throwable throwable) { + this.failed(throwable); + } } - this.throwable = throwable; - this.completeAllWaiters(null, throwable); } - private final class CancellableImpl implements Cancellable { + private static final class HandleTransform extends Transform { + + private final BiFunction function; + + public HandleTransform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler, + final BiFunction function) { + super(executor, from, to, exceptionHandler); + this.function = function; + } - private final BiConsumer waiter; + @Override + public void run() { + final Object result = this.from.getResultPlain(); + try { + if (result instanceof ExceptionResult exRes) { + this.to.complete(this.function.apply(null, exRes.ex)); + } else { + this.to.complete(this.function.apply((T)unmaskNull(result), null)); + } + } catch (final Throwable throwable) { + this.failed(throwable); + } + } + } + + private static final class WhenTransform extends Transform { + + private final BiConsumer consumer; + + public WhenTransform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler, + final BiConsumer consumer) { + super(executor, from, to, exceptionHandler); + this.consumer = consumer; + } + + @Override + public void run() { + final Object result = this.from.getResultPlain(); + try { + if (result instanceof ExceptionResult exRes) { + this.consumer.accept(null, exRes.ex); + this.to.completeExceptionally(exRes.ex); + } else { + final T unmasked = (T)unmaskNull(result); + this.consumer.accept(unmasked, null); + this.to.complete(unmasked); + } + } catch (final Throwable throwable) { + this.failed(throwable); + } + } + } + + private static final class ExceptionallyTransform extends Transform { + private final Function function; + + public ExceptionallyTransform(final Executor executor, final Completable from, final Completable to, + final Function exceptionHandler, + final Function function) { + super(executor, from, to, exceptionHandler); + this.function = function; + } + + @Override + public void run() { + final Object result = this.from.getResultPlain(); + try { + if (result instanceof ExceptionResult exRes) { + this.to.complete(this.function.apply(exRes.ex)); + } else { + this.to.complete((T)unmaskNull(result)); + } + } catch (final Throwable throwable) { + this.failed(throwable); + } + } + } + + private static final class UnparkTransform extends Transform implements ForkJoinPool.ManagedBlocker { + + private volatile Thread thread; + + public UnparkTransform(final Completable from, final Thread target) { + super(null, from, null, null); + this.thread = target; + } + + @Override + public void run() { + final Thread t = this.thread; + this.thread = null; + LockSupport.unpark(t); + } + + @Override + public boolean block() throws InterruptedException { + while (!this.isReleasable()) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + LockSupport.park(this); + } - private CancellableImpl(final BiConsumer waiter) { - this.waiter = waiter; + return true; } @Override - public boolean cancel() { - return Completable.this.waiters.remove(this.waiter); + public boolean isReleasable() { + return this.thread == null; } } } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java deleted file mode 100644 index 18d646676fd022afd64afaac30ec1bd283a73b0e..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java +++ /dev/null @@ -1,208 +0,0 @@ -package ca.spottedleaf.concurrentutil.executor; - -import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -import java.util.function.BooleanSupplier; - -/** - * Base implementation for an abstract queue of tasks which are executed either synchronously or asynchronously. - * - *

- * The implementation supports tracking task executions using {@link #getTotalTasksScheduled()} and - * {@link #getTotalTasksExecuted()}, and optionally shutting down the executor using {@link #shutdown()} - *

- * - *

- * The base implementation does not provide a method to queue a task for execution, rather that is specified in - * the specific implementation. However, it is required that a specific implementation provides a method to - * queue a task or create a task. A queued task is one which will eventually be executed, - * and a created task must be queued to execute via {@link BaseTask#queue()} or be executed manually via - * {@link BaseTask#execute()}. This choice of delaying the queueing of a task may be useful to provide a task handle - * which may be cancelled or adjusted before the actual real task logic is ready to be executed. - *

- */ -public interface BaseExecutor { - - /** - * Returns whether every task scheduled to this queue has been removed and executed or cancelled. If no tasks have been queued, - * returns {@code true}. - * - * @return {@code true} if all tasks that have been queued have finished executing or no tasks have been queued, {@code false} otherwise. - */ - public default boolean haveAllTasksExecuted() { - // order is important - // if new tasks are scheduled between the reading of these variables, scheduled is guaranteed to be higher - - // so our check fails, and we try again - final long completed = this.getTotalTasksExecuted(); - final long scheduled = this.getTotalTasksScheduled(); - - return completed == scheduled; - } - - /** - * Returns the number of tasks that have been scheduled or execute or are pending to be scheduled. - */ - public long getTotalTasksScheduled(); - - /** - * Returns the number of tasks that have fully been executed. - */ - public long getTotalTasksExecuted(); - - /** - * Waits until this queue has had all of its tasks executed (NOT removed). See {@link #haveAllTasksExecuted()} - *

- * This call is most effective after a {@link #shutdown()} call, as the shutdown call guarantees no tasks can - * be executed and the waitUntilAllExecuted call makes sure the queue is empty. Effectively, using shutdown then using - * waitUntilAllExecuted ensures this queue is empty - and most importantly, will remain empty. - *

- *

- * This method is not guaranteed to be immediately responsive to queue state, so calls may take significantly more - * time than expected. Effectively, do not rely on this call being fast - even if there are few tasks scheduled. - *

- *

- * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this call. - *

- * - * @throws IllegalStateException If the current thread is not allowed to wait - */ - public default void waitUntilAllExecuted() throws IllegalStateException { - long failures = 1L; // start at 0.25ms - - while (!this.haveAllTasksExecuted()) { - Thread.yield(); - failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms - } - } - - /** - * Executes the next available task. - * - * @return {@code true} if a task was executed, {@code false} otherwise - * @throws IllegalStateException If the current thread is not allowed to execute a task - */ - public boolean executeTask() throws IllegalStateException; - - /** - * Executes all queued tasks. - * - * @return {@code true} if a task was executed, {@code false} otherwise - * @throws IllegalStateException If the current thread is not allowed to execute a task - */ - public default boolean executeAll() { - if (!this.executeTask()) { - return false; - } - - while (this.executeTask()); - - return true; - } - - /** - * Waits and executes tasks until the condition returns {@code true}. - *

- * WARNING: This function is not suitable for waiting until a deadline! - * Use {@link #executeUntil(long)} or {@link #executeConditionally(BooleanSupplier, long)} instead. - *

- */ - public default void executeConditionally(final BooleanSupplier condition) { - long failures = 0; - while (!condition.getAsBoolean()) { - if (this.executeTask()) { - failures = failures >>> 2; - } else { - failures = ConcurrentUtil.linearLongBackoff(failures, 100_000L, 10_000_000L); // 100us, 10ms - } - } - } - - /** - * Waits and executes tasks until the condition returns {@code true} or {@code System.nanoTime() - deadline >= 0}. - */ - public default void executeConditionally(final BooleanSupplier condition, final long deadline) { - long failures = 0; - // double check deadline; we don't know how expensive the condition is - while ((System.nanoTime() - deadline < 0L) && !condition.getAsBoolean() && (System.nanoTime() - deadline < 0L)) { - if (this.executeTask()) { - failures = failures >>> 2; - } else { - failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms - } - } - } - - /** - * Waits and executes tasks until {@code System.nanoTime() - deadline >= 0}. - */ - public default void executeUntil(final long deadline) { - long failures = 0; - while (System.nanoTime() - deadline < 0L) { - if (this.executeTask()) { - failures = failures >>> 2; - } else { - failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms - } - } - } - - /** - * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will - * result in {@link IllegalStateException} being thrown. - *

- * This operation is atomic with respect to other shutdown calls - *

- *

- * After this call has completed, regardless of return value, this queue will be shutdown. - *

- * - * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already - * @throws UnsupportedOperationException If this queue does not support shutdown - * @see #isShutdown() - */ - public default boolean shutdown() throws UnsupportedOperationException { - throw new UnsupportedOperationException(); - } - - /** - * Returns whether this queue has shut down. Effectively, whether new tasks will be rejected - this method - * does not indicate whether all the tasks scheduled have been executed. - * @return Returns whether this queue has shut down. - * @see #waitUntilAllExecuted() - */ - public default boolean isShutdown() { - return false; - } - - /** - * Task object returned for any {@link BaseExecutor} scheduled task. - * @see BaseExecutor - */ - public static interface BaseTask extends Cancellable { - - /** - * Causes a lazily queued task to become queued or executed - * - * @throws IllegalStateException If the backing queue has shutdown - * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed - */ - public boolean queue(); - - /** - * Forces this task to be marked as completed. - * - * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed. - */ - @Override - public boolean cancel(); - - /** - * Executes this task. This will also mark the task as completing. - *

- * Exceptions thrown from the runnable will be rethrown. - *

- * - * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. - */ - public boolean execute(); - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..17cbaee1e89bd3f6d905e640d20d0119ab0570a0 --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java @@ -0,0 +1,271 @@ +package ca.spottedleaf.concurrentutil.executor; + +import ca.spottedleaf.concurrentutil.util.Priority; + +public interface PrioritisedExecutor { + + /** + * Returns the number of tasks that have been scheduled are pending to be scheduled. + */ + public long getTotalTasksScheduled(); + + /** + * Returns the number of tasks that have been executed. + */ + public long getTotalTasksExecuted(); + + /** + * Generates the next suborder id. + * @return The next suborder id. + */ + public long generateNextSubOrder(); + + /** + * Executes the next available task. + *

+ * If there is a task with priority {@link Priority#BLOCKING} available, then that such task is executed. + *

+ *

+ * If there is a task with priority {@link Priority#IDLE} available then that task is only executed + * when there are no other tasks available with a higher priority. + *

+ *

+ * If there are no tasks that have priority {@link Priority#BLOCKING} or {@link Priority#IDLE}, then + * this function will be biased to execute tasks that have higher priorities. + *

+ * + * @return {@code true} if a task was executed, {@code false} otherwise + * @throws IllegalStateException If the current thread is not allowed to execute a task + */ + public boolean executeTask() throws IllegalStateException; + + /** + * Prevent further additions to this executor. Attempts to add after this call has completed (potentially during) will + * result in {@link IllegalStateException} being thrown. + *

+ * This operation is atomic with respect to other shutdown calls + *

+ *

+ * After this call has completed, regardless of return value, this executor will be shutdown. + *

+ * + * @return {@code true} if the executor was shutdown, {@code false} if it has shut down already + * @see #isShutdown() + */ + public boolean shutdown(); + + /** + * Returns whether this executor has shut down. Effectively, returns whether new tasks will be rejected. + * This method does not indicate whether all the tasks scheduled have been executed. + * @return Returns whether this executor has shut down. + */ + public boolean isShutdown(); + + /** + * Queues or executes a task at {@link Priority#NORMAL} priority. + * @param task The task to run. + * + * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task + * associated with the parameter + */ + public PrioritisedTask queueTask(final Runnable task); + + /** + * Queues or executes a task. + * + * @param task The task to run. + * @param priority The priority for the task. + * + * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task + * associated with the parameter + */ + public PrioritisedTask queueTask(final Runnable task, final Priority priority); + + /** + * Queues or executes a task. + * + * @param task The task to run. + * @param priority The priority for the task. + * @param subOrder The task's suborder. + * + * @throws IllegalStateException If this executor has shutdown. + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task + * associated with the parameter + */ + public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder); + + /** + * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. + * @param task The task to run. + * + * @throws NullPointerException If the task is null + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task + * associated with the parameter + */ + public PrioritisedTask createTask(final Runnable task); + + /** + * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. + * + * @param task The task to run. + * @param priority The priority for the task. + * + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task + * associated with the parameter + */ + public PrioritisedTask createTask(final Runnable task, final Priority priority); + + /** + * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority. + * + * @param task The task to run. + * @param priority The priority for the task. + * @param subOrder The task's suborder. + * + * @throws NullPointerException If the task is null + * @throws IllegalArgumentException If the priority is invalid. + * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task + * associated with the parameter + */ + public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder); + + public static interface PrioritisedTask extends Cancellable { + + /** + * Returns the executor associated with this task. + * @return The executor associated with this task. + */ + public PrioritisedExecutor getExecutor(); + + /** + * Causes a lazily queued task to become queued or executed + * + * @throws IllegalStateException If the backing executor has shutdown + * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed + */ + public boolean queue(); + + /** + * Returns whether this task has been queued and is not completing. + * @return {@code true} If the task has been queued, {@code false} if the task has not been queued or is marked + * as completing. + */ + public boolean isQueued(); + + /** + * Forces this task to be marked as completed. + * + * @return {@code true} if the task was cancelled, {@code false} if the task has already completed + * or is being completed. + */ + @Override + public boolean cancel(); + + /** + * Executes this task. This will also mark the task as completing. + *

+ * Exceptions thrown from the runnable will be rethrown. + *

+ * + * @return {@code true} if this task was executed, {@code false} if it was already marked as completed. + */ + public boolean execute(); + + /** + * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned + * if this task is completing or has completed. + */ + public Priority getPriority(); + + /** + * Attempts to set this task's priority level to the level specified. + * + * @param priority Specified priority level. + * + * @throws IllegalArgumentException If the priority is invalid + * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue + * this task was scheduled on was shutdown, or if the priority was already at the specified level. + */ + public boolean setPriority(final Priority priority); + + /** + * Attempts to raise the priority to the priority level specified. + * + * @param priority Priority specified + * + * @throws IllegalArgumentException If the priority is invalid + * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the + * specified level or was already at the specified level or higher. + */ + public boolean raisePriority(final Priority priority); + + /** + * Attempts to lower the priority to the priority level specified. + * + * @param priority Priority specified + * + * @throws IllegalArgumentException If the priority is invalid + * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the + * specified level or was already at the specified level or lower. + */ + public boolean lowerPriority(final Priority priority); + + /** + * Returns the suborder id associated with this task. + * @return The suborder id associated with this task. + */ + public long getSubOrder(); + + /** + * Sets the suborder id associated with this task. Ths function has no effect when this task + * is completing or is completed. + * + * @param subOrder Specified new sub order. + * + * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue + * this task was scheduled on was shutdown, or if the current suborder is the same as the new sub order. + */ + public boolean setSubOrder(final long subOrder); + + /** + * Attempts to raise the suborder to the suborder specified. + * + * @param subOrder Specified new sub order. + * + * @return {@code false} if the current task is completing, {@code true} if the suborder was raised to the + * specified suborder or was already at the specified suborder or higher. + */ + public boolean raiseSubOrder(final long subOrder); + + /** + * Attempts to lower the suborder to the suborder specified. + * + * @param subOrder Specified new sub order. + * + * @return {@code false} if the current task is completing, {@code true} if the suborder was lowered to the + * specified suborder or was already at the specified suborder or lower. + */ + public boolean lowerSubOrder(final long subOrder); + + /** + * Sets the priority and suborder id associated with this task. Ths function has no effect when this task + * is completing or is completed. + * + * @param priority Priority specified + * @param subOrder Specified new sub order. + * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue + * this task was scheduled on was shutdown, or if the current priority and suborder are the same as + * the parameters. + */ + public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder); + } +} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..edb8c6611bdc9aced2714b963e00bbb7829603d2 --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java @@ -0,0 +1,454 @@ +package ca.spottedleaf.concurrentutil.executor.queue; + +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import ca.spottedleaf.concurrentutil.util.Priority; +import java.lang.invoke.VarHandle; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +public final class PrioritisedTaskQueue implements PrioritisedExecutor { + + /** + * Required for tie-breaking in the queue + */ + private final AtomicLong taskIdGenerator = new AtomicLong(); + private final AtomicLong scheduledTasks = new AtomicLong(); + private final AtomicLong executedTasks = new AtomicLong(); + private final AtomicLong subOrderGenerator = new AtomicLong(); + private final AtomicBoolean shutdown = new AtomicBoolean(); + private final ConcurrentSkipListMap tasks = new ConcurrentSkipListMap<>(PrioritisedQueuedTask.COMPARATOR); + + @Override + public long getTotalTasksScheduled() { + return this.scheduledTasks.get(); + } + + @Override + public long getTotalTasksExecuted() { + return this.executedTasks.get(); + } + + @Override + public long generateNextSubOrder() { + return this.subOrderGenerator.getAndIncrement(); + } + + @Override + public boolean shutdown() { + return !this.shutdown.getAndSet(true); + } + + @Override + public boolean isShutdown() { + return this.shutdown.get(); + } + + public PrioritisedTask peekFirst() { + final Map.Entry firstEntry = this.tasks.firstEntry(); + return firstEntry == null ? null : firstEntry.getKey().task; + } + + public Priority getHighestPriority() { + final Map.Entry firstEntry = this.tasks.firstEntry(); + return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority); + } + + public boolean hasNoScheduledTasks() { + final long executedTasks = this.executedTasks.get(); + final long scheduledTasks = this.scheduledTasks.get(); + + return executedTasks == scheduledTasks; + } + + public PrioritySubOrderPair getHighestPrioritySubOrder() { + final Map.Entry firstEntry = this.tasks.firstEntry(); + if (firstEntry == null) { + return null; + } + + final PrioritisedQueuedTask.Holder holder = firstEntry.getKey(); + + return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder); + } + + public Runnable pollTask() { + for (;;) { + final Map.Entry firstEntry = this.tasks.pollFirstEntry(); + if (firstEntry != null) { + final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); + task.markRemoved(); + if (!task.task.cancel()) { + continue; + } + return task.task.execute; + } + + return null; + } + } + + @Override + public boolean executeTask() { + for (;;) { + final Map.Entry firstEntry = this.tasks.pollFirstEntry(); + if (firstEntry != null) { + final PrioritisedQueuedTask.Holder task = firstEntry.getKey(); + task.markRemoved(); + if (!task.task.execute()) { + continue; + } + return true; + } + + return false; + } + } + + @Override + public PrioritisedTask createTask(final Runnable task) { + return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority) { + return this.createTask(task, priority, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { + return new PrioritisedQueuedTask(task, priority, subOrder); + } + + @Override + public PrioritisedTask queueTask(final Runnable task) { + return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority) { + return this.queueTask(task, priority, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { + final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder); + + ret.queue(); + + return ret; + } + + private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask { + public static final Comparator COMPARATOR = (final PrioritisedQueuedTask.Holder t1, final PrioritisedQueuedTask.Holder t2) -> { + final int priorityCompare = t1.priority - t2.priority; + if (priorityCompare != 0) { + return priorityCompare; + } + + final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder); + if (subOrderCompare != 0) { + return subOrderCompare; + } + + return Long.compare(t1.id, t2.id); + }; + + private static final class Holder { + private final PrioritisedQueuedTask task; + private final int priority; + private final long subOrder; + private final long id; + + private volatile boolean removed; + private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class); + + private Holder(final PrioritisedQueuedTask task, final int priority, final long subOrder, + final long id) { + this.task = task; + this.priority = priority; + this.subOrder = subOrder; + this.id = id; + } + + /** + * Returns true if marked as removed + */ + public boolean markRemoved() { + return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true); + } + } + + private final long id; + private final Runnable execute; + + private Priority priority; + private long subOrder; + private Holder holder; + + public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) { + if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + + this.execute = execute; + this.priority = priority; + this.subOrder = subOrder; + this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement(); + } + + @Override + public PrioritisedExecutor getExecutor() { + return PrioritisedTaskQueue.this; + } + + @Override + public boolean queue() { + synchronized (this) { + if (this.holder != null || this.priority == Priority.COMPLETING) { + return false; + } + + if (PrioritisedTaskQueue.this.isShutdown()) { + throw new IllegalStateException("Queue is shutdown"); + } + + final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id); + this.holder = holder; + + PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement(); + PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE); + } + + if (PrioritisedTaskQueue.this.isShutdown()) { + this.cancel(); + throw new IllegalStateException("Queue is shutdown"); + } + + + return true; + } + + @Override + public boolean isQueued() { + synchronized (this) { + return this.holder != null && this.priority != Priority.COMPLETING; + } + } + + @Override + public boolean cancel() { + synchronized (this) { + if (this.priority == Priority.COMPLETING) { + return false; + } + + this.priority = Priority.COMPLETING; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); + } + + return true; + } + } + + @Override + public boolean execute() { + final boolean increaseExecuted; + + synchronized (this) { + if (this.priority == Priority.COMPLETING) { + return false; + } + + this.priority = Priority.COMPLETING; + + if (increaseExecuted = (this.holder != null)) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + } + } + + try { + this.execute.run(); + return true; + } finally { + if (increaseExecuted) { + PrioritisedTaskQueue.this.executedTasks.getAndIncrement(); + } + } + } + + @Override + public Priority getPriority() { + synchronized (this) { + return this.priority; + } + } + + @Override + public boolean setPriority(final Priority priority) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.priority == priority) { + return false; + } + + this.priority = priority; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean raisePriority(final Priority priority) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) { + return false; + } + + this.priority = priority; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean lowerPriority(Priority priority) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) { + return false; + } + + this.priority = priority; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public long getSubOrder() { + synchronized (this) { + return this.subOrder; + } + } + + @Override + public boolean setSubOrder(final long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) { + return false; + } + + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean raiseSubOrder(long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) { + return false; + } + + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean lowerSubOrder(final long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) { + return false; + } + + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) { + return false; + } + + this.priority = priority; + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + PrioritisedTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new Holder(this, priority.priority, this.subOrder, this.id); + PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + } + + public static record PrioritySubOrderPair(Priority priority, long subOrder) {} +} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java deleted file mode 100644 index 3ce10053d4ec51855ad7012abb5d97df1c0e557a..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java +++ /dev/null @@ -1,170 +0,0 @@ -package ca.spottedleaf.concurrentutil.executor.standard; - -import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -import java.lang.invoke.VarHandle; - -public class DelayedPrioritisedTask { - - protected volatile int priority; - protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "priority", int.class); - - protected static final int PRIORITY_SET = Integer.MIN_VALUE >>> 0; - - protected final int getPriorityVolatile() { - return (int)PRIORITY_HANDLE.getVolatile((DelayedPrioritisedTask)this); - } - - protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { - return (int)PRIORITY_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (int)expect, (int)update); - } - - protected final int getAndOrPriorityVolatile(final int val) { - return (int)PRIORITY_HANDLE.getAndBitwiseOr((DelayedPrioritisedTask)this, (int)val); - } - - protected final void setPriorityPlain(final int val) { - PRIORITY_HANDLE.set((DelayedPrioritisedTask)this, (int)val); - } - - protected volatile PrioritisedExecutor.PrioritisedTask task; - protected static final VarHandle TASK_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "task", PrioritisedExecutor.PrioritisedTask.class); - - protected PrioritisedExecutor.PrioritisedTask getTaskPlain() { - return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.get((DelayedPrioritisedTask)this); - } - - protected PrioritisedExecutor.PrioritisedTask getTaskVolatile() { - return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.getVolatile((DelayedPrioritisedTask)this); - } - - protected final PrioritisedExecutor.PrioritisedTask compareAndExchangeTaskVolatile(final PrioritisedExecutor.PrioritisedTask expect, final PrioritisedExecutor.PrioritisedTask update) { - return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (PrioritisedExecutor.PrioritisedTask)expect, (PrioritisedExecutor.PrioritisedTask)update); - } - - public DelayedPrioritisedTask(final PrioritisedExecutor.Priority priority) { - this.setPriorityPlain(priority.priority); - } - - // only public for debugging - public int getPriorityInternal() { - return this.getPriorityVolatile(); - } - - public PrioritisedExecutor.PrioritisedTask getTask() { - return this.getTaskVolatile(); - } - - public void setTask(final PrioritisedExecutor.PrioritisedTask task) { - int priority = this.getPriorityVolatile(); - - if (this.compareAndExchangeTaskVolatile(null, task) != null) { - throw new IllegalStateException("setTask() called twice"); - } - - int failures = 0; - for (;;) { - task.setPriority(PrioritisedExecutor.Priority.getPriority(priority)); - - if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SET))) { - return; - } - - ++failures; - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - } - } - - public PrioritisedExecutor.Priority getPriority() { - final int priority = this.getPriorityVolatile(); - if ((priority & PRIORITY_SET) != 0) { - return this.task.getPriority(); - } - - return PrioritisedExecutor.Priority.getPriority(priority); - } - - public void raisePriority(final PrioritisedExecutor.Priority priority) { - if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - int failures = 0; - for (int curr = this.getPriorityVolatile();;) { - if ((curr & PRIORITY_SET) != 0) { - this.getTaskPlain().raisePriority(priority); - return; - } - - if (!priority.isLowerPriority(curr)) { - return; - } - - if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { - return; - } - - // failed, retry - - ++failures; - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - } - } - - public void setPriority(final PrioritisedExecutor.Priority priority) { - if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - int failures = 0; - for (int curr = this.getPriorityVolatile();;) { - if ((curr & PRIORITY_SET) != 0) { - this.getTaskPlain().setPriority(priority); - return; - } - - if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { - return; - } - - // failed, retry - - ++failures; - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - } - } - - public void lowerPriority(final PrioritisedExecutor.Priority priority) { - if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - int failures = 0; - for (int curr = this.getPriorityVolatile();;) { - if ((curr & PRIORITY_SET) != 0) { - this.getTaskPlain().lowerPriority(priority); - return; - } - - if (!priority.isHigherPriority(curr)) { - return; - } - - if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { - return; - } - - // failed, retry - - ++failures; - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - } - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java deleted file mode 100644 index 91beb6f23f257cf265fe3150f760892e605f217a..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java +++ /dev/null @@ -1,276 +0,0 @@ -package ca.spottedleaf.concurrentutil.executor.standard; - -import ca.spottedleaf.concurrentutil.executor.BaseExecutor; - -/** - * Implementation of {@link BaseExecutor} which schedules tasks to be executed by a given priority. - * @see BaseExecutor - */ -public interface PrioritisedExecutor extends BaseExecutor { - - public static enum Priority { - - /** - * Priority value indicating the task has completed or is being completed. - * This priority cannot be used to schedule tasks. - */ - COMPLETING(-1), - - /** - * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. - */ - BLOCKING(), - - /** - * Should only be used for urgent but not time-critical tasks. - */ - HIGHEST(), - - /** - * Two priorities above normal. - */ - HIGHER(), - - /** - * One priority above normal. - */ - HIGH(), - - /** - * Default priority. - */ - NORMAL(), - - /** - * One priority below normal. - */ - LOW(), - - /** - * Two priorities below normal. - */ - LOWER(), - - /** - * Use for tasks that should eventually execute, but are not needed to. - */ - LOWEST(), - - /** - * Use for tasks that can be delayed indefinitely. - */ - IDLE(); - - // returns whether the priority can be scheduled - public static boolean isValidPriority(final Priority priority) { - return priority != null && priority != Priority.COMPLETING; - } - - // returns the higher priority of the two - public static Priority max(final Priority p1, final Priority p2) { - return p1.isHigherOrEqualPriority(p2) ? p1 : p2; - } - - // returns the lower priroity of the two - public static Priority min(final Priority p1, final Priority p2) { - return p1.isLowerOrEqualPriority(p2) ? p1 : p2; - } - - public boolean isHigherOrEqualPriority(final Priority than) { - return this.priority <= than.priority; - } - - public boolean isHigherPriority(final Priority than) { - return this.priority < than.priority; - } - - public boolean isLowerOrEqualPriority(final Priority than) { - return this.priority >= than.priority; - } - - public boolean isLowerPriority(final Priority than) { - return this.priority > than.priority; - } - - public boolean isHigherOrEqualPriority(final int than) { - return this.priority <= than; - } - - public boolean isHigherPriority(final int than) { - return this.priority < than; - } - - public boolean isLowerOrEqualPriority(final int than) { - return this.priority >= than; - } - - public boolean isLowerPriority(final int than) { - return this.priority > than; - } - - public static boolean isHigherOrEqualPriority(final int priority, final int than) { - return priority <= than; - } - - public static boolean isHigherPriority(final int priority, final int than) { - return priority < than; - } - - public static boolean isLowerOrEqualPriority(final int priority, final int than) { - return priority >= than; - } - - public static boolean isLowerPriority(final int priority, final int than) { - return priority > than; - } - - static final Priority[] PRIORITIES = Priority.values(); - - /** includes special priorities */ - public static final int TOTAL_PRIORITIES = PRIORITIES.length; - - public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; - - public static Priority getPriority(final int priority) { - return PRIORITIES[priority + 1]; - } - - private static int priorityCounter; - - private static int nextCounter() { - return priorityCounter++; - } - - public final int priority; - - Priority() { - this(nextCounter()); - } - - Priority(final int priority) { - this.priority = priority; - } - } - - /** - * Executes the next available task. - *

- * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed. - *

- *

- * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed - * when there are no other tasks available with a higher priority. - *

- *

- * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then - * this function will be biased to execute tasks that have higher priorities. - *

- * - * @return {@code true} if a task was executed, {@code false} otherwise - * @throws IllegalStateException If the current thread is not allowed to execute a task - */ - @Override - public boolean executeTask() throws IllegalStateException; - - /** - * Queues or executes a task at {@link Priority#NORMAL} priority. - * @param task The task to run. - * - * @throws IllegalStateException If this queue has shutdown. - * @throws NullPointerException If the task is null - * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task - * associated with the parameter - */ - public default PrioritisedTask queueRunnable(final Runnable task) { - return this.queueRunnable(task, Priority.NORMAL); - } - - /** - * Queues or executes a task. - * - * @param task The task to run. - * @param priority The priority for the task. - * - * @throws IllegalStateException If this queue has shutdown. - * @throws NullPointerException If the task is null - * @throws IllegalArgumentException If the priority is invalid. - * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task - * associated with the parameter - */ - public PrioritisedTask queueRunnable(final Runnable task, final Priority priority); - - /** - * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. - * - * @param task The task to run. - * - * @throws IllegalStateException If this queue has shutdown. - * @throws NullPointerException If the task is null - * @throws IllegalArgumentException If the priority is invalid. - * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks - * @return The prioritised task associated with the parameters - */ - public default PrioritisedTask createTask(final Runnable task) { - return this.createTask(task, Priority.NORMAL); - } - - /** - * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}. - * - * @param task The task to run. - * @param priority The priority for the task. - * - * @throws IllegalStateException If this queue has shutdown. - * @throws NullPointerException If the task is null - * @throws IllegalArgumentException If the priority is invalid. - * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks - * @return The prioritised task associated with the parameters - */ - public PrioritisedTask createTask(final Runnable task, final Priority priority); - - /** - * Extension of {@link ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask} which adds functions - * to retrieve and modify the task's associated priority. - * - * @see ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask - */ - public static interface PrioritisedTask extends BaseTask { - - /** - * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned - * if this task is completing or has completed. - */ - public Priority getPriority(); - - /** - * Attempts to set this task's priority level to the level specified. - * - * @param priority Specified priority level. - * - * @throws IllegalArgumentException If the priority is invalid - * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue - * this task was scheduled on was shutdown, or if the priority was already at the specified level. - */ - public boolean setPriority(final Priority priority); - - /** - * Attempts to raise the priority to the priority level specified. - * - * @param priority Priority specified - * - * @throws IllegalArgumentException If the priority is invalid - * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the specified level or was already at the specified level or higher. - */ - public boolean raisePriority(final Priority priority); - - /** - * Attempts to lower the priority to the priority level specified. - * - * @param priority Priority specified - * - * @throws IllegalArgumentException If the priority is invalid - * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the specified level or was already at the specified level or lower. - */ - public boolean lowerPriority(final Priority priority); - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java deleted file mode 100644 index 2ba36e29d0d8693f2f5e6c6d195ca27f2a5099aa..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java +++ /dev/null @@ -1,632 +0,0 @@ -package ca.spottedleaf.concurrentutil.executor.standard; - -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; - -public final class PrioritisedThreadPool { - - private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); - - private final PrioritisedThread[] threads; - private final TreeSet queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator()); - private final String name; - private final long queueMaxHoldTime; - - private final ReferenceOpenHashSet nonShutdownQueues = new ReferenceOpenHashSet<>(); - private final ReferenceOpenHashSet activeQueues = new ReferenceOpenHashSet<>(); - - private boolean shutdown; - - private long schedulingIdGenerator; - - private static final long DEFAULT_QUEUE_HOLD_TIME = (long)(5.0e6); - - /** - * @param name Specified debug name of this thread pool - * @param threads The number of threads to use - */ - public PrioritisedThreadPool(final String name, final int threads) { - this(name, threads, null); - } - - /** - * @param name Specified debug name of this thread pool - * @param threads The number of threads to use - * @param threadModifier Invoked for each created thread with its incremental id before starting them - */ - public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier) { - this(name, threads, threadModifier, DEFAULT_QUEUE_HOLD_TIME); // 5ms - } - - /** - * @param name Specified debug name of this thread pool - * @param threads The number of threads to use - * @param threadModifier Invoked for each created thread with its incremental id before starting them - * @param queueHoldTime The maximum amount of time to spend executing tasks in a specific queue before attempting - * to switch to another queue, per thread - */ - public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier, - final long queueHoldTime) { // in ns - if (threads <= 0) { - throw new IllegalArgumentException("Thread count must be > 0, not " + threads); - } - if (name == null) { - throw new IllegalArgumentException("Name cannot be null"); - } - this.name = name; - this.queueMaxHoldTime = queueHoldTime; - - this.threads = new PrioritisedThread[threads]; - for (int i = 0; i < threads; ++i) { - this.threads[i] = new PrioritisedThread(this); - - // set default attributes - this.threads[i].setName("Prioritised thread for pool '" + name + "' #" + i); - this.threads[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { - LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); - }); - - // let thread modifier override defaults - if (threadModifier != null) { - threadModifier.accept(this.threads[i], Integer.valueOf(i)); - } - - // now the thread can start - this.threads[i].start(); - } - } - - /** - * Returns an array representing the threads backing this thread pool. - */ - public Thread[] getThreads() { - return Arrays.copyOf(this.threads, this.threads.length, Thread[].class); - } - - /** - * Creates and returns a {@link PrioritisedPoolExecutor} to schedule tasks onto. The returned executor will execute - * tasks on this thread pool only. - * @param name The debug name of the executor. - * @param minParallelism The minimum number of threads to be executing tasks from the returned executor - * before threads may be allocated to other queues in this thread pool. - * @param parallelism The maximum number of threads which may be executing tasks from the returned executor. - * @throws IllegalStateException If this thread pool is shut down - */ - public PrioritisedPoolExecutor createExecutor(final String name, final int minParallelism, final int parallelism) { - synchronized (this.nonShutdownQueues) { - if (this.shutdown) { - throw new IllegalStateException("Queue is shutdown: " + this.toString()); - } - final PrioritisedPoolExecutorImpl ret = new PrioritisedPoolExecutorImpl( - this, name, - Math.min(Math.max(1, parallelism), this.threads.length), - Math.min(Math.max(0, minParallelism), this.threads.length) - ); - - this.nonShutdownQueues.add(ret); - - synchronized (this.activeQueues) { - this.activeQueues.add(ret); - } - - return ret; - } - } - - /** - * Prevents creation of new queues, shutdowns all non-shutdown queues if specified - */ - public void halt(final boolean shutdownQueues) { - synchronized (this.nonShutdownQueues) { - this.shutdown = true; - } - if (shutdownQueues) { - final ArrayList queuesToShutdown; - synchronized (this.nonShutdownQueues) { - this.shutdown = true; - queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); - } - - for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { - queue.shutdown(); - } - } - - - for (final PrioritisedThread thread : this.threads) { - // can't kill queue, queue is null - thread.halt(false); - } - } - - /** - * Waits until all threads in this pool have shutdown, or until the specified time has passed. - * @param msToWait Maximum time to wait. - * @return {@code false} if the maximum time passed, {@code true} otherwise. - */ - public boolean join(final long msToWait) { - try { - return this.join(msToWait, false); - } catch (final InterruptedException ex) { - throw new IllegalStateException(ex); - } - } - - /** - * Waits until all threads in this pool have shutdown, or until the specified time has passed. - * @param msToWait Maximum time to wait. - * @return {@code false} if the maximum time passed, {@code true} otherwise. - * @throws InterruptedException If this thread is interrupted. - */ - public boolean joinInterruptable(final long msToWait) throws InterruptedException { - return this.join(msToWait, true); - } - - protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException { - final long nsToWait = msToWait * (1000 * 1000); - final long start = System.nanoTime(); - final long deadline = start + nsToWait; - boolean interrupted = false; - try { - for (final PrioritisedThread thread : this.threads) { - for (;;) { - if (!thread.isAlive()) { - break; - } - final long current = System.nanoTime(); - if (current >= deadline) { - return false; - } - - try { - thread.join(Math.max(1L, (deadline - current) / (1000 * 1000))); - } catch (final InterruptedException ex) { - if (interruptable) { - throw ex; - } - interrupted = true; - } - } - } - - return true; - } finally { - if (interrupted) { - Thread.currentThread().interrupt(); - } - } - } - - /** - * Shuts down this thread pool, optionally waiting for all tasks to be executed. - * This function will invoke {@link PrioritisedPoolExecutor#shutdown()} on all created executors on this - * thread pool. - * @param wait Whether to wait for tasks to be executed - */ - public void shutdown(final boolean wait) { - final ArrayList queuesToShutdown; - synchronized (this.nonShutdownQueues) { - this.shutdown = true; - queuesToShutdown = new ArrayList<>(this.nonShutdownQueues); - } - - for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) { - queue.shutdown(); - } - - for (final PrioritisedThread thread : this.threads) { - // none of these can be true or else NPE - thread.close(false, false); - } - - if (wait) { - final ArrayList queues; - synchronized (this.activeQueues) { - queues = new ArrayList<>(this.activeQueues); - } - for (final PrioritisedPoolExecutorImpl queue : queues) { - queue.waitUntilAllExecuted(); - } - } - } - - protected static final class PrioritisedThread extends PrioritisedQueueExecutorThread { - - protected final PrioritisedThreadPool pool; - protected final AtomicBoolean alertedHighPriority = new AtomicBoolean(); - - public PrioritisedThread(final PrioritisedThreadPool pool) { - super(null); - this.pool = pool; - } - - public boolean alertHighPriorityExecutor() { - if (!this.notifyTasks()) { - if (!this.alertedHighPriority.get()) { - this.alertedHighPriority.set(true); - } - return false; - } - - return true; - } - - private boolean isAlertedHighPriority() { - return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false); - } - - @Override - protected boolean pollTasks() { - final PrioritisedThreadPool pool = this.pool; - final TreeSet queues = this.pool.queues; - - boolean ret = false; - for (;;) { - if (this.halted) { - break; - } - // try to find a queue - // note that if and ONLY IF the queues set is empty, this means there are no tasks for us to execute. - // so we can only break when it's empty - final PrioritisedPoolExecutorImpl queue; - // select queue - synchronized (queues) { - queue = queues.pollFirst(); - if (queue == null) { - // no tasks to execute - break; - } - - queue.schedulingId = ++pool.schedulingIdGenerator; - // we own this queue now, so increment the executor count - // do we also need to push this queue up for grabs for another executor? - if (++queue.concurrentExecutors < queue.maximumExecutors) { - // re-add to queues - // it's very important this is done in the same synchronised block for polling, as this prevents - // us from possibly later adding a queue that should not exist in the set - queues.add(queue); - queue.isQueued = true; - } else { - queue.isQueued = false; - } - // note: we cannot drain entries from the queue while holding this lock, as it will cause deadlock - // the queue addition holds the per-queue lock first then acquires the lock we have now, but if we - // try to poll now we don't hold the per queue lock but we do hold the global lock... - } - - // parse tasks as long as we are allowed - final long start = System.nanoTime(); - final long deadline = start + pool.queueMaxHoldTime; - do { - try { - if (this.halted) { - break; - } - if (!queue.executeTask()) { - // no more tasks, try next queue - break; - } - ret = true; - } catch (final ThreadDeath death) { - throw death; // goodbye world... - } catch (final Throwable throwable) { - LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + queue.toString() + "'", throwable); - } - } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); - - synchronized (queues) { - // decrement executors, we are no longer executing - if (queue.isQueued) { - queues.remove(queue); - queue.isQueued = false; - } - if (--queue.concurrentExecutors == 0 && queue.scheduledPriority == null) { - // reset scheduling id once the queue is empty again - // this will ensure empty queues are not prioritised suddenly over active queues once tasks are - // queued - queue.schedulingId = 0L; - } - - // ensure the executor is queued for execution again - if (!queue.isHalted && queue.scheduledPriority != null) { // make sure it actually has tasks - queues.add(queue); - queue.isQueued = true; - } - } - } - - return ret; - } - } - - public interface PrioritisedPoolExecutor extends PrioritisedExecutor { - - /** - * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed - */ - public void halt(); - - /** - * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether - * this queue is not halted and not shutdown. - */ - public boolean isActive(); - } - - protected static final class PrioritisedPoolExecutorImpl extends PrioritisedThreadedTaskQueue implements PrioritisedPoolExecutor { - - protected final PrioritisedThreadPool pool; - protected final long[] priorityCounts = new long[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; - protected long schedulingId; - protected int concurrentExecutors; - protected Priority scheduledPriority; - - protected final String name; - protected final int maximumExecutors; - protected final int minimumExecutors; - protected boolean isQueued; - - public PrioritisedPoolExecutorImpl(final PrioritisedThreadPool pool, final String name, final int maximumExecutors, final int minimumExecutors) { - this.pool = pool; - this.name = name; - this.maximumExecutors = maximumExecutors; - this.minimumExecutors = minimumExecutors; - } - - public static Comparator comparator() { - return (final PrioritisedPoolExecutorImpl p1, final PrioritisedPoolExecutorImpl p2) -> { - if (p1 == p2) { - return 0; - } - - final int belowMin1 = p1.minimumExecutors - p1.concurrentExecutors; - final int belowMin2 = p2.minimumExecutors - p2.concurrentExecutors; - - // test minimum executors - if (belowMin1 > 0 || belowMin2 > 0) { - // want the largest belowMin to be first - final int minCompare = Integer.compare(belowMin2, belowMin1); - - if (minCompare != 0) { - return minCompare; - } - } - - // prefer higher priority - final int priorityCompare = p1.scheduledPriority.ordinal() - p2.scheduledPriority.ordinal(); - if (priorityCompare != 0) { - return priorityCompare; - } - - // try to spread out the executors so that each can have threads executing - final int executorCompare = p1.concurrentExecutors - p2.concurrentExecutors; - if (executorCompare != 0) { - return executorCompare; - } - - // if all else fails here we just choose whichever executor was queued first - return Long.compare(p1.schedulingId, p2.schedulingId); - }; - } - - private boolean isHalted; - - @Override - public void halt() { - final PrioritisedThreadPool pool = this.pool; - final TreeSet queues = pool.queues; - synchronized (queues) { - if (this.isHalted) { - return; - } - this.isHalted = true; - if (this.isQueued) { - queues.remove(this); - this.isQueued = false; - } - } - synchronized (pool.nonShutdownQueues) { - pool.nonShutdownQueues.remove(this); - } - synchronized (pool.activeQueues) { - pool.activeQueues.remove(this); - } - } - - @Override - public boolean isActive() { - final PrioritisedThreadPool pool = this.pool; - final TreeSet queues = pool.queues; - - synchronized (queues) { - if (this.concurrentExecutors != 0) { - return true; - } - synchronized (pool.activeQueues) { - if (pool.activeQueues.contains(this)) { - return true; - } - } - } - - return false; - } - - private long totalQueuedTasks = 0L; - - @Override - protected void priorityChange(final PrioritisedThreadedTaskQueue.PrioritisedTask task, final Priority from, final Priority to) { - // Note: The superclass' queue lock is ALWAYS held when inside this method. So we do NOT need to do any additional synchronisation - // for accessing this queue's state. - final long[] priorityCounts = this.priorityCounts; - final boolean shutdown = this.isShutdown(); - - if (from == null && to == Priority.COMPLETING) { - throw new IllegalStateException("Cannot complete task without queueing it first"); - } - - // we should only notify for queueing of tasks, not changing priorities - final boolean shouldNotifyTasks = from == null; - - final Priority scheduledPriority = this.scheduledPriority; - if (from != null) { - --priorityCounts[from.priority]; - } - if (to != Priority.COMPLETING) { - ++priorityCounts[to.priority]; - } - final long totalQueuedTasks; - if (to == Priority.COMPLETING) { - totalQueuedTasks = --this.totalQueuedTasks; - } else if (from == null) { - totalQueuedTasks = ++this.totalQueuedTasks; - } else { - totalQueuedTasks = this.totalQueuedTasks; - } - - // find new highest priority - int highest = Math.min(to == Priority.COMPLETING ? Priority.IDLE.priority : to.priority, scheduledPriority == null ? Priority.IDLE.priority : scheduledPriority.priority); - int lowestPriority = priorityCounts.length; // exclusive - for (;highest < lowestPriority; ++highest) { - final long count = priorityCounts[highest]; - if (count < 0) { - throw new IllegalStateException("Priority " + highest + " has " + count + " scheduled tasks"); - } - - if (count != 0) { - break; - } - } - - final Priority newPriority; - if (highest == lowestPriority) { - // no tasks left - newPriority = null; - } else if (shutdown) { - // whichever is lower, the actual greatest priority or simply HIGHEST - // this is so shutdown automatically gets priority - newPriority = Priority.getPriority(Math.min(highest, Priority.HIGHEST.priority)); - } else { - newPriority = Priority.getPriority(highest); - } - - final int executorsWanted; - boolean shouldNotifyHighPriority = false; - - final PrioritisedThreadPool pool = this.pool; - final TreeSet queues = pool.queues; - - synchronized (queues) { - if (!this.isQueued) { - // see if we need to be queued - if (newPriority != null) { - if (this.schedulingId == 0L) { - this.schedulingId = ++pool.schedulingIdGenerator; - } - this.scheduledPriority = newPriority; // must be updated before queue add - if (!this.isHalted && this.concurrentExecutors < this.maximumExecutors) { - shouldNotifyHighPriority = newPriority.isHigherOrEqualPriority(Priority.HIGH); - queues.add(this); - this.isQueued = true; - } - } else { - // do not queue - this.scheduledPriority = null; - } - } else { - // see if we need to NOT be queued - if (newPriority == null) { - queues.remove(this); - this.scheduledPriority = null; - this.isQueued = false; - } else if (scheduledPriority != newPriority) { - // if our priority changed, we need to update it - which means removing and re-adding into the queue - queues.remove(this); - // only now can we update scheduledPriority, since we are no longer in queue - this.scheduledPriority = newPriority; - queues.add(this); - shouldNotifyHighPriority = (scheduledPriority == null || scheduledPriority.isLowerPriority(Priority.HIGH)) && newPriority.isHigherOrEqualPriority(Priority.HIGH); - } - } - - if (this.isQueued) { - executorsWanted = Math.min(this.maximumExecutors - this.concurrentExecutors, (int)totalQueuedTasks); - } else { - executorsWanted = 0; - } - } - - if (newPriority == null && shutdown) { - synchronized (pool.activeQueues) { - pool.activeQueues.remove(this); - } - } - - // Wake up the number of executors we want - if (executorsWanted > 0 || (shouldNotifyTasks | shouldNotifyHighPriority)) { - int notified = 0; - for (final PrioritisedThread thread : pool.threads) { - if ((shouldNotifyHighPriority ? thread.alertHighPriorityExecutor() : thread.notifyTasks()) - && (++notified >= executorsWanted)) { - break; - } - } - } - } - - @Override - public boolean shutdown() { - final boolean ret = super.shutdown(); - if (!ret) { - return ret; - } - - final PrioritisedThreadPool pool = this.pool; - - // remove from active queues - synchronized (pool.nonShutdownQueues) { - pool.nonShutdownQueues.remove(this); - } - - final TreeSet queues = pool.queues; - - // try and shift around our priority - synchronized (queues) { - if (this.scheduledPriority == null) { - // no tasks are queued, ensure we aren't in activeQueues - synchronized (pool.activeQueues) { - pool.activeQueues.remove(this); - } - - return ret; - } - - // try to set scheduled priority to HIGHEST so it drains faster - - if (this.scheduledPriority.isHigherOrEqualPriority(Priority.HIGHEST)) { - // already at target priority (highest or above) - return ret; - } - - // shift priority to HIGHEST - - if (this.isQueued) { - queues.remove(this); - this.scheduledPriority = Priority.HIGHEST; - queues.add(this); - } else { - this.scheduledPriority = Priority.HIGHEST; - } - } - - return ret; - } - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java deleted file mode 100644 index 3e8401b1b1f833c4f01bc87059a2f48d761d989f..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java +++ /dev/null @@ -1,378 +0,0 @@ -package ca.spottedleaf.concurrentutil.executor.standard; - -import java.util.ArrayDeque; -import java.util.concurrent.atomic.AtomicLong; - -public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor { - - protected final ArrayDeque[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; { - for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) { - this.queues[i] = new ArrayDeque<>(); - } - } - - // Use AtomicLong to separate from the queue field, we don't want false sharing here. - protected final AtomicLong totalScheduledTasks = new AtomicLong(); - protected final AtomicLong totalCompletedTasks = new AtomicLong(); - - // this is here to prevent failures to queue stalling flush() calls (as the schedule calls would increment totalScheduledTasks without this check) - protected volatile boolean hasShutdown; - - protected long taskIdGenerator = 0; - - @Override - public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final Priority priority) throws IllegalStateException, IllegalArgumentException { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Priority " + priority + " is invalid"); - } - if (task == null) { - throw new NullPointerException("Task cannot be null"); - } - - if (this.hasShutdown) { - // prevent us from stalling flush() calls by incrementing scheduled tasks when we really didn't schedule something - throw new IllegalStateException("Queue has shutdown"); - } - - final PrioritisedTask ret; - - synchronized (this.queues) { - if (this.hasShutdown) { - throw new IllegalStateException("Queue has shutdown"); - } - this.getAndAddTotalScheduledTasksVolatile(1L); - - ret = new PrioritisedTask(this.taskIdGenerator++, task, priority, this); - - this.queues[ret.priority.priority].add(ret); - - // call priority change callback (note: only after we successfully queue!) - this.priorityChange(ret, null, priority); - } - - return ret; - } - - @Override - public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final Priority priority) { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Priority " + priority + " is invalid"); - } - if (task == null) { - throw new NullPointerException("Task cannot be null"); - } - - return new PrioritisedTask(task, priority, this); - } - - @Override - public long getTotalTasksScheduled() { - return this.totalScheduledTasks.get(); - } - - @Override - public long getTotalTasksExecuted() { - return this.totalCompletedTasks.get(); - } - - // callback method for subclasses to override - // from is null when a task is immediately created - protected void priorityChange(final PrioritisedTask task, final Priority from, final Priority to) {} - - /** - * Polls the highest priority task currently available. {@code null} if none. This will mark the - * returned task as completed. - */ - protected PrioritisedTask poll() { - return this.poll(Priority.IDLE); - } - - protected PrioritisedTask poll(final Priority minPriority) { - final ArrayDeque[] queues = this.queues; - synchronized (queues) { - final int max = minPriority.priority; - for (int i = 0; i <= max; ++i) { - final ArrayDeque queue = queues[i]; - PrioritisedTask task; - while ((task = queue.pollFirst()) != null) { - if (task.trySetCompleting(i)) { - return task; - } - } - } - } - - return null; - } - - /** - * Polls and executes the highest priority task currently available. Exceptions thrown during task execution will - * be rethrown. - * @return {@code true} if a task was executed, {@code false} otherwise. - */ - @Override - public boolean executeTask() { - final PrioritisedTask task = this.poll(); - - if (task != null) { - task.executeInternal(); - return true; - } - - return false; - } - - @Override - public boolean shutdown() { - synchronized (this.queues) { - if (this.hasShutdown) { - return false; - } - this.hasShutdown = true; - } - return true; - } - - @Override - public boolean isShutdown() { - return this.hasShutdown; - } - - /* totalScheduledTasks */ - - protected final long getTotalScheduledTasksVolatile() { - return this.totalScheduledTasks.get(); - } - - protected final long getAndAddTotalScheduledTasksVolatile(final long value) { - return this.totalScheduledTasks.getAndAdd(value); - } - - /* totalCompletedTasks */ - - protected final long getTotalCompletedTasksVolatile() { - return this.totalCompletedTasks.get(); - } - - protected final long getAndAddTotalCompletedTasksVolatile(final long value) { - return this.totalCompletedTasks.getAndAdd(value); - } - - protected static final class PrioritisedTask implements PrioritisedExecutor.PrioritisedTask { - protected final PrioritisedThreadedTaskQueue queue; - protected long id; - protected static final long NOT_SCHEDULED_ID = -1L; - - protected Runnable runnable; - protected volatile Priority priority; - - protected PrioritisedTask(final long id, final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - this.priority = priority; - this.runnable = runnable; - this.queue = queue; - this.id = id; - } - - protected PrioritisedTask(final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - this.priority = priority; - this.runnable = runnable; - this.queue = queue; - this.id = NOT_SCHEDULED_ID; - } - - @Override - public boolean queue() { - if (this.queue.hasShutdown) { - throw new IllegalStateException("Queue has shutdown"); - } - - synchronized (this.queue.queues) { - if (this.queue.hasShutdown) { - throw new IllegalStateException("Queue has shutdown"); - } - - final Priority priority = this.priority; - if (priority == Priority.COMPLETING) { - return false; - } - - if (this.id != NOT_SCHEDULED_ID) { - return false; - } - - this.queue.getAndAddTotalScheduledTasksVolatile(1L); - this.id = this.queue.taskIdGenerator++; - this.queue.queues[priority.priority].add(this); - - this.queue.priorityChange(this, null, priority); - - return true; - } - } - - protected boolean trySetCompleting(final int minPriority) { - final Priority oldPriority = this.priority; - if (oldPriority != Priority.COMPLETING && oldPriority.isHigherOrEqualPriority(minPriority)) { - this.priority = Priority.COMPLETING; - if (this.id != NOT_SCHEDULED_ID) { - this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); - } - return true; - } - - return false; - } - - @Override - public Priority getPriority() { - return this.priority; - } - - @Override - public boolean setPriority(final Priority priority) { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - synchronized (this.queue.queues) { - final Priority curr = this.priority; - - if (curr == Priority.COMPLETING) { - return false; - } - - if (curr == priority) { - return true; - } - - this.priority = priority; - if (this.id != NOT_SCHEDULED_ID) { - this.queue.queues[priority.priority].add(this); - - // call priority change callback - this.queue.priorityChange(this, curr, priority); - } - } - - return true; - } - - @Override - public boolean raisePriority(final Priority priority) { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - synchronized (this.queue.queues) { - final Priority curr = this.priority; - - if (curr == Priority.COMPLETING) { - return false; - } - - if (curr.isHigherOrEqualPriority(priority)) { - return true; - } - - this.priority = priority; - if (this.id != NOT_SCHEDULED_ID) { - this.queue.queues[priority.priority].add(this); - - // call priority change callback - this.queue.priorityChange(this, curr, priority); - } - } - - return true; - } - - @Override - public boolean lowerPriority(final Priority priority) { - if (!Priority.isValidPriority(priority)) { - throw new IllegalArgumentException("Invalid priority " + priority); - } - - synchronized (this.queue.queues) { - final Priority curr = this.priority; - - if (curr == Priority.COMPLETING) { - return false; - } - - if (curr.isLowerOrEqualPriority(priority)) { - return true; - } - - this.priority = priority; - if (this.id != NOT_SCHEDULED_ID) { - this.queue.queues[priority.priority].add(this); - - // call priority change callback - this.queue.priorityChange(this, curr, priority); - } - } - - return true; - } - - @Override - public boolean cancel() { - final long id; - synchronized (this.queue.queues) { - final Priority oldPriority = this.priority; - if (oldPriority == Priority.COMPLETING) { - return false; - } - - this.priority = Priority.COMPLETING; - // call priority change callback - if ((id = this.id) != NOT_SCHEDULED_ID) { - this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); - } - } - this.runnable = null; - if (id != NOT_SCHEDULED_ID) { - this.queue.getAndAddTotalCompletedTasksVolatile(1L); - } - return true; - } - - protected void executeInternal() { - try { - final Runnable execute = this.runnable; - this.runnable = null; - execute.run(); - } finally { - if (this.id != NOT_SCHEDULED_ID) { - this.queue.getAndAddTotalCompletedTasksVolatile(1L); - } - } - } - - @Override - public boolean execute() { - synchronized (this.queue.queues) { - final Priority oldPriority = this.priority; - if (oldPriority == Priority.COMPLETING) { - return false; - } - - this.priority = Priority.COMPLETING; - // call priority change callback - if (this.id != NOT_SCHEDULED_ID) { - this.queue.priorityChange(this, oldPriority, Priority.COMPLETING); - } - } - - this.executeInternal(); - return true; - } - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java similarity index 60% rename from src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java rename to src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java index d1683ba6350e530373944f98192c0f2baf241e70..f5367a13aaa02f0f929813c00a67e6ac7c8652cb 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java @@ -1,6 +1,8 @@ -package ca.spottedleaf.concurrentutil.executor.standard; +package ca.spottedleaf.concurrentutil.executor.thread; +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import ca.spottedleaf.concurrentutil.util.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.VarHandle; @@ -11,8 +13,7 @@ import java.util.concurrent.locks.LockSupport; *

* Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour - * of task scheduling (thread wakes up after tasks are scheduled), use the methods provided on {@link PrioritisedExecutor} - * methods. + * of task scheduling, use the methods provided on this class to schedule tasks. *

*/ public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor { @@ -30,7 +31,7 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise protected final long spinWaitTime; - static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms + protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) { this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms @@ -42,7 +43,16 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise } @Override - public void run() { + public final void run() { + try { + this.begin(); + this.doRun(); + } finally { + this.die(); + } + } + + public final void doRun() { final long spinWaitTime = this.spinWaitTime; main_loop: @@ -80,7 +90,7 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise this.setThreadParkedVolatile(true); // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true - // (i.e it will not notify us) + // (i.e. it will not notify us) if (this.pollTasks()) { this.setThreadParkedVolatile(false); continue; @@ -99,6 +109,10 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise } } + protected void begin() {} + + protected void die() {} + /** * Attempts to poll as many tasks as possible, returning when finished. * @return Whether any tasks were executed. @@ -115,8 +129,6 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise break; } ret = true; - } catch (final ThreadDeath death) { - throw death; // goodbye world... } catch (final Throwable throwable) { LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable); } @@ -146,95 +158,86 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise } @Override - public PrioritisedTask createTask(final Runnable task, final Priority priority) { - final PrioritisedTask queueTask = this.queue.createTask(task, priority); - - // need to override queue() to notify us of tasks - return new PrioritisedTask() { - @Override - public Priority getPriority() { - return queueTask.getPriority(); - } - - @Override - public boolean setPriority(final Priority priority) { - return queueTask.setPriority(priority); - } + public long getTotalTasksExecuted() { + return this.queue.getTotalTasksExecuted(); + } - @Override - public boolean raisePriority(final Priority priority) { - return queueTask.raisePriority(priority); - } + @Override + public long getTotalTasksScheduled() { + return this.queue.getTotalTasksScheduled(); + } - @Override - public boolean lowerPriority(final Priority priority) { - return queueTask.lowerPriority(priority); - } + @Override + public long generateNextSubOrder() { + return this.queue.generateNextSubOrder(); + } - @Override - public boolean queue() { - final boolean ret = queueTask.queue(); - if (ret) { - PrioritisedQueueExecutorThread.this.notifyTasks(); - } - return ret; - } + @Override + public boolean shutdown() { + throw new UnsupportedOperationException(); + } - @Override - public boolean cancel() { - return queueTask.cancel(); - } + @Override + public boolean isShutdown() { + return false; + } - @Override - public boolean execute() { - return queueTask.execute(); - } - }; + /** + * {@inheritDoc} + * @throws IllegalStateException Always + */ + @Override + public boolean executeTask() throws IllegalStateException { + throw new IllegalStateException(); } @Override - public PrioritisedTask queueRunnable(final Runnable task, final Priority priority) { - final PrioritisedTask ret = this.queue.queueRunnable(task, priority); + public PrioritisedTask queueTask(final Runnable task) { + final PrioritisedTask ret = this.createTask(task); - this.notifyTasks(); + ret.queue(); return ret; } @Override - public boolean haveAllTasksExecuted() { - return this.queue.haveAllTasksExecuted(); + public PrioritisedTask queueTask(final Runnable task, final Priority priority) { + final PrioritisedTask ret = this.createTask(task, priority); + + ret.queue(); + + return ret; } @Override - public long getTotalTasksExecuted() { - return this.queue.getTotalTasksExecuted(); + public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { + final PrioritisedTask ret = this.createTask(task, priority, subOrder); + + ret.queue(); + + return ret; } + @Override - public long getTotalTasksScheduled() { - return this.queue.getTotalTasksScheduled(); + public PrioritisedTask createTask(Runnable task) { + final PrioritisedTask queueTask = this.queue.createTask(task); + + return new WrappedTask(queueTask); } - /** - * {@inheritDoc} - * @throws IllegalStateException If the current thread is {@code this} thread, or the underlying queue throws this exception. - */ @Override - public void waitUntilAllExecuted() throws IllegalStateException { - if (Thread.currentThread() == this) { - throw new IllegalStateException("Cannot block on our own queue"); - } - this.queue.waitUntilAllExecuted(); + public PrioritisedTask createTask(final Runnable task, final Priority priority) { + final PrioritisedTask queueTask = this.queue.createTask(task, priority); + + return new WrappedTask(queueTask); } - /** - * {@inheritDoc} - * @throws IllegalStateException Always - */ @Override - public boolean executeTask() throws IllegalStateException { - throw new IllegalStateException(); + public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { + final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder); + + return new WrappedTask(queueTask); } /** @@ -242,7 +245,7 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise *

* This function is MT-Safe. *

- * @param wait If this call is to wait until the queue is empty and there are no tasks executing in the queue. + * @param wait If this call is to wait until this thread shuts down. * @param killQueue Whether to shutdown this thread's queue * @return whether this thread shut down the queue * @see #halt(boolean) @@ -256,7 +259,20 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise LockSupport.unpark(this); if (wait) { - this.waitUntilAllExecuted(); + boolean interrupted = false; + for (;;) { + if (this.isAlive()) { + if (interrupted) { + Thread.currentThread().interrupt(); + } + break; + } + try { + this.join(); + } catch (final InterruptedException ex) { + interrupted = true; + } + } } return ret; @@ -298,4 +314,89 @@ public class PrioritisedQueueExecutorThread extends Thread implements Prioritise protected final void setThreadParkedVolatile(final boolean value) { THREAD_PARKED_HANDLE.setVolatile(this, value); } + + /** + * Required so that queue() can notify (unpark) this thread + */ + private final class WrappedTask implements PrioritisedTask { + private final PrioritisedTask queueTask; + + public WrappedTask(final PrioritisedTask queueTask) { + this.queueTask = queueTask; + } + + @Override + public PrioritisedExecutor getExecutor() { + return PrioritisedQueueExecutorThread.this; + } + + @Override + public boolean queue() { + final boolean ret = this.queueTask.queue(); + if (ret) { + PrioritisedQueueExecutorThread.this.notifyTasks(); + } + return ret; + } + + @Override + public boolean isQueued() { + return this.queueTask.isQueued(); + } + + @Override + public boolean cancel() { + return this.queueTask.cancel(); + } + + @Override + public boolean execute() { + return this.queueTask.execute(); + } + + @Override + public Priority getPriority() { + return this.queueTask.getPriority(); + } + + @Override + public boolean setPriority(final Priority priority) { + return this.queueTask.setPriority(priority); + } + + @Override + public boolean raisePriority(final Priority priority) { + return this.queueTask.raisePriority(priority); + } + + @Override + public boolean lowerPriority(final Priority priority) { + return this.queueTask.lowerPriority(priority); + } + + @Override + public long getSubOrder() { + return this.queueTask.getSubOrder(); + } + + @Override + public boolean setSubOrder(final long subOrder) { + return this.queueTask.setSubOrder(subOrder); + } + + @Override + public boolean raiseSubOrder(final long subOrder) { + return this.queueTask.raiseSubOrder(subOrder); + } + + @Override + public boolean lowerSubOrder(final long subOrder) { + return this.queueTask.lowerSubOrder(subOrder); + } + + @Override + public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { + return this.queueTask.setPriorityAndSubOrder(priority, subOrder); + } + } } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java new file mode 100644 index 0000000000000000000000000000000000000000..cb9df914a9a6d0d3f58fa58d8c93f4f583416cd1 --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java @@ -0,0 +1,741 @@ +package ca.spottedleaf.concurrentutil.executor.thread; + +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue; +import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.concurrentutil.util.TimeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +public final class PrioritisedThreadPool { + + private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class); + + private final Consumer threadModifier; + private final COWArrayList executors = new COWArrayList<>(ExecutorGroup.class); + private final COWArrayList threads = new COWArrayList<>(PrioritisedThread.class); + private final COWArrayList aliveThreads = new COWArrayList<>(PrioritisedThread.class); + + private static final Priority HIGH_PRIORITY_NOTIFY_THRESHOLD = Priority.HIGH; + private static final Priority QUEUE_SHUTDOWN_PRIORITY = Priority.HIGH; + + private boolean shutdown; + + public PrioritisedThreadPool(final Consumer threadModifier) { + this.threadModifier = threadModifier; + + if (threadModifier == null) { + throw new NullPointerException("Thread factory may not be null"); + } + } + + public Thread[] getAliveThreads() { + final PrioritisedThread[] threads = this.aliveThreads.getArray(); + + return Arrays.copyOf(threads, threads.length, Thread[].class); + } + + public Thread[] getCoreThreads() { + final PrioritisedThread[] threads = this.threads.getArray(); + + return Arrays.copyOf(threads, threads.length, Thread[].class); + } + + /** + * Prevents creation of new queues, shutdowns all non-shutdown queues if specified + */ + public void halt(final boolean shutdownQueues) { + synchronized (this) { + this.shutdown = true; + } + + if (shutdownQueues) { + for (final ExecutorGroup group : this.executors.getArray()) { + for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { + executor.shutdown(); + } + } + } + + for (final PrioritisedThread thread : this.threads.getArray()) { + thread.halt(false); + } + } + + /** + * Waits until all threads in this pool have shutdown, or until the specified time has passed. + * @param msToWait Maximum time to wait. + * @return {@code false} if the maximum time passed, {@code true} otherwise. + */ + public boolean join(final long msToWait) { + try { + return this.join(msToWait, false); + } catch (final InterruptedException ex) { + throw new IllegalStateException(ex); + } + } + + /** + * Waits until all threads in this pool have shutdown, or until the specified time has passed. + * @param msToWait Maximum time to wait. + * @return {@code false} if the maximum time passed, {@code true} otherwise. + * @throws InterruptedException If this thread is interrupted. + */ + public boolean joinInterruptable(final long msToWait) throws InterruptedException { + return this.join(msToWait, true); + } + + protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException { + final long nsToWait = msToWait * (1000 * 1000); + final long start = System.nanoTime(); + final long deadline = start + nsToWait; + boolean interrupted = false; + try { + for (final PrioritisedThread thread : this.aliveThreads.getArray()) { + for (;;) { + if (!thread.isAlive()) { + break; + } + final long current = System.nanoTime(); + if (current >= deadline && msToWait > 0L) { + return false; + } + + try { + thread.join(msToWait <= 0L ? 0L : Math.max(1L, (deadline - current) / (1000 * 1000))); + } catch (final InterruptedException ex) { + if (interruptable) { + throw ex; + } + interrupted = true; + } + } + } + + return true; + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Shuts down this thread pool, optionally waiting for all tasks to be executed. + * This function will invoke {@link PrioritisedExecutor#shutdown()} on all created executors on this + * thread pool. + * @param wait Whether to wait for tasks to be executed + */ + public void shutdown(final boolean wait) { + synchronized (this) { + this.shutdown = true; + } + + for (final ExecutorGroup group : this.executors.getArray()) { + for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { + executor.shutdown(); + } + } + + + for (final PrioritisedThread thread : this.threads.getArray()) { + // none of these can be true or else NPE + thread.close(false, false); + } + + if (wait) { + this.join(0L); + } + } + + private void die(final PrioritisedThread thread) { + this.aliveThreads.remove(thread); + } + + public void adjustThreadCount(final int threads) { + synchronized (this) { + if (this.shutdown) { + return; + } + + final PrioritisedThread[] currentThreads = this.threads.getArray(); + if (threads == currentThreads.length) { + // no adjustment needed + return; + } + + if (threads < currentThreads.length) { + // we need to trim threads + for (int i = 0, difference = currentThreads.length - threads; i < difference; ++i) { + final PrioritisedThread remove = currentThreads[currentThreads.length - i - 1]; + + remove.halt(false); + this.threads.remove(remove); + } + } else { + // we need to add threads + for (int i = 0, difference = threads - currentThreads.length; i < difference; ++i) { + final PrioritisedThread thread = new PrioritisedThread(); + + this.threadModifier.accept(thread); + this.aliveThreads.add(thread); + this.threads.add(thread); + + thread.start(); + } + } + } + } + + private static int compareInsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, + final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { + final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); + if (priorityCompare != 0) { + return priorityCompare; + } + + final int parallelismCompare = src.currentParallelism - dst.currentParallelism; + if (parallelismCompare != 0) { + return parallelismCompare; + } + + return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); + } + + private static int compareOutsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority, + final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) { + if (src.getGroup().division == dst.getGroup().division) { + // can only compare priorities inside the same division + final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal(); + if (priorityCompare != 0) { + return priorityCompare; + } + } + + final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism; + if (parallelismCompare != 0) { + return parallelismCompare; + } + + return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved); + } + + private ExecutorGroup.ThreadPoolExecutor obtainQueue() { + final long time = System.nanoTime(); + synchronized (this) { + ExecutorGroup.ThreadPoolExecutor ret = null; + Priority retPriority = null; + + for (final ExecutorGroup executorGroup : this.executors.getArray()) { + ExecutorGroup.ThreadPoolExecutor highest = null; + Priority highestPriority = null; + for (final ExecutorGroup.ThreadPoolExecutor executor : executorGroup.executors.getArray()) { + final int maxParallelism = executor.maxParallelism; + if (maxParallelism > 0 && executor.currentParallelism >= maxParallelism) { + continue; + } + + final Priority priority = executor.getTargetPriority(); + + if (priority == null) { + continue; + } + + if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) { + highest = executor; + highestPriority = priority; + } + } + + if (highest == null) { + continue; + } + + if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) { + ret = highest; + retPriority = highestPriority; + } + } + + if (ret != null) { + ret.lastRetrieved = time; + ++ret.currentParallelism; + ++ret.getGroup().currentParallelism; + return ret; + } + + return ret; + } + } + + private void returnQueue(final ExecutorGroup.ThreadPoolExecutor executor) { + synchronized (this) { + --executor.currentParallelism; + --executor.getGroup().currentParallelism; + } + + if (executor.isShutdown() && executor.queue.hasNoScheduledTasks()) { + executor.getGroup().executors.remove(executor); + } + } + + private void notifyAllThreads() { + for (final PrioritisedThread thread : this.threads.getArray()) { + thread.notifyTasks(); + } + } + + public ExecutorGroup createExecutorGroup(final int division, final int flags) { + synchronized (this) { + if (this.shutdown) { + throw new IllegalStateException("Queue is shutdown: " + this.toString()); + } + + final ExecutorGroup ret = new ExecutorGroup(division, flags); + + this.executors.add(ret); + + return ret; + } + } + + private final class PrioritisedThread extends PrioritisedQueueExecutorThread { + + private final AtomicBoolean alertedHighPriority = new AtomicBoolean(); + + public PrioritisedThread() { + super(null); + } + + public boolean alertHighPriorityExecutor() { + if (!this.notifyTasks()) { + if (!this.alertedHighPriority.get()) { + this.alertedHighPriority.set(true); + } + return false; + } + + return true; + } + + private boolean isAlertedHighPriority() { + return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false); + } + + @Override + protected void die() { + PrioritisedThreadPool.this.die(this); + } + + @Override + protected boolean pollTasks() { + boolean ret = false; + + for (;;) { + if (this.halted) { + break; + } + + final ExecutorGroup.ThreadPoolExecutor executor = PrioritisedThreadPool.this.obtainQueue(); + if (executor == null) { + break; + } + final long deadline = System.nanoTime() + executor.queueMaxHoldTime; + do { + try { + if (this.halted || executor.halt) { + break; + } + if (!executor.executeTask()) { + // no more tasks, try next queue + break; + } + ret = true; + } catch (final Throwable throwable) { + LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + executor.toString() + "'", throwable); + } + } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline); + + PrioritisedThreadPool.this.returnQueue(executor); + } + + + return ret; + } + } + + public final class ExecutorGroup { + + private final AtomicLong subOrderGenerator = new AtomicLong(); + private final COWArrayList executors = new COWArrayList<>(ThreadPoolExecutor.class); + + private final int division; + private int currentParallelism; + + private ExecutorGroup(final int division, final int flags) { + this.division = division; + } + + public ThreadPoolExecutor[] getAllExecutors() { + return this.executors.getArray().clone(); + } + + private PrioritisedThreadPool getThreadPool() { + return PrioritisedThreadPool.this; + } + + public ThreadPoolExecutor createExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { + synchronized (PrioritisedThreadPool.this) { + if (PrioritisedThreadPool.this.shutdown) { + throw new IllegalStateException("Queue is shutdown: " + PrioritisedThreadPool.this.toString()); + } + + final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags); + + this.executors.add(ret); + + return ret; + } + } + + public final class ThreadPoolExecutor implements PrioritisedExecutor { + + private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue(); + + private volatile int maxParallelism; + private final long queueMaxHoldTime; + private volatile int currentParallelism; + private volatile boolean halt; + private long lastRetrieved = System.nanoTime(); + + private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) { + this.maxParallelism = maxParallelism; + this.queueMaxHoldTime = queueMaxHoldTime; + } + + private ExecutorGroup getGroup() { + return ExecutorGroup.this; + } + + private boolean canNotify() { + if (this.halt) { + return false; + } + + final int max = this.maxParallelism; + return max < 0 || this.currentParallelism < max; + } + + private void notifyHighPriority() { + if (!this.canNotify()) { + return; + } + for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { + if (thread.alertHighPriorityExecutor()) { + return; + } + } + } + + private void notifyScheduled() { + if (!this.canNotify()) { + return; + } + for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) { + if (thread.notifyTasks()) { + return; + } + } + } + + /** + * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed + */ + public void halt() { + this.halt = true; + + ExecutorGroup.this.executors.remove(this); + } + + /** + * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether + * this queue is not halted and not shutdown. + */ + public boolean isActive() { + if (this.halt) { + return this.currentParallelism > 0; + } else { + if (!this.isShutdown()) { + return true; + } + + return !this.queue.hasNoScheduledTasks(); + } + } + + @Override + public boolean shutdown() { + if (!this.queue.shutdown()) { + return false; + } + + if (this.queue.hasNoScheduledTasks()) { + ExecutorGroup.this.executors.remove(this); + } + + return true; + } + + @Override + public boolean isShutdown() { + return this.queue.isShutdown(); + } + + public void setMaxParallelism(final int maxParallelism) { + this.maxParallelism = maxParallelism; + // assume that we could have increased the parallelism + if (this.getTargetPriority() != null) { + ExecutorGroup.this.getThreadPool().notifyAllThreads(); + } + } + + Priority getTargetPriority() { + final Priority ret = this.queue.getHighestPriority(); + if (!this.isShutdown()) { + return ret; + } + + return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY); + } + + @Override + public long getTotalTasksScheduled() { + return this.queue.getTotalTasksScheduled(); + } + + @Override + public long getTotalTasksExecuted() { + return this.queue.getTotalTasksExecuted(); + } + + @Override + public long generateNextSubOrder() { + return ExecutorGroup.this.subOrderGenerator.getAndIncrement(); + } + + @Override + public boolean executeTask() { + return this.queue.executeTask(); + } + + @Override + public PrioritisedTask queueTask(final Runnable task) { + final PrioritisedTask ret = this.createTask(task); + + ret.queue(); + + return ret; + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority) { + final PrioritisedTask ret = this.createTask(task, priority); + + ret.queue(); + + return ret; + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { + final PrioritisedTask ret = this.createTask(task, priority, subOrder); + + ret.queue(); + + return ret; + } + + @Override + public PrioritisedTask createTask(final Runnable task) { + return this.createTask(task, Priority.NORMAL); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority) { + return this.createTask(task, priority, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { + return new WrappedTask(this.queue.createTask(task, priority, subOrder)); + } + + private final class WrappedTask implements PrioritisedTask { + + private final PrioritisedTask wrapped; + + private WrappedTask(final PrioritisedTask wrapped) { + this.wrapped = wrapped; + } + + @Override + public PrioritisedExecutor getExecutor() { + return ThreadPoolExecutor.this; + } + + @Override + public boolean queue() { + if (this.wrapped.queue()) { + final Priority priority = this.getPriority(); + if (priority != Priority.COMPLETING) { + if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { + ThreadPoolExecutor.this.notifyHighPriority(); + } else { + ThreadPoolExecutor.this.notifyScheduled(); + } + } + return true; + } + + return false; + } + + @Override + public boolean isQueued() { + return this.wrapped.isQueued(); + } + + @Override + public boolean cancel() { + return this.wrapped.cancel(); + } + + @Override + public boolean execute() { + return this.wrapped.execute(); + } + + @Override + public Priority getPriority() { + return this.wrapped.getPriority(); + } + + @Override + public boolean setPriority(final Priority priority) { + if (this.wrapped.setPriority(priority)) { + if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { + ThreadPoolExecutor.this.notifyHighPriority(); + } + return true; + } + + return false; + } + + @Override + public boolean raisePriority(final Priority priority) { + if (this.wrapped.raisePriority(priority)) { + if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { + ThreadPoolExecutor.this.notifyHighPriority(); + } + return true; + } + + return false; + } + + @Override + public boolean lowerPriority(final Priority priority) { + return this.wrapped.lowerPriority(priority); + } + + @Override + public long getSubOrder() { + return this.wrapped.getSubOrder(); + } + + @Override + public boolean setSubOrder(final long subOrder) { + return this.wrapped.setSubOrder(subOrder); + } + + @Override + public boolean raiseSubOrder(final long subOrder) { + return this.wrapped.raiseSubOrder(subOrder); + } + + @Override + public boolean lowerSubOrder(final long subOrder) { + return this.wrapped.lowerSubOrder(subOrder); + } + + @Override + public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { + if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) { + if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) { + ThreadPoolExecutor.this.notifyHighPriority(); + } + return true; + } + + return false; + } + } + } + } + + private static final class COWArrayList { + + private volatile E[] array; + + public COWArrayList(final Class clazz) { + this.array = (E[])Array.newInstance(clazz, 0); + } + + public E[] getArray() { + return this.array; + } + + public void add(final E element) { + synchronized (this) { + final E[] array = this.array; + + final E[] copy = Arrays.copyOf(array, array.length + 1); + copy[array.length] = element; + + this.array = copy; + } + } + + public boolean remove(final E element) { + synchronized (this) { + final E[] array = this.array; + int index = -1; + for (int i = 0, len = array.length; i < len; ++i) { + if (array[i] == element) { + index = i; + break; + } + } + + if (index == -1) { + return false; + } + + final E[] copy = (E[])Array.newInstance(array.getClass().getComponentType(), array.length - 1); + + System.arraycopy(array, 0, copy, 0, index); + System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index); + + this.array = copy; + } + + return true; + } + } +} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java index d701998b376579ec652fb94823befa3cc0bc4090..6918f130099e6c19e20a47bfdb54915cdd13732a 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java @@ -43,7 +43,7 @@ import java.util.function.Predicate; * @param * @see java.util.concurrent.ConcurrentMap */ -public class ConcurrentLong2ReferenceChainedHashTable { +public class ConcurrentLong2ReferenceChainedHashTable implements Iterable> { protected static final int DEFAULT_CAPACITY = 16; protected static final float DEFAULT_LOAD_FACTOR = 0.75f; @@ -192,6 +192,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue; } @@ -274,10 +275,10 @@ public class ConcurrentLong2ReferenceChainedHashTable { public int size() { final long ret = this.size.sum(); - if (ret <= 0L) { + if (ret < 0L) { return 0; } - if (ret >= (long)Integer.MAX_VALUE) { + if (ret > (long)Integer.MAX_VALUE) { return Integer.MAX_VALUE; } @@ -341,6 +342,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { // create new table data + // noinspection unchecked final TableEntry[] newTable = new TableEntry[capacity]; // noinspection unchecked final TableEntry resizeNode = new TableEntry<>(0L, (V)newTable, true); @@ -365,6 +367,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { throw new IllegalStateException("Resizing to same size"); } + // noinspection unchecked final TableEntry[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1 for (int i = 0, len = oldTable.length; i < len; ++i) { @@ -538,6 +541,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -599,6 +603,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -653,6 +658,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -704,6 +710,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -772,6 +779,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -848,6 +856,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -944,6 +953,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -1058,6 +1068,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -1126,6 +1137,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -1200,6 +1212,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } if (node.resize) { + // noinspection unchecked table = (TableEntry[])node.getValuePlain(); continue table_loop; } @@ -1288,6 +1301,11 @@ public class ConcurrentLong2ReferenceChainedHashTable { return new EntryIterator<>(this); } + @Override + public final Iterator> iterator() { + return this.entryIterator(); + } + /** * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were * added before the beginning of this call, but it may see keys added during. @@ -1306,7 +1324,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { protected static final class EntryIterator extends BaseIteratorImpl> { - protected EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { + public EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) { super(map); } @@ -1326,7 +1344,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { - protected KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { + public KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) { super(map); } @@ -1365,7 +1383,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { protected static final class ValueIterator extends BaseIteratorImpl { - protected ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { + public ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) { super(map); } @@ -1420,7 +1438,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { @Override public final void remove() { - final TableEntry lastReturned = this.nextToReturn; + final TableEntry lastReturned = this.lastReturned; if (lastReturned == null) { throw new NoSuchElementException(); } @@ -1492,6 +1510,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { final ResizeChain chain = this.resizeChain; if (chain == null) { + // noinspection unchecked final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain(); final ResizeChain oldChain = new ResizeChain<>(table, null, null); @@ -1506,6 +1525,7 @@ public class ConcurrentLong2ReferenceChainedHashTable { } else { ResizeChain currChain = chain.next; if (currChain == null) { + // noinspection unchecked final TableEntry[] ret = (TableEntry[])entry.getValuePlain(); currChain = new ResizeChain<>(ret, chain, null); chain.next = currChain; @@ -1586,11 +1606,11 @@ public class ConcurrentLong2ReferenceChainedHashTable { protected static final class ResizeChain { - protected final TableEntry[] table; - protected final ResizeChain prev; - protected ResizeChain next; + public final TableEntry[] table; + public final ResizeChain prev; + public ResizeChain next; - protected ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { + public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) { this.table = table; this.prev = prev; this.next = next; @@ -1600,64 +1620,64 @@ public class ConcurrentLong2ReferenceChainedHashTable { public static final class TableEntry { - protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); - protected final boolean resize; + private final boolean resize; - protected final long key; + private final long key; - protected volatile V value; - protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); + private volatile V value; + private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); - protected final V getValuePlain() { + private V getValuePlain() { //noinspection unchecked return (V)VALUE_HANDLE.get(this); } - protected final V getValueAcquire() { + private V getValueAcquire() { //noinspection unchecked return (V)VALUE_HANDLE.getAcquire(this); } - protected final V getValueVolatile() { + private V getValueVolatile() { //noinspection unchecked return (V)VALUE_HANDLE.getVolatile(this); } - protected final void setValuePlain(final V value) { + private void setValuePlain(final V value) { VALUE_HANDLE.set(this, (Object)value); } - protected final void setValueRelease(final V value) { + private void setValueRelease(final V value) { VALUE_HANDLE.setRelease(this, (Object)value); } - protected final void setValueVolatile(final V value) { + private void setValueVolatile(final V value) { VALUE_HANDLE.setVolatile(this, (Object)value); } - protected volatile TableEntry next; - protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + private volatile TableEntry next; + private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); - protected final TableEntry getNextPlain() { + private TableEntry getNextPlain() { //noinspection unchecked return (TableEntry)NEXT_HANDLE.get(this); } - protected final TableEntry getNextVolatile() { + private TableEntry getNextVolatile() { //noinspection unchecked return (TableEntry)NEXT_HANDLE.getVolatile(this); } - protected final void setNextPlain(final TableEntry next) { + private void setNextPlain(final TableEntry next) { NEXT_HANDLE.set(this, next); } - protected final void setNextRelease(final TableEntry next) { + private void setNextRelease(final TableEntry next) { NEXT_HANDLE.setRelease(this, next); } - protected final void setNextVolatile(final TableEntry next) { + private void setNextVolatile(final TableEntry next) { NEXT_HANDLE.setVolatile(this, next); } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java index 8197ccb1c4e5878dbd8007b5fb514640765ec8e4..85e6ef636d435a0ee4bf3e0760b0c87422c520a1 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java @@ -13,6 +13,10 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; import java.util.function.BooleanSupplier; +/** + * @deprecated To be replaced + */ +@Deprecated public class SchedulerThreadPool { public static final long DEADLINE_NOT_SET = Long.MIN_VALUE; @@ -297,7 +301,9 @@ public class SchedulerThreadPool { * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to * parse intermediate tasks. *

+ * @deprecated To be replaced */ + @Deprecated public static abstract class SchedulableTick { private static final AtomicLong ID_GENERATOR = new AtomicLong(); public final long id = ID_GENERATOR.getAndIncrement(); diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java index 212bc9ae2fc7d37d4a089a2921b00de1e97f7cc1..82c4c11b0b564c97ac92bd5f54e3754a7ba95184 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java @@ -8,8 +8,8 @@ public final class LinkedSortedSet implements Iterable { public final Comparator comparator; - protected Link head; - protected Link tail; + private Link head; + private Link tail; public LinkedSortedSet() { this((Comparator)Comparator.naturalOrder()); @@ -257,8 +257,6 @@ public final class LinkedSortedSet implements Iterable { private Link prev; private Link next; - private Link() {} - private Link(final E element) { this.element = element; } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java new file mode 100644 index 0000000000000000000000000000000000000000..bd8eb4f25d1dee00fbf9c05c14b0d94c5c641a55 --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java @@ -0,0 +1,204 @@ +package ca.spottedleaf.concurrentutil.set; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; + +public final class LinkedUnsortedList implements Iterable { + + private Link head; + private Link tail; + + public LinkedUnsortedList() {} + + public void clear() { + this.head = this.tail = null; + } + + public boolean isEmpty() { + return this.head == null; + } + + public E first() { + final Link head = this.head; + return head == null ? null : head.element; + } + + public E last() { + final Link tail = this.tail; + return tail == null ? null : tail.element; + } + + public boolean containsFirst(final E element) { + for (Link curr = this.head; curr != null; curr = curr.next) { + if (Objects.equals(element, curr.element)) { + return true; + } + } + return false; + } + + public boolean containsLast(final E element) { + for (Link curr = this.tail; curr != null; curr = curr.prev) { + if (Objects.equals(element, curr.element)) { + return true; + } + } + return false; + } + + private void removeNode(final Link node) { + final Link prev = node.prev; + final Link next = node.next; + + // help GC + node.element = null; + node.prev = null; + node.next = null; + + if (prev == null) { + this.head = next; + } else { + prev.next = next; + } + + if (next == null) { + this.tail = prev; + } else { + next.prev = prev; + } + } + + public boolean remove(final Link link) { + if (link.element == null) { + return false; + } + + this.removeNode(link); + return true; + } + + public boolean removeFirst(final E element) { + for (Link curr = this.head; curr != null; curr = curr.next) { + if (Objects.equals(element, curr.element)) { + this.removeNode(curr); + return true; + } + } + return false; + } + + public boolean removeLast(final E element) { + for (Link curr = this.tail; curr != null; curr = curr.prev) { + if (Objects.equals(element, curr.element)) { + this.removeNode(curr); + return true; + } + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + private Link next = LinkedUnsortedList.this.head; + + @Override + public boolean hasNext() { + return this.next != null; + } + + @Override + public E next() { + final Link next = this.next; + if (next == null) { + throw new NoSuchElementException(); + } + this.next = next.next; + return next.element; + } + }; + } + + public E pollFirst() { + final Link head = this.head; + if (head == null) { + return null; + } + + final E ret = head.element; + final Link next = head.next; + + // unlink head + this.head = next; + if (next == null) { + this.tail = null; + } else { + next.prev = null; + } + + // help GC + head.element = null; + head.next = null; + + return ret; + } + + public E pollLast() { + final Link tail = this.tail; + if (tail == null) { + return null; + } + + final E ret = tail.element; + final Link prev = tail.prev; + + // unlink tail + this.tail = prev; + if (prev == null) { + this.head = null; + } else { + prev.next = null; + } + + // help GC + tail.element = null; + tail.prev = null; + + return ret; + } + + public Link addLast(final E element) { + final Link curr = this.tail; + if (curr != null) { + return this.tail = new Link<>(element, curr, null); + } else { + return this.head = this.tail = new Link<>(element); + } + } + + public Link addFirst(final E element) { + final Link curr = this.head; + if (curr != null) { + return this.head = new Link<>(element, null, curr); + } else { + return this.head = this.tail = new Link<>(element); + } + } + + public static final class Link { + private E element; + private Link prev; + private Link next; + + private Link(final E element) { + this.element = element; + } + + private Link(final E element, final Link prev, final Link next) { + this.element = element; + this.prev = prev; + this.next = next; + } + } +} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java deleted file mode 100644 index ebb1ab06165addb173fea4d295001fe37f4e79d3..0000000000000000000000000000000000000000 --- a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java +++ /dev/null @@ -1,816 +0,0 @@ -package ca.spottedleaf.concurrentutil.util; - -import java.lang.invoke.VarHandle; - -public final class ArrayUtil { - - public static final VarHandle BOOLEAN_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(boolean[].class); - - public static final VarHandle BYTE_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(byte[].class); - - public static final VarHandle SHORT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(short[].class); - - public static final VarHandle INT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(int[].class); - - public static final VarHandle LONG_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(long[].class); - - public static final VarHandle OBJECT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(Object[].class); - - private ArrayUtil() { - throw new RuntimeException(); - } - - /* byte array */ - - public static byte getPlain(final byte[] array, final int index) { - return (byte)BYTE_ARRAY_HANDLE.get(array, index); - } - - public static byte getOpaque(final byte[] array, final int index) { - return (byte)BYTE_ARRAY_HANDLE.getOpaque(array, index); - } - - public static byte getAcquire(final byte[] array, final int index) { - return (byte)BYTE_ARRAY_HANDLE.getAcquire(array, index); - } - - public static byte getVolatile(final byte[] array, final int index) { - return (byte)BYTE_ARRAY_HANDLE.getVolatile(array, index); - } - - public static void setPlain(final byte[] array, final int index, final byte value) { - BYTE_ARRAY_HANDLE.set(array, index, value); - } - - public static void setOpaque(final byte[] array, final int index, final byte value) { - BYTE_ARRAY_HANDLE.setOpaque(array, index, value); - } - - public static void setRelease(final byte[] array, final int index, final byte value) { - BYTE_ARRAY_HANDLE.setRelease(array, index, value); - } - - public static void setVolatile(final byte[] array, final int index, final byte value) { - BYTE_ARRAY_HANDLE.setVolatile(array, index, value); - } - - public static void setVolatileContended(final byte[] array, final int index, final byte param) { - int failures = 0; - - for (byte curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return; - } - } - } - - public static byte compareAndExchangeVolatile(final byte[] array, final int index, final byte expect, final byte update) { - return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static byte getAndAddVolatile(final byte[] array, final int index, final byte param) { - return (byte)BYTE_ARRAY_HANDLE.getAndAdd(array, index, param); - } - - public static byte getAndAndVolatile(final byte[] array, final int index, final byte param) { - return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); - } - - public static byte getAndOrVolatile(final byte[] array, final int index, final byte param) { - return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); - } - - public static byte getAndXorVolatile(final byte[] array, final int index, final byte param) { - return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); - } - - public static byte getAndSetVolatile(final byte[] array, final int index, final byte param) { - return (byte)BYTE_ARRAY_HANDLE.getAndSet(array, index, param); - } - - public static byte compareAndExchangeVolatileContended(final byte[] array, final int index, final byte expect, final byte update) { - return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static byte getAndAddVolatileContended(final byte[] array, final int index, final byte param) { - int failures = 0; - - for (byte curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr + param)))) { - return curr; - } - } - } - - public static byte getAndAndVolatileContended(final byte[] array, final int index, final byte param) { - int failures = 0; - - for (byte curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr & param)))) { - return curr; - } - } - } - - public static byte getAndOrVolatileContended(final byte[] array, final int index, final byte param) { - int failures = 0; - - for (byte curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr | param)))) { - return curr; - } - } - } - - public static byte getAndXorVolatileContended(final byte[] array, final int index, final byte param) { - int failures = 0; - - for (byte curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr ^ param)))) { - return curr; - } - } - } - - public static byte getAndSetVolatileContended(final byte[] array, final int index, final byte param) { - int failures = 0; - - for (byte curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return curr; - } - } - } - - /* short array */ - - public static short getPlain(final short[] array, final int index) { - return (short)SHORT_ARRAY_HANDLE.get(array, index); - } - - public static short getOpaque(final short[] array, final int index) { - return (short)SHORT_ARRAY_HANDLE.getOpaque(array, index); - } - - public static short getAcquire(final short[] array, final int index) { - return (short)SHORT_ARRAY_HANDLE.getAcquire(array, index); - } - - public static short getVolatile(final short[] array, final int index) { - return (short)SHORT_ARRAY_HANDLE.getVolatile(array, index); - } - - public static void setPlain(final short[] array, final int index, final short value) { - SHORT_ARRAY_HANDLE.set(array, index, value); - } - - public static void setOpaque(final short[] array, final int index, final short value) { - SHORT_ARRAY_HANDLE.setOpaque(array, index, value); - } - - public static void setRelease(final short[] array, final int index, final short value) { - SHORT_ARRAY_HANDLE.setRelease(array, index, value); - } - - public static void setVolatile(final short[] array, final int index, final short value) { - SHORT_ARRAY_HANDLE.setVolatile(array, index, value); - } - - public static void setVolatileContended(final short[] array, final int index, final short param) { - int failures = 0; - - for (short curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return; - } - } - } - - public static short compareAndExchangeVolatile(final short[] array, final int index, final short expect, final short update) { - return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static short getAndAddVolatile(final short[] array, final int index, final short param) { - return (short)SHORT_ARRAY_HANDLE.getAndAdd(array, index, param); - } - - public static short getAndAndVolatile(final short[] array, final int index, final short param) { - return (short)SHORT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); - } - - public static short getAndOrVolatile(final short[] array, final int index, final short param) { - return (short)SHORT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); - } - - public static short getAndXorVolatile(final short[] array, final int index, final short param) { - return (short)SHORT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); - } - - public static short getAndSetVolatile(final short[] array, final int index, final short param) { - return (short)SHORT_ARRAY_HANDLE.getAndSet(array, index, param); - } - - public static short compareAndExchangeVolatileContended(final short[] array, final int index, final short expect, final short update) { - return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static short getAndAddVolatileContended(final short[] array, final int index, final short param) { - int failures = 0; - - for (short curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr + param)))) { - return curr; - } - } - } - - public static short getAndAndVolatileContended(final short[] array, final int index, final short param) { - int failures = 0; - - for (short curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr & param)))) { - return curr; - } - } - } - - public static short getAndOrVolatileContended(final short[] array, final int index, final short param) { - int failures = 0; - - for (short curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr | param)))) { - return curr; - } - } - } - - public static short getAndXorVolatileContended(final short[] array, final int index, final short param) { - int failures = 0; - - for (short curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr ^ param)))) { - return curr; - } - } - } - - public static short getAndSetVolatileContended(final short[] array, final int index, final short param) { - int failures = 0; - - for (short curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return curr; - } - } - } - - /* int array */ - - public static int getPlain(final int[] array, final int index) { - return (int)INT_ARRAY_HANDLE.get(array, index); - } - - public static int getOpaque(final int[] array, final int index) { - return (int)INT_ARRAY_HANDLE.getOpaque(array, index); - } - - public static int getAcquire(final int[] array, final int index) { - return (int)INT_ARRAY_HANDLE.getAcquire(array, index); - } - - public static int getVolatile(final int[] array, final int index) { - return (int)INT_ARRAY_HANDLE.getVolatile(array, index); - } - - public static void setPlain(final int[] array, final int index, final int value) { - INT_ARRAY_HANDLE.set(array, index, value); - } - - public static void setOpaque(final int[] array, final int index, final int value) { - INT_ARRAY_HANDLE.setOpaque(array, index, value); - } - - public static void setRelease(final int[] array, final int index, final int value) { - INT_ARRAY_HANDLE.setRelease(array, index, value); - } - - public static void setVolatile(final int[] array, final int index, final int value) { - INT_ARRAY_HANDLE.setVolatile(array, index, value); - } - - public static void setVolatileContended(final int[] array, final int index, final int param) { - int failures = 0; - - for (int curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return; - } - } - } - - public static int compareAndExchangeVolatile(final int[] array, final int index, final int expect, final int update) { - return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static int getAndAddVolatile(final int[] array, final int index, final int param) { - return (int)INT_ARRAY_HANDLE.getAndAdd(array, index, param); - } - - public static int getAndAndVolatile(final int[] array, final int index, final int param) { - return (int)INT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); - } - - public static int getAndOrVolatile(final int[] array, final int index, final int param) { - return (int)INT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); - } - - public static int getAndXorVolatile(final int[] array, final int index, final int param) { - return (int)INT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); - } - - public static int getAndSetVolatile(final int[] array, final int index, final int param) { - return (int)INT_ARRAY_HANDLE.getAndSet(array, index, param); - } - - public static int compareAndExchangeVolatileContended(final int[] array, final int index, final int expect, final int update) { - return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static int getAndAddVolatileContended(final int[] array, final int index, final int param) { - int failures = 0; - - for (int curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr + param)))) { - return curr; - } - } - } - - public static int getAndAndVolatileContended(final int[] array, final int index, final int param) { - int failures = 0; - - for (int curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr & param)))) { - return curr; - } - } - } - - public static int getAndOrVolatileContended(final int[] array, final int index, final int param) { - int failures = 0; - - for (int curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr | param)))) { - return curr; - } - } - } - - public static int getAndXorVolatileContended(final int[] array, final int index, final int param) { - int failures = 0; - - for (int curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr ^ param)))) { - return curr; - } - } - } - - public static int getAndSetVolatileContended(final int[] array, final int index, final int param) { - int failures = 0; - - for (int curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return curr; - } - } - } - - /* long array */ - - public static long getPlain(final long[] array, final int index) { - return (long)LONG_ARRAY_HANDLE.get(array, index); - } - - public static long getOpaque(final long[] array, final int index) { - return (long)LONG_ARRAY_HANDLE.getOpaque(array, index); - } - - public static long getAcquire(final long[] array, final int index) { - return (long)LONG_ARRAY_HANDLE.getAcquire(array, index); - } - - public static long getVolatile(final long[] array, final int index) { - return (long)LONG_ARRAY_HANDLE.getVolatile(array, index); - } - - public static void setPlain(final long[] array, final int index, final long value) { - LONG_ARRAY_HANDLE.set(array, index, value); - } - - public static void setOpaque(final long[] array, final int index, final long value) { - LONG_ARRAY_HANDLE.setOpaque(array, index, value); - } - - public static void setRelease(final long[] array, final int index, final long value) { - LONG_ARRAY_HANDLE.setRelease(array, index, value); - } - - public static void setVolatile(final long[] array, final int index, final long value) { - LONG_ARRAY_HANDLE.setVolatile(array, index, value); - } - - public static void setVolatileContended(final long[] array, final int index, final long param) { - int failures = 0; - - for (long curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return; - } - } - } - - public static long compareAndExchangeVolatile(final long[] array, final int index, final long expect, final long update) { - return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static long getAndAddVolatile(final long[] array, final int index, final long param) { - return (long)LONG_ARRAY_HANDLE.getAndAdd(array, index, param); - } - - public static long getAndAndVolatile(final long[] array, final int index, final long param) { - return (long)LONG_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param); - } - - public static long getAndOrVolatile(final long[] array, final int index, final long param) { - return (long)LONG_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); - } - - public static long getAndXorVolatile(final long[] array, final int index, final long param) { - return (long)LONG_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); - } - - public static long getAndSetVolatile(final long[] array, final int index, final long param) { - return (long)LONG_ARRAY_HANDLE.getAndSet(array, index, param); - } - - public static long compareAndExchangeVolatileContended(final long[] array, final int index, final long expect, final long update) { - return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static long getAndAddVolatileContended(final long[] array, final int index, final long param) { - int failures = 0; - - for (long curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr + param)))) { - return curr; - } - } - } - - public static long getAndAndVolatileContended(final long[] array, final int index, final long param) { - int failures = 0; - - for (long curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr & param)))) { - return curr; - } - } - } - - public static long getAndOrVolatileContended(final long[] array, final int index, final long param) { - int failures = 0; - - for (long curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr | param)))) { - return curr; - } - } - } - - public static long getAndXorVolatileContended(final long[] array, final int index, final long param) { - int failures = 0; - - for (long curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr ^ param)))) { - return curr; - } - } - } - - public static long getAndSetVolatileContended(final long[] array, final int index, final long param) { - int failures = 0; - - for (long curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return curr; - } - } - } - - /* boolean array */ - - public static boolean getPlain(final boolean[] array, final int index) { - return (boolean)BOOLEAN_ARRAY_HANDLE.get(array, index); - } - - public static boolean getOpaque(final boolean[] array, final int index) { - return (boolean)BOOLEAN_ARRAY_HANDLE.getOpaque(array, index); - } - - public static boolean getAcquire(final boolean[] array, final int index) { - return (boolean)BOOLEAN_ARRAY_HANDLE.getAcquire(array, index); - } - - public static boolean getVolatile(final boolean[] array, final int index) { - return (boolean)BOOLEAN_ARRAY_HANDLE.getVolatile(array, index); - } - - public static void setPlain(final boolean[] array, final int index, final boolean value) { - BOOLEAN_ARRAY_HANDLE.set(array, index, value); - } - - public static void setOpaque(final boolean[] array, final int index, final boolean value) { - BOOLEAN_ARRAY_HANDLE.setOpaque(array, index, value); - } - - public static void setRelease(final boolean[] array, final int index, final boolean value) { - BOOLEAN_ARRAY_HANDLE.setRelease(array, index, value); - } - - public static void setVolatile(final boolean[] array, final int index, final boolean value) { - BOOLEAN_ARRAY_HANDLE.setVolatile(array, index, value); - } - - public static void setVolatileContended(final boolean[] array, final int index, final boolean param) { - int failures = 0; - - for (boolean curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return; - } - } - } - - public static boolean compareAndExchangeVolatile(final boolean[] array, final int index, final boolean expect, final boolean update) { - return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static boolean getAndOrVolatile(final boolean[] array, final int index, final boolean param) { - return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseOr(array, index, param); - } - - public static boolean getAndXorVolatile(final boolean[] array, final int index, final boolean param) { - return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseXor(array, index, param); - } - - public static boolean getAndSetVolatile(final boolean[] array, final int index, final boolean param) { - return (boolean)BOOLEAN_ARRAY_HANDLE.getAndSet(array, index, param); - } - - public static boolean compareAndExchangeVolatileContended(final boolean[] array, final int index, final boolean expect, final boolean update) { - return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update); - } - - public static boolean getAndAndVolatileContended(final boolean[] array, final int index, final boolean param) { - int failures = 0; - - for (boolean curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr & param)))) { - return curr; - } - } - } - - public static boolean getAndOrVolatileContended(final boolean[] array, final int index, final boolean param) { - int failures = 0; - - for (boolean curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr | param)))) { - return curr; - } - } - } - - public static boolean getAndXorVolatileContended(final boolean[] array, final int index, final boolean param) { - int failures = 0; - - for (boolean curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr ^ param)))) { - return curr; - } - } - } - - public static boolean getAndSetVolatileContended(final boolean[] array, final int index, final boolean param) { - int failures = 0; - - for (boolean curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return curr; - } - } - } - - @SuppressWarnings("unchecked") - public static T getPlain(final T[] array, final int index) { - final Object ret = OBJECT_ARRAY_HANDLE.get((Object[])array, index); - return (T)ret; - } - - @SuppressWarnings("unchecked") - public static T getOpaque(final T[] array, final int index) { - final Object ret = OBJECT_ARRAY_HANDLE.getOpaque((Object[])array, index); - return (T)ret; - } - - @SuppressWarnings("unchecked") - public static T getAcquire(final T[] array, final int index) { - final Object ret = OBJECT_ARRAY_HANDLE.getAcquire((Object[])array, index); - return (T)ret; - } - - @SuppressWarnings("unchecked") - public static T getVolatile(final T[] array, final int index) { - final Object ret = OBJECT_ARRAY_HANDLE.getVolatile((Object[])array, index); - return (T)ret; - } - - public static void setPlain(final T[] array, final int index, final T value) { - OBJECT_ARRAY_HANDLE.set((Object[])array, index, (Object)value); - } - - public static void setOpaque(final T[] array, final int index, final T value) { - OBJECT_ARRAY_HANDLE.setOpaque((Object[])array, index, (Object)value); - } - - public static void setRelease(final T[] array, final int index, final T value) { - OBJECT_ARRAY_HANDLE.setRelease((Object[])array, index, (Object)value); - } - - public static void setVolatile(final T[] array, final int index, final T value) { - OBJECT_ARRAY_HANDLE.setVolatile((Object[])array, index, (Object)value); - } - - public static void setVolatileContended(final T[] array, final int index, final T param) { - int failures = 0; - - for (T curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return; - } - } - } - - @SuppressWarnings("unchecked") - public static T compareAndExchangeVolatile(final T[] array, final int index, final T expect, final T update) { - final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); - return (T)ret; - } - - @SuppressWarnings("unchecked") - public static T getAndSetVolatile(final T[] array, final int index, final T param) { - final Object ret = BYTE_ARRAY_HANDLE.getAndSet((Object[])array, index, (Object)param); - return (T)ret; - } - - @SuppressWarnings("unchecked") - public static T compareAndExchangeVolatileContended(final T[] array, final int index, final T expect, final T update) { - final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update); - return (T)ret; - } - - public static T getAndSetVolatileContended(final T[] array, final int index, final T param) { - int failures = 0; - - for (T curr = getVolatile(array, index);;++failures) { - for (int i = 0; i < failures; ++i) { - ConcurrentUtil.backoff(); - } - - if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) { - return curr; - } - } - } -} diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java index 77699c5fa9681f9ec7aa05cbb50eb60828e193ab..9d7b9b8158cd01d12adbd7896ff77bee9828e101 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java @@ -170,6 +170,26 @@ public final class IntegerUtil { return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 } + // https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide + /** + * + * Usage: + *
+     * {@code
+     *     static final long mult = getSimpleMultiplier(divisor, bits);
+     *     long x = ...;
+     *     long magic = x * mult;
+     *     long divQ = magic >>> bits;
+     *     long divR = ((magic & ((1 << bits) - 1)) * divisor) >>> bits;
+     * }
+     * 
+ * + * @param bits The number of bits of precision for the returned result + */ + public static long getUnsignedDivisorMagic(final long divisor, final int bits) { + return (((1L << bits) - 1L) / divisor) + 1; + } + private IntegerUtil() { throw new RuntimeException(); } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java new file mode 100644 index 0000000000000000000000000000000000000000..2919bbaa07b70f182438c3be8f9ebbe0649809b6 --- /dev/null +++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java @@ -0,0 +1,145 @@ +package ca.spottedleaf.concurrentutil.util; + +public enum Priority { + + /** + * Priority value indicating the task has completed or is being completed. + * This priority cannot be used to schedule tasks. + */ + COMPLETING(-1), + + /** + * Absolute highest priority, should only be used for when a task is blocking a time-critical thread. + */ + BLOCKING(), + + /** + * Should only be used for urgent but not time-critical tasks. + */ + HIGHEST(), + + /** + * Two priorities above normal. + */ + HIGHER(), + + /** + * One priority above normal. + */ + HIGH(), + + /** + * Default priority. + */ + NORMAL(), + + /** + * One priority below normal. + */ + LOW(), + + /** + * Two priorities below normal. + */ + LOWER(), + + /** + * Use for tasks that should eventually execute, but are not needed to. + */ + LOWEST(), + + /** + * Use for tasks that can be delayed indefinitely. + */ + IDLE(); + + // returns whether the priority can be scheduled + public static boolean isValidPriority(final Priority priority) { + return priority != null && priority != priority.COMPLETING; + } + + // returns the higher priority of the two + public static Priority max(final Priority p1, final Priority p2) { + return p1.isHigherOrEqualPriority(p2) ? p1 : p2; + } + + // returns the lower priroity of the two + public static Priority min(final Priority p1, final Priority p2) { + return p1.isLowerOrEqualPriority(p2) ? p1 : p2; + } + + public boolean isHigherOrEqualPriority(final Priority than) { + return this.priority <= than.priority; + } + + public boolean isHigherPriority(final Priority than) { + return this.priority < than.priority; + } + + public boolean isLowerOrEqualPriority(final Priority than) { + return this.priority >= than.priority; + } + + public boolean isLowerPriority(final Priority than) { + return this.priority > than.priority; + } + + public boolean isHigherOrEqualPriority(final int than) { + return this.priority <= than; + } + + public boolean isHigherPriority(final int than) { + return this.priority < than; + } + + public boolean isLowerOrEqualPriority(final int than) { + return this.priority >= than; + } + + public boolean isLowerPriority(final int than) { + return this.priority > than; + } + + public static boolean isHigherOrEqualPriority(final int priority, final int than) { + return priority <= than; + } + + public static boolean isHigherPriority(final int priority, final int than) { + return priority < than; + } + + public static boolean isLowerOrEqualPriority(final int priority, final int than) { + return priority >= than; + } + + public static boolean isLowerPriority(final int priority, final int than) { + return priority > than; + } + + static final Priority[] PRIORITIES = Priority.values(); + + /** includes special priorities */ + public static final int TOTAL_PRIORITIES = PRIORITIES.length; + + public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1; + + public static Priority getPriority(final int priority) { + return PRIORITIES[priority + 1]; + } + + private static int priorityCounter; + + private static int nextCounter() { + return priorityCounter++; + } + + public final int priority; + + private Priority() { + this(nextCounter()); + } + + private Priority(final int priority) { + this.priority = priority; + } +} \ No newline at end of file