From 1e1a35648470b7275da3824d49c6f8d6b6145999 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Tue, 27 Feb 2024 23:43:49 +0300 Subject: [PATCH 01/14] small changes to the scheduler --- .../common/scheduler/AsyncScheduler.java | 89 +++++++++---------- .../common/scheduler/SpongeScheduler.java | 17 ++-- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index 6ad2165db0d..7390144c4fb 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -37,7 +37,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -47,15 +46,14 @@ public final class AsyncScheduler extends SpongeScheduler { // Locking mechanism private final Lock lock = new ReentrantLock(); private final Condition condition = this.lock.newCondition(); - private final AtomicBoolean stateChanged = new AtomicBoolean(false); + private boolean stateChanged; // The dynamic thread pooling executor of asynchronous tasks. private final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder() - .setNameFormat("Sponge-AsyncScheduler-%d") - .build()); + .setNameFormat("Sponge-AsyncScheduler-%d") + .build()); private volatile boolean running = true; // Adjustable timeout for pending Tasks - private long minimumTimeout = Long.MAX_VALUE; private long lastProcessingTimestamp; public AsyncScheduler() { @@ -70,51 +68,48 @@ public AsyncScheduler() { private void mainLoop() { this.lastProcessingTimestamp = System.nanoTime(); while (this.running) { - this.recalibrateMinimumTimeout(); this.runTick(); } } - private void recalibrateMinimumTimeout() { - this.lock.lock(); - try { - final Set tasks = this.tasks(); - this.minimumTimeout = Long.MAX_VALUE; - final long now = System.nanoTime(); - for (final ScheduledTask tmpTask : tasks) { - final SpongeScheduledTask task = (SpongeScheduledTask) tmpTask; - if (task.state() == SpongeScheduledTask.ScheduledTaskState.EXECUTING) { - // bail out for this task. We'll signal when we complete the task. - continue; - } - // Recalibrate the wait delay for processing tasks before new - // tasks cause the scheduler to process pending tasks. - if (task.task.delay == 0 && task.task.interval == 0) { - this.minimumTimeout = 0; - } - // The time since the task last executed or was added to the map - final long timeSinceLast = now - task.timestamp(); - - if (task.task.delay > 0 && task.state() == SpongeScheduledTask.ScheduledTaskState.WAITING) { - // There is an delay and the task hasn't run yet - this.minimumTimeout = Math.min(task.task.delay - timeSinceLast, this.minimumTimeout); - } - if (task.task.interval > 0 && task.state().isActive) { - // The task repeats and has run after the initial delay - this.minimumTimeout = Math.min(task.task.interval - timeSinceLast, this.minimumTimeout); - } - if (this.minimumTimeout <= 0) { - break; - } + private long recalibrateMinimumTimeout() { + long minimumTimeout = Long.MAX_VALUE; + final long now = System.nanoTime(); + boolean present = false; + for (final SpongeScheduledTask task : this.activeTasks()) { + present = true; + if (task.state() == SpongeScheduledTask.ScheduledTaskState.EXECUTING) { + // bail out for this task. We'll signal when we complete the task. + continue; } - if (!tasks.isEmpty()) { - final long latency = System.nanoTime() - this.lastProcessingTimestamp; - this.minimumTimeout -= (latency <= 0) ? 0 : latency; - this.minimumTimeout = (this.minimumTimeout < 0) ? 0 : this.minimumTimeout; + // Recalibrate the wait delay for processing tasks before new + // tasks cause the scheduler to process pending tasks. + if (task.task.delay == 0 && task.task.interval == 0) { + minimumTimeout = 0; } - } finally { - this.lock.unlock(); + // The time since the task last executed or was added to the map + final long timeSinceLast = now - task.timestamp(); + + if (task.task.delay > 0 && task.state() == SpongeScheduledTask.ScheduledTaskState.WAITING) { + // There is an delay and the task hasn't run yet + minimumTimeout = Math.min(task.task.delay - timeSinceLast, minimumTimeout); + } + if (task.task.interval > 0 && task.state().isActive) { + // The task repeats and has run after the initial delay + minimumTimeout = Math.min(task.task.interval - timeSinceLast, minimumTimeout); + } + if (minimumTimeout <= 0) { + break; + } + } + if (present) { + final long latency = System.nanoTime() - this.lastProcessingTimestamp; + if (latency > 0) + minimumTimeout -= latency; + + minimumTimeout = Math.max(0, minimumTimeout); } + return minimumTimeout; } @Override @@ -134,11 +129,11 @@ protected void preTick() { try { // If we have something that has indicated it needs to change, // don't await, just continue. - if (!this.stateChanged.get()) { - this.condition.await(this.minimumTimeout, TimeUnit.NANOSECONDS); + if (!this.stateChanged) { + this.condition.awaitNanos(this.recalibrateMinimumTimeout()); } // We're processing now. Set to false. - this.stateChanged.set(false); + this.stateChanged = false; } catch (final InterruptedException ignored) { // The taskMap has been modified; there is work to do. // Continue on without handling the Exception. @@ -162,7 +157,7 @@ protected void onTaskCompletion(final SpongeScheduledTask task) { if (task.state() == SpongeScheduledTask.ScheduledTaskState.RUNNING) { this.lock.lock(); try { - this.stateChanged.set(true); + this.stateChanged = true; this.condition.signalAll(); } finally { this.lock.unlock(); diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 1c0b8608246..a18717e3a70 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -38,17 +38,20 @@ import org.spongepowered.plugin.PluginContainer; import java.util.Iterator; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.Collection; +import java.util.Collections; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -62,8 +65,8 @@ public abstract class SpongeScheduler implements Scheduler { private final String tag; // The simple queue of all pending (and running) ScheduledTasks - private final Map tasks = new ConcurrentHashMap<>(); - private long sequenceNumber = 0L; + private final ConcurrentMap tasks = new ConcurrentHashMap<>(); + private final AtomicLong sequenceNumber = new AtomicLong(); SpongeScheduler(final String tag) { this.tag = tag; @@ -170,7 +173,7 @@ public SpongeScheduledTask submit(Task task, String name) { } final SpongeScheduledTask scheduledTask = new SpongeScheduledTask(this, (SpongeTask) task, - name + "-" + this.tag + "-#" + this.sequenceNumber++); + name + "-" + this.tag + "-#" + this.sequenceNumber.getAndIncrement()); this.addTask(scheduledTask); return scheduledTask; } @@ -188,6 +191,10 @@ final void runTick() { } } + protected Collection activeTasks() { + return Collections.unmodifiableCollection(this.tasks.values()); + } + /** * Fired when the scheduler begins to tick, before any tasks are processed. */ From ed1131632ecc512b428262024daed23bd64c212c Mon Sep 17 00:00:00 2001 From: sunmisc Date: Wed, 28 Feb 2024 00:22:07 +0300 Subject: [PATCH 02/14] fixed import format --- .../spongepowered/common/scheduler/SpongeScheduler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index a18717e3a70..688ab988910 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -37,19 +37,19 @@ import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.PluginContainer; +import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.Collection; -import java.util.Collections; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; From 921a0e9cfcae115b07d268c4d65ce63a73e1e6c8 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Fri, 1 Mar 2024 15:53:45 +0300 Subject: [PATCH 03/14] new scheduler based on PriorityQueue --- .../common/scheduler/AsyncScheduler.java | 166 +++--------- .../common/scheduler/Cyclic.java | 9 + .../scheduler/DelayQueueAsRunnable.java | 200 ++++++++++++++ .../common/scheduler/DelayedRunnable.java | 9 + .../common/scheduler/SpongeScheduledTask.java | 204 +++++++------- .../common/scheduler/SpongeScheduler.java | 254 ++++-------------- .../common/scheduler/SpongeTask.java | 35 +-- .../scheduler/SpongeTaskExecutorService.java | 63 ++--- .../common/scheduler/SyncScheduler.java | 21 +- 9 files changed, 440 insertions(+), 521 deletions(-) create mode 100644 src/main/java/org/spongepowered/common/scheduler/Cyclic.java create mode 100644 src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java create mode 100644 src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index 7390144c4fb..c1dc952dc96 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -26,148 +26,46 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.logging.log4j.Level; -import org.spongepowered.api.scheduler.ScheduledTask; +import org.spongepowered.api.scheduler.Task; import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.Set; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.DelayQueue; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -public final class AsyncScheduler extends SpongeScheduler { - // Locking mechanism - private final Lock lock = new ReentrantLock(); - private final Condition condition = this.lock.newCondition(); - private boolean stateChanged; - // The dynamic thread pooling executor of asynchronous tasks. - private final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder() - .setNameFormat("Sponge-AsyncScheduler-%d") - .build()); - private volatile boolean running = true; - - // Adjustable timeout for pending Tasks - private long lastProcessingTimestamp; +public final class AsyncScheduler extends SpongeScheduler implements AutoCloseable { + private static final long KEEP_ALIVE_MILLIS = 12L; + private final ThreadPoolExecutor executor; + private final BlockingQueue workQueue + = new DelayQueue<>(); public AsyncScheduler() { super("A"); - - final Thread thread = new Thread(AsyncScheduler.this::mainLoop); - thread.setName("Sponge Async Scheduler Thread"); - thread.setDaemon(true); - thread.start(); - } - - private void mainLoop() { - this.lastProcessingTimestamp = System.nanoTime(); - while (this.running) { - this.runTick(); - } - } - - private long recalibrateMinimumTimeout() { - long minimumTimeout = Long.MAX_VALUE; - final long now = System.nanoTime(); - boolean present = false; - for (final SpongeScheduledTask task : this.activeTasks()) { - present = true; - if (task.state() == SpongeScheduledTask.ScheduledTaskState.EXECUTING) { - // bail out for this task. We'll signal when we complete the task. - continue; - } - // Recalibrate the wait delay for processing tasks before new - // tasks cause the scheduler to process pending tasks. - if (task.task.delay == 0 && task.task.interval == 0) { - minimumTimeout = 0; - } - // The time since the task last executed or was added to the map - final long timeSinceLast = now - task.timestamp(); - - if (task.task.delay > 0 && task.state() == SpongeScheduledTask.ScheduledTaskState.WAITING) { - // There is an delay and the task hasn't run yet - minimumTimeout = Math.min(task.task.delay - timeSinceLast, minimumTimeout); - } - if (task.task.interval > 0 && task.state().isActive) { - // The task repeats and has run after the initial delay - minimumTimeout = Math.min(task.task.interval - timeSinceLast, minimumTimeout); - } - if (minimumTimeout <= 0) { - break; - } - } - if (present) { - final long latency = System.nanoTime() - this.lastProcessingTimestamp; - if (latency > 0) - minimumTimeout -= latency; - - minimumTimeout = Math.max(0, minimumTimeout); - } - return minimumTimeout; - } - - @Override - protected void addTask(final SpongeScheduledTask task) { - this.lock.lock(); - try { - super.addTask(task); - this.condition.signalAll(); - } finally { - this.lock.unlock(); - } - } - - @Override - protected void preTick() { - this.lock.lock(); - try { - // If we have something that has indicated it needs to change, - // don't await, just continue. - if (!this.stateChanged) { - this.condition.awaitNanos(this.recalibrateMinimumTimeout()); - } - // We're processing now. Set to false. - this.stateChanged = false; - } catch (final InterruptedException ignored) { - // The taskMap has been modified; there is work to do. - // Continue on without handling the Exception. - } catch (final IllegalMonitorStateException e) { - SpongeCommon.logger().error("The scheduler internal state machine suffered a catastrophic error", e); - } - } - - @Override - protected void postTick() { - this.lastProcessingTimestamp = System.nanoTime(); + this.executor = new ThreadPoolExecutor(1, Integer.MAX_VALUE, + KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS, + new DelayQueueAsRunnable<>(workQueue), + new ThreadFactoryBuilder() + .setNameFormat("Sponge-AsyncScheduler-%d") + .build() + ); } @Override - protected void finallyPostTick() { - this.lock.unlock(); + protected BlockingQueue getWorkQueue() { + return this.workQueue; } @Override - protected void onTaskCompletion(final SpongeScheduledTask task) { - if (task.state() == SpongeScheduledTask.ScheduledTaskState.RUNNING) { - this.lock.lock(); - try { - this.stateChanged = true; - this.condition.signalAll(); - } finally { - this.lock.unlock(); - } - } - } - - @Override - protected void executeRunnable(final Runnable runnable) { - this.executor.submit(runnable); + public DelayedRunnable submit(Task task) { + this.executor.prestartCoreThread(); + return super.submit(task); } public CompletableFuture submit(final Callable callable) { @@ -186,27 +84,25 @@ private CompletableFuture asyncFailableFuture(Callable call, Executor return ret; } + @Override public void close() { - this.running = false; - // Cancel all tasks - final Set tasks = this.tasks(); - tasks.forEach(ScheduledTask::cancel); - - // Shut down the executor - this.executor.shutdown(); - + final ExecutorService scheduler = this.executor; + if (scheduler.isTerminated()) { + return; + } + scheduler.shutdown(); try { - if (!this.executor.awaitTermination(5, TimeUnit.SECONDS)) { + if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { new PrettyPrinter() .add("Sponge async scheduler failed to shut down in 5 seconds! Tasks that may have been active:") - .addWithIndices(tasks) + .addWithIndices(activeTasks()) .add() .add("We will now attempt immediate shutdown.") .log(SpongeCommon.logger(), Level.WARN); - this.executor.shutdownNow(); + scheduler.shutdownNow(); } - } catch (final InterruptedException e) { + } catch (InterruptedException e) { SpongeCommon.logger().error("The async scheduler was interrupted while awaiting shutdown!"); } } diff --git a/src/main/java/org/spongepowered/common/scheduler/Cyclic.java b/src/main/java/org/spongepowered/common/scheduler/Cyclic.java new file mode 100644 index 00000000000..51bae270cf2 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/Cyclic.java @@ -0,0 +1,9 @@ +package org.spongepowered.common.scheduler; + +public interface Cyclic { + + void enqueue(DelayedRunnable command); + + + void finish(); +} diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java new file mode 100644 index 00000000000..b8c5a045403 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java @@ -0,0 +1,200 @@ +package org.spongepowered.common.scheduler; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.IntFunction; + +@SuppressWarnings("unchecked") +public class DelayQueueAsRunnable + implements BlockingQueue { + + private final BlockingQueue delayQueue; + + public DelayQueueAsRunnable(BlockingQueue delayQueue) { + this.delayQueue = delayQueue; + } + @Override + public Runnable poll() { + return this.delayQueue.poll(); + } + + @Override + public boolean add(Runnable runnable) { + return this.delayQueue.add((E) runnable); + } + + @Override + public boolean offer(Runnable runnable) { + return this.delayQueue.offer((E) runnable); + } + + @Override + public void put(Runnable runnable) throws InterruptedException { + this.delayQueue.put((E) runnable); + } + + @Override + public boolean offer(Runnable runnable, long timeout, TimeUnit unit) throws InterruptedException { + return this.delayQueue.offer((E)runnable, timeout, unit); + } + + @Override + public @NotNull E take() throws InterruptedException { + return this.delayQueue.take(); + } + + @Override + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + return this.delayQueue.poll(timeout, unit); + } + + @Override + public E remove() { + return this.delayQueue.remove(); + } + + @Override + public E peek() { + return this.delayQueue.peek(); + } + + @Override + public int size() { + return this.delayQueue.size(); + } + + @Override + public void clear() { + this.delayQueue.clear(); + } + + @Override + public int remainingCapacity() { + return this.delayQueue.remainingCapacity(); + } + + @Override + public boolean remove(Object o) { + return this.delayQueue.remove(o); + } + + @Override + public E element() { + return this.delayQueue.element(); + } + + @Override + public boolean isEmpty() { + return this.delayQueue.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.delayQueue.contains(o); + } + + + @Override + public int drainTo(Collection c) { + return this.delayQueue.drainTo(c); + } + + @Override + public int drainTo(Collection c, int maxElements) { + return this.delayQueue.drainTo(c, maxElements); + } + + @Override + public boolean containsAll(Collection c) { + return this.delayQueue.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + Objects.requireNonNull(c); + if (c == this) + throw new IllegalArgumentException(); + boolean modified = false; + for (Runnable e : c) + if (add(e)) + modified = true; + return modified; + } + + @Override + public boolean removeAll(Collection c) { + return this.delayQueue.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return this.delayQueue.retainAll(c); + } + + @Override + public Object[] toArray() { + return this.delayQueue.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return this.delayQueue.toArray(a); + } + @Override + public T[] toArray(IntFunction generator) { + return this.delayQueue.toArray(generator); + } + + @Override + public Iterator iterator() { + return new Itr<>(this.delayQueue.iterator()); + } + + @Override + public boolean equals(Object o) { + return this.delayQueue.equals(o); + } + @Override + public int hashCode() { + return this.delayQueue.hashCode(); + } + + @Override + public String toString() { + return this.delayQueue.toString(); + } + + + private record Itr( + Iterator src + ) implements Iterator { + + @Override + public boolean hasNext() { + return this.src.hasNext(); + } + + @Override + public Runnable next() { + return this.src.next(); + } + + @Override + public void remove() { + this.src.remove(); + } + + @Override + public void forEachRemaining(Consumer action) { + this.src.forEachRemaining(action); + } + + } +} diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java new file mode 100644 index 00000000000..ae47180fde9 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java @@ -0,0 +1,9 @@ +package org.spongepowered.common.scheduler; + +import org.spongepowered.api.scheduler.ScheduledTask; + +import java.util.concurrent.Delayed; + +public interface DelayedRunnable extends Delayed, Runnable, ScheduledTask { + boolean isPeriodic(); +} diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java index 5a4d1544fe9..322f4a2a76a 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java @@ -1,157 +1,141 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package org.spongepowered.common.scheduler; -import com.google.common.base.MoreObjects; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; +import org.spongepowered.common.SpongeCommon; +import org.spongepowered.common.event.tracking.PhaseContext; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.event.tracking.phase.plugin.PluginPhase; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.util.UUID; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; -/** - * An internal representation of a {@link Task} created by a plugin. - */ -public final class SpongeScheduledTask implements ScheduledTask { +import static java.util.concurrent.TimeUnit.NANOSECONDS; +public class SpongeScheduledTask implements ScheduledTask, DelayedRunnable { private final SpongeScheduler scheduler; - final SpongeTask task; - private final UUID uniqueId; private final String name; + private final UUID uuid; + private final SpongeTask src; + private volatile boolean cancelled = false; - private long timestamp; - private ScheduledTaskState state; - private boolean isCancelled = false; - - SpongeScheduledTask(final SpongeScheduler scheduler, final SpongeTask task, final String name) { + /* + * Negative value - time in ticks + * This is done for atomic access of the getDelay() method + */ + private volatile long time; + private final Cyclic cyclic; + + SpongeScheduledTask(final SpongeScheduler scheduler, + final String name, final UUID uuid, + final SpongeTask src, + final long start, + final Cyclic cyclic) { this.scheduler = scheduler; - this.task = task; this.name = name; - this.uniqueId = UUID.randomUUID(); - // All tasks begin waiting. - this.state = ScheduledTaskState.WAITING; + this.uuid = uuid; + this.src = src; + this.time = start; + this.cyclic = cyclic; } - @Override - public Scheduler scheduler() { - return this.scheduler; + public void run() { + if (this.isCancelled()) + return; + final SpongeTask x = this.src; + + try (final @Nullable PhaseContext<@NonNull ?> context = PluginPhase.State.SCHEDULED_TASK + .createPhaseContext(PhaseTracker.getInstance()) + .source(this) + .container(x.plugin())) { + context.buildAndSwitch(); + try { + x.executor().accept(this); + } catch (final Throwable t) { + SpongeCommon.logger().error("The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", + name, x.plugin().metadata().id(), t); + } + } + + if (isPeriodic()) { + final long q = x.interval; + this.time = q < 0 + ? -(this.scheduler.timestamp(true) - q) + : this.scheduler.timestamp(false) + q; + this.cyclic.enqueue(this); + } else { + this.cyclic.finish(); + } } @Override - public Task task() { - return this.task; + public long getDelay(TimeUnit unit) { + final long p = this.time, now = this.scheduler.timestamp(p < 0); + return unit.convert(Math.abs(p) - now, NANOSECONDS); } @Override - public UUID uniqueId() { - return this.uniqueId; + public Scheduler scheduler() { + return this.scheduler; } @Override - public String name() { - return this.name; + public Task task() { + return this.src; } @Override public boolean cancel() { - final boolean success = this.state() == ScheduledTaskState.RUNNING - || this.state() == ScheduledTaskState.EXECUTING; - this.state = ScheduledTaskState.CANCELED; - this.isCancelled = true; - return success; + final boolean cancelled = !this.cancelled || + CANCELLED.compareAndSet(this, false, true); + if (cancelled) + this.cyclic.finish(); + return cancelled; } @Override public boolean isCancelled() { - return this.isCancelled; - } - - long timestamp() { - return this.timestamp; + return this.cancelled; } - void setTimestamp(final long timestamp) { - this.timestamp = timestamp; - } - - /** - * Returns a timestamp after which the next execution will take place. - * Should only be compared to - * {@link SpongeScheduler#timestamp(boolean)}. - * - * @return The next execution timestamp - */ - long nextExecutionTimestamp() { - if (this.state.isActive) { - return this.timestamp + this.task.interval; - } - return this.timestamp + this.task.delay; + @Override + public int compareTo(Delayed other) { + if (other == this) return 0; + return Long.compare( + this.getDelay(TimeUnit.NANOSECONDS), + other.getDelay(TimeUnit.NANOSECONDS)); } - ScheduledTaskState state() { - return this.state; + @Override + public boolean isPeriodic() { + return this.src.interval != 0; } - void setState(ScheduledTaskState state) { - this.state = state; + @Override + public UUID uniqueId() { + return this.uuid; } @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("task", this.task) - .toString(); + public String name() { + return this.name; } - // Internal Task state. Not for user-service use. - public enum ScheduledTaskState { - /** - * Never ran before, waiting for the delay to pass. - */ - WAITING(false), - /** - * In the process of switching to the execution state. - */ - SWITCHING(true), - /** - * Is being executed. - */ - EXECUTING(true), - /** - * Has ran, and will continue to unless removed from the task map. - */ - RUNNING(true), - /** - * Task cancelled, scheduled to be removed from the task map. - */ - CANCELED(false); - - public final boolean isActive; - - ScheduledTaskState(boolean active) { - this.isActive = active; + + private static final VarHandle CANCELLED; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + CANCELLED = l.findVarHandle(SpongeScheduledTask.class, + "cancelled", boolean.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 688ab988910..2b6b7ed39a8 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -24,29 +24,26 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.collect.Sets; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; -import org.spongepowered.common.SpongeCommon; -import org.spongepowered.common.event.tracking.PhaseContext; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.event.tracking.phase.plugin.PluginPhase; +import org.spongepowered.api.scheduler.TaskExecutorService; import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.PluginContainer; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; @@ -56,61 +53,69 @@ import java.util.regex.Pattern; public abstract class SpongeScheduler implements Scheduler { - private static final AtomicInteger TASK_CREATED_COUNTER = new AtomicInteger(); private static final int TICK_DURATION_MS = 50; - static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS.convert(SpongeScheduler.TICK_DURATION_MS, TimeUnit.MILLISECONDS); + static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS + .convert(SpongeScheduler.TICK_DURATION_MS, TimeUnit.MILLISECONDS); private final String tag; - - // The simple queue of all pending (and running) ScheduledTasks - private final ConcurrentMap tasks = new ConcurrentHashMap<>(); + private final ConcurrentMap cachedTasks = + new ConcurrentHashMap<>(); private final AtomicLong sequenceNumber = new AtomicLong(); SpongeScheduler(final String tag) { this.tag = tag; } - /** - * Gets the timestamp to update the timestamp of a task. This method is task - * sensitive to support different timestamp types i.e. real time and ticks. - * - *

Subtracting the result of this method from a previously obtained - * result should become a representation of the time that has passed - * between those calls.

- * - * @param tickBased The task - * @return Timestamp for the task - */ protected long timestamp(final boolean tickBased) { return System.nanoTime(); } - /** - * Adds the task to the task map, will attempt to process the task on the - * next call to {@link #runTick}. - * - * @param task The task to add - */ - protected void addTask(final SpongeScheduledTask task) { - task.setTimestamp(this.timestamp(task.task.tickBasedDelay)); - this.tasks.put(task.uniqueId(), task); + protected abstract BlockingQueue getWorkQueue(); + + @Override + public DelayedRunnable submit(Task task) { + final String name = + task.plugin().metadata().id() + + "-" + + TASK_CREATED_COUNTER.incrementAndGet(); + return this.submit(task, name); } - /** - * Removes the task from the task map. - * - * @param task The task to remove - */ - private void removeTask(final SpongeScheduledTask task) { - this.tasks.remove(task.uniqueId()); + @Override + public DelayedRunnable submit(Task task, String name) { + final long number = this.sequenceNumber.getAndIncrement(); + final SpongeTask sp = (SpongeTask) task; + + final long start = sp.delay < 0 + ? -(timestamp(true) - sp.delay) + : timestamp(false) + sp.delay; + + final UUID uuid = new UUID(number, 0); + final SpongeScheduledTask sc = new SpongeScheduledTask(this, + "%s-%s-#%s".formatted(name, this.tag, number), uuid, + sp, start, + new Cyclic() { + @Override + public void enqueue(DelayedRunnable command) { + getWorkQueue().add(command); + } + + @Override + public void finish() { + cachedTasks.remove(uuid); + } + }); + if (getWorkQueue().add(sc)) + cachedTasks.put(uuid, sc); + return sc; } @Override public Optional findTask(final UUID id) { Objects.requireNonNull(id, "id"); - return Optional.ofNullable(this.tasks.get(id)); + return Optional.ofNullable(this.cachedTasks.get(id)); } @Override @@ -131,7 +136,7 @@ public Set findTasks(final String pattern) { @Override public Set tasks() { - return Sets.newHashSet(this.tasks.values()); + return new HashSet<>(cachedTasks.values()); } @Override @@ -150,176 +155,27 @@ public Set tasks(final PluginContainer plugin) { return allTasks; } - @Override - public SpongeTaskExecutorService executor(final PluginContainer plugin) { + public TaskExecutorService executor(PluginContainer plugin) { Objects.requireNonNull(plugin, "plugin"); return new SpongeTaskExecutorService(() -> Task.builder().plugin(plugin), this); } - @Override - public SpongeScheduledTask submit(final Task task) { - Objects.requireNonNull(task, "task"); - - final String name = task.plugin().metadata().id() + "-" + SpongeScheduler.TASK_CREATED_COUNTER.incrementAndGet(); - return this.submit(task, name); - } - - @Override - public SpongeScheduledTask submit(Task task, String name) { - Objects.requireNonNull(task, "task"); - if (Objects.requireNonNull(name, "name").isEmpty()) { - throw new IllegalArgumentException("Task name cannot empty!"); - } - - final SpongeScheduledTask scheduledTask = new SpongeScheduledTask(this, (SpongeTask) task, - name + "-" + this.tag + "-#" + this.sequenceNumber.getAndIncrement()); - this.addTask(scheduledTask); - return scheduledTask; - } - - /** - * Process all tasks in the map. - */ - final void runTick() { - this.preTick(); - try { - this.tasks.values().forEach(this::processTask); - this.postTick(); - } finally { - this.finallyPostTick(); - } - } - - protected Collection activeTasks() { - return Collections.unmodifiableCollection(this.tasks.values()); - } - - /** - * Fired when the scheduler begins to tick, before any tasks are processed. - */ - protected void preTick() { - } - - /** - * Fired when the scheduler has processed all tasks. - */ - protected void postTick() { - } - - /** - * Fired after tasks have attempted to be processed, in a finally block to - * guarantee execution regardless of any error when processing a task. - */ - protected void finallyPostTick() { - } - - /** - * Processes the task. - * - * @param task The task to process - */ - private void processTask(final SpongeScheduledTask task) { - // If the task is now slated to be cancelled, we just remove it as if it - // no longer exists. - if (task.state() == SpongeScheduledTask.ScheduledTaskState.CANCELED) { - this.removeTask(task); - return; - } - // If the task is already being processed, we wait for the previous - // occurrence to terminate. - if (task.state() == SpongeScheduledTask.ScheduledTaskState.EXECUTING) { - return; - } - final long threshold; - final boolean tickBased; - // Figure out if we start a delayed Task after threshold ticks or, start - // it after the interval (interval) of the repeating task parameter. - if (task.state() == SpongeScheduledTask.ScheduledTaskState.WAITING) { - threshold = task.task.delay; - tickBased = task.task.tickBasedDelay; - } else if (task.state() == SpongeScheduledTask.ScheduledTaskState.RUNNING) { - threshold = task.task.interval; - tickBased = task.task.tickBasedInterval; - } else { - threshold = Long.MAX_VALUE; - tickBased = false; - } - // This moment is 'now' - final long now = this.timestamp(tickBased); - // So, if the current time minus the timestamp of the task is greater - // than the delay to wait before starting the task, then start the task. - // Repeating tasks get a reset-timestamp each time they are set RUNNING - // If the task has a interval of 0 (zero) this task will not repeat, and - // is removed after we start it. - if (threshold <= (now - task.timestamp())) { - task.setState(SpongeScheduledTask.ScheduledTaskState.SWITCHING); - // It is always interval here because that's the only thing that matters - // at this point. - task.setTimestamp(this.timestamp(task.task.tickBasedInterval)); - this.startTask(task); - // If task is one time shot, remove it from the map. - if (task.task.interval == 0L) { - this.removeTask(task); - } - } - } - - /** - * Begin the execution of a task. Exceptions are caught and logged. - * - * @param task The task to start - */ - private void startTask(final SpongeScheduledTask task) { - this.executeRunnable(() -> { - task.setState(SpongeScheduledTask.ScheduledTaskState.EXECUTING); - try (final @Nullable PhaseContext<@NonNull ?> context = this.createContext(task, task.task().plugin())) { - if (context != null) { - context.buildAndSwitch(); - } - try { - task.task.executor().accept(task); - } catch (final Throwable t) { - SpongeCommon.logger().error("The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", - task.name(), task.task().plugin().metadata().id(), t); - } - } finally { - if (!task.isCancelled()) { - task.setState(SpongeScheduledTask.ScheduledTaskState.RUNNING); - } - this.onTaskCompletion(task); - } - }); - } - - protected @Nullable PhaseContext createContext(final SpongeScheduledTask task, final PluginContainer plugin) { - return PluginPhase.State.SCHEDULED_TASK.createPhaseContext(PhaseTracker.getInstance()) - .source(task) - .container(plugin); - } - - /** - * Run when a task has completed and is switching into - * the {@link SpongeScheduledTask.ScheduledTaskState#RUNNING} state - */ - protected void onTaskCompletion(final SpongeScheduledTask task) { - // no-op for sync methods. - } - - protected void executeRunnable(final Runnable runnable) { - runnable.run(); + protected Collection activeTasks() { + return Collections.unmodifiableCollection(this.cachedTasks.values()); } public Future execute(final Callable callable) { final FutureTask runnable = new FutureTask<>(callable); - this.submit(new SpongeTask.BuilderImpl().execute(runnable).plugin(Launch.instance().commonPlugin()).build()); + this.submit(new SpongeTask.BuilderImpl() + .execute(runnable) + .plugin(Launch.instance().commonPlugin()) + .build()); return runnable; } public Future execute(final Runnable runnable) { - return this.execute(() -> { - runnable.run(); - return null; - }); + return this.execute(Executors.callable(runnable)); } + } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index 39e090bfaa1..e8a6657391a 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -37,24 +37,21 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -public final class SpongeTask implements Task { +public class SpongeTask implements Task { private final PluginContainer plugin; private final Consumer executor; final long delay; // nanos final long interval; // nanos - final boolean tickBasedDelay; - final boolean tickBasedInterval; - SpongeTask(final PluginContainer plugin, final Consumer executor, final long delay, - final long interval, final boolean tickBasedDelay, final boolean tickBasedInterval) { + SpongeTask(final PluginContainer plugin, + final Consumer executor, + final long delay, final long interval) { this.plugin = plugin; this.executor = executor; this.delay = delay; this.interval = interval; - this.tickBasedDelay = tickBasedDelay; - this.tickBasedInterval = tickBasedInterval; } @Override @@ -64,12 +61,12 @@ public PluginContainer plugin() { @Override public Duration delay() { - return Duration.ofNanos(this.delay); + return Duration.ofNanos(Math.abs(this.delay)); } @Override public Duration interval() { - return Duration.ofNanos(this.interval); + return Duration.ofNanos(Math.abs(this.interval)); } public Consumer executor() { @@ -92,8 +89,6 @@ public static final class BuilderImpl implements Task.Builder { private long delay; private long interval; - private boolean tickBasedDelay; - private boolean tickBasedInterval; @Override public Task.Builder execute(final Consumer executor) { @@ -108,7 +103,6 @@ public Task.Builder delay(final long delay, final TemporalUnit unit) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } this.delay = Objects.requireNonNull(unit, "unit").getDuration().toNanos() * delay; - this.tickBasedDelay = false; return this; } @@ -119,7 +113,6 @@ public Task.Builder delay(final long delay, final TimeUnit unit) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } this.delay = Objects.requireNonNull(unit, "unit").toNanos(delay); - this.tickBasedDelay = false; return this; } @@ -129,22 +122,19 @@ public Task.Builder delay(final Ticks delay) { if (delay.ticks() < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = delay.ticks() * SpongeScheduler.TICK_DURATION_NS; - this.tickBasedDelay = true; + this.delay = -delay.ticks() * SpongeScheduler.TICK_DURATION_NS; return this; } @Override public Task.Builder delay(final Duration delay) { this.delay = Objects.requireNonNull(delay, "delay").toNanos(); - this.tickBasedDelay = false; return this; } @Override public Task.Builder interval(final Duration interval) { this.interval = Objects.requireNonNull(interval, "interval").toNanos(); - this.tickBasedInterval = false; return this; } @@ -154,7 +144,6 @@ public Task.Builder interval(final long delay, final TemporalUnit unit) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } this.interval = Objects.requireNonNull(unit, "unit").getDuration().toNanos() * delay; - this.tickBasedInterval = false; return this; } @@ -164,7 +153,6 @@ public Task.Builder interval(final long interval, final TimeUnit unit) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } this.interval = Objects.requireNonNull(unit, "unit").toNanos(interval); - this.tickBasedInterval = false; return this; } @@ -174,8 +162,7 @@ public Task.Builder interval(final Ticks interval) { if (interval.ticks() < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = interval.ticks() * SpongeScheduler.TICK_DURATION_NS; - this.tickBasedInterval = true; + this.interval = -(interval.ticks() * SpongeScheduler.TICK_DURATION_NS); return this; } @@ -193,8 +180,6 @@ public Task.Builder from(final Task value) { this.plugin = task.plugin(); this.interval = task.interval; this.delay = task.delay; - this.tickBasedDelay = task.tickBasedDelay; - this.tickBasedInterval = task.tickBasedInterval; return this; } @@ -204,8 +189,6 @@ public Task.Builder reset() { this.plugin = null; this.interval = 0; this.delay = 0; - this.tickBasedDelay = false; - this.tickBasedInterval = false; return this; } @@ -214,7 +197,7 @@ public Task build() { Objects.requireNonNull(this.executor, "executor"); Objects.requireNonNull(this.plugin, "plugin"); - return new SpongeTask(this.plugin, this.executor, this.delay, this.interval, this.tickBasedDelay, this.tickBasedInterval); + return new SpongeTask(this.plugin, this.executor, this.delay, this.interval); } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java index 434e8f616c1..458bb3649f4 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java @@ -24,16 +24,15 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.Nullable; + import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.ScheduledTaskFuture; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.scheduler.TaskExecutorService; import org.spongepowered.api.scheduler.TaskFuture; -import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.List; import java.util.concurrent.AbstractExecutorService; @@ -102,7 +101,7 @@ public TaskFuture submit(final Runnable command, final @Nullable T result final FutureTask runnable = new FutureTask<>(command, result); final Task task = this.createTask(runnable) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -110,7 +109,7 @@ public TaskFuture submit(final Callable command) { final FutureTask runnable = new FutureTask<>(command); final Task task = this.createTask(runnable) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -119,7 +118,7 @@ public ScheduledTaskFuture schedule(final Runnable command, final long delay, final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -128,7 +127,7 @@ public ScheduledTaskFuture schedule(final Runnable command, final long delay, final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -137,7 +136,7 @@ public ScheduledTaskFuture schedule(final Callable callable, final lon final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -146,7 +145,7 @@ public ScheduledTaskFuture schedule(final Callable callable, final lon final Task task = this.createTask(runnable) .delay(delay, unit) .build(); - return new SpongeScheduledFuture<>(runnable, this.submitTask(task), this.scheduler); + return new SpongeScheduledFuture<>(runnable, this.submitTask(task)); } @Override @@ -156,10 +155,10 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final SpongeScheduledTask scheduledTask = this.submitTask(task); + final DelayedRunnable scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); - return new SpongeScheduledFuture<>(runnable, scheduledTask, this.scheduler); + return new SpongeScheduledFuture<>(runnable, scheduledTask); } @Override @@ -169,10 +168,10 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final SpongeScheduledTask scheduledTask = this.submitTask(task); + final DelayedRunnable scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); - return new SpongeScheduledFuture<>(runnable, scheduledTask, this.scheduler); + return new SpongeScheduledFuture<>(runnable, scheduledTask); } @Override @@ -191,20 +190,19 @@ private Task.Builder createTask(final Runnable command) { return this.taskBuilderProvider.get().execute(command); } - private SpongeScheduledTask submitTask(final Task task) { + private DelayedRunnable submitTask(final Task task) { return this.scheduler.submit(task); } private static class SpongeScheduledFuture implements org.spongepowered.api.scheduler.ScheduledTaskFuture { private final FutureTask runnable; - private final SpongeScheduledTask task; - private final SpongeScheduler scheduler; + private final DelayedRunnable task; - SpongeScheduledFuture(final FutureTask runnable, final SpongeScheduledTask task, final SpongeScheduler scheduler) { + SpongeScheduledFuture(final FutureTask runnable, + final DelayedRunnable task) { this.runnable = runnable; this.task = task; - this.scheduler = scheduler; } @Override @@ -214,38 +212,17 @@ public ScheduledTask task() { @Override public boolean isPeriodic() { - final Duration interval = this.task.task.interval(); - return interval.toMillis() > 0; + return this.task.isPeriodic(); } @Override public long getDelay(final TimeUnit unit) { - // Since these tasks are scheduled through - // SchedulerExecutionService, they are - // always nanotime-based, not tick-based. - return unit.convert(this.task.nextExecutionTimestamp() - this.scheduler.timestamp(false), TimeUnit.NANOSECONDS); + return this.task.getDelay(unit); } - @SuppressWarnings("rawtypes") @Override public int compareTo(final Delayed other) { - // Since delay may return different values for each call, - // this check is required to correctly implement Comparable - if (other == this) { - return 0; - } - - // If we are considering other sponge tasks, we can order by - // their internal tasks - if (other instanceof SpongeScheduledFuture) { - final SpongeScheduledTask otherTask = ((SpongeScheduledFuture) other).task; - return ComparisonChain.start() - .compare(this.task.nextExecutionTimestamp(), otherTask.nextExecutionTimestamp()) - .compare(this.task.uniqueId(), otherTask.uniqueId()) - .result(); - } - - return Long.compare(this.getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS)); + return this.task.compareTo(other); } @Override @@ -255,7 +232,7 @@ public void run() { @Override public boolean cancel(final boolean mayInterruptIfRunning) { - this.task.cancel(); //Ensure Sponge is not going to try to run a cancelled task. + this.task.cancel(); // Ensure Sponge is not going to try to run a cancelled task. return this.runnable.cancel(mayInterruptIfRunning); } @@ -264,7 +241,7 @@ public boolean isCancelled() { // It might be externally cancelled, the runnable would never // run in that case return this.runnable.isCancelled() || - (this.task.state() == SpongeScheduledTask.ScheduledTaskState.CANCELED && !this.runnable.isDone()); + (this.task.isCancelled() && !this.runnable.isDone()); } @Override diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 6d42c6c7ad9..4454c940417 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -24,30 +24,35 @@ */ package org.spongepowered.common.scheduler; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.DelayQueue; + public abstract class SyncScheduler extends SpongeScheduler { - // The number of ticks elapsed since this scheduler began. - private long counter = 0L; + private final DelayQueue workQueue = new DelayQueue<>(); + private volatile long timestamp; SyncScheduler(final String tag) { super(tag); } + @Override + protected BlockingQueue getWorkQueue() { + return workQueue; + } + /** * The hook to update the Ticks known by the SyncScheduler. */ public void tick() { - this.counter++; - this.runTick(); + this.timestamp = System.nanoTime(); + for (Runnable task; (task = this.workQueue.poll()) != null; task.run()); } @Override protected long timestamp(final boolean tickBased) { // The task is based on minecraft ticks, so we generate // a timestamp based on the elapsed ticks - if (tickBased) { - return this.counter * SpongeScheduler.TICK_DURATION_NS; - } - return super.timestamp(false); + return tickBased ? this.timestamp : super.timestamp(false); } } From 4eef5a81e26fa1cfaafbc3e8d8f1052c0f7e2646 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Fri, 1 Mar 2024 15:56:04 +0300 Subject: [PATCH 04/14] file licensed --- .../common/scheduler/SpongeScheduledTask.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java index 322f4a2a76a..a928d0daf04 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java @@ -1,3 +1,27 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package org.spongepowered.common.scheduler; import org.checkerframework.checker.nullness.qual.NonNull; From 1171a667c336f38d9899098dd7fc6b1023f9bb89 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Fri, 1 Mar 2024 16:26:30 +0300 Subject: [PATCH 05/14] format fixed --- .../common/scheduler/Cyclic.java | 24 +++++++++++++++++++ .../scheduler/DelayQueueAsRunnable.java | 24 +++++++++++++++++++ .../common/scheduler/DelayedRunnable.java | 24 +++++++++++++++++++ .../common/scheduler/SpongeScheduledTask.java | 4 ++-- .../common/scheduler/SpongeScheduler.java | 5 ++-- .../scheduler/SpongeTaskExecutorService.java | 1 - 6 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/Cyclic.java b/src/main/java/org/spongepowered/common/scheduler/Cyclic.java index 51bae270cf2..175a97e98b9 100644 --- a/src/main/java/org/spongepowered/common/scheduler/Cyclic.java +++ b/src/main/java/org/spongepowered/common/scheduler/Cyclic.java @@ -1,3 +1,27 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package org.spongepowered.common.scheduler; public interface Cyclic { diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java index b8c5a045403..0357dee209b 100644 --- a/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java +++ b/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java @@ -1,3 +1,27 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package org.spongepowered.common.scheduler; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java index ae47180fde9..52bcab7ae3c 100644 --- a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java +++ b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java @@ -1,3 +1,27 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package org.spongepowered.common.scheduler; import org.spongepowered.api.scheduler.ScheduledTask; diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java index a928d0daf04..20a9946645e 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java @@ -24,6 +24,8 @@ */ package org.spongepowered.common.scheduler; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; @@ -40,8 +42,6 @@ import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - public class SpongeScheduledTask implements ScheduledTask, DelayedRunnable { private final SpongeScheduler scheduler; private final String name; diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 2b6b7ed39a8..1ec97f6fa9b 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -47,13 +47,12 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; public abstract class SpongeScheduler implements Scheduler { - private static final AtomicInteger TASK_CREATED_COUNTER = new AtomicInteger(); + private static final AtomicLong TASK_CREATED_COUNTER = new AtomicLong(); private static final int TICK_DURATION_MS = 50; static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS @@ -92,7 +91,7 @@ public DelayedRunnable submit(Task task, String name) { ? -(timestamp(true) - sp.delay) : timestamp(false) + sp.delay; - final UUID uuid = new UUID(number, 0); + final UUID uuid = new UUID(number, System.identityHashCode(this)); final SpongeScheduledTask sc = new SpongeScheduledTask(this, "%s-%s-#%s".formatted(name, this.tag, number), uuid, sp, start, diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java index 458bb3649f4..817e74517f5 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java @@ -26,7 +26,6 @@ import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.Nullable; - import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.ScheduledTaskFuture; import org.spongepowered.api.scheduler.Task; From 2f5172ef51cb820dc886f67519211218a40fb02b Mon Sep 17 00:00:00 2001 From: sunmisc Date: Mon, 4 Mar 2024 14:08:34 +0300 Subject: [PATCH 06/14] scheduler updated --- .../common/scheduler/AsyncScheduler.java | 219 +++++++++++++++-- .../scheduler/DelayQueueAsRunnable.java | 224 ------------------ .../common/scheduler/DelayedRunnable.java | 32 ++- .../common/scheduler/SpongeScheduledTask.java | 59 ++--- .../common/scheduler/SpongeScheduler.java | 63 ++--- .../common/scheduler/SpongeTask.java | 35 +-- .../scheduler/SpongeTaskExecutorService.java | 20 +- .../common/scheduler/SyncScheduler.java | 17 +- .../common/scheduler/TaskExecutor.java | 51 ++++ 9 files changed, 371 insertions(+), 349 deletions(-) delete mode 100644 src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java create mode 100644 src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index c1dc952dc96..c77050d5d8e 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -26,31 +26,30 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.logging.log4j.Level; +import org.jetbrains.annotations.NotNull; import org.spongepowered.api.scheduler.Task; import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.*; +import java.util.function.Consumer; +import java.util.function.IntFunction; public final class AsyncScheduler extends SpongeScheduler implements AutoCloseable { - private static final long KEEP_ALIVE_MILLIS = 12L; + private static final int NCPU = Runtime.getRuntime().availableProcessors(); + private static final long KEEP_ALIVE_MILLIS = 10L; private final ThreadPoolExecutor executor; private final BlockingQueue workQueue = new DelayQueue<>(); public AsyncScheduler() { super("A"); - this.executor = new ThreadPoolExecutor(1, Integer.MAX_VALUE, + this.executor = new ThreadPoolExecutor(NCPU, Integer.MAX_VALUE, KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS, - new DelayQueueAsRunnable<>(workQueue), + new DelayQueueAsRunnable(workQueue), new ThreadFactoryBuilder() .setNameFormat("Sponge-AsyncScheduler-%d") .build() @@ -63,9 +62,10 @@ protected BlockingQueue getWorkQueue() { } @Override - public DelayedRunnable submit(Task task) { + public SpongeScheduledTask submit(Task task) { + SpongeScheduledTask sst = super.submit(task); this.executor.prestartCoreThread(); - return super.submit(task); + return sst; } public CompletableFuture submit(final Callable callable) { @@ -77,7 +77,7 @@ private CompletableFuture asyncFailableFuture(Callable call, Executor exec.execute(() -> { try { ret.complete(call.call()); - } catch (Throwable e) { + } catch (final Throwable e) { ret.completeExceptionally(e); } }); @@ -104,6 +104,195 @@ public void close() { } } catch (InterruptedException e) { SpongeCommon.logger().error("The async scheduler was interrupted while awaiting shutdown!"); + + Thread.currentThread().interrupt(); + } + } + + private record DelayQueueAsRunnable( + BlockingQueue src + ) implements BlockingQueue { + + @Override + public Runnable poll() { + return this.src.poll(); + } + + @Override + public boolean add(final Runnable runnable) { + return this.src.add( + new DelayedRunnable.NoDelayRunnable(runnable)); + } + + @Override + public boolean offer(final Runnable runnable) { + return this.src.offer( + new DelayedRunnable.NoDelayRunnable(runnable)); + } + + @Override + public void put(final Runnable runnable) throws InterruptedException { + this.src.put(new DelayedRunnable.NoDelayRunnable(runnable)); + } + + @Override + public boolean offer(final Runnable runnable, + final long timeout, + final TimeUnit unit + ) throws InterruptedException { + return this.src.offer( + new DelayedRunnable.NoDelayRunnable(runnable), + timeout, unit); + } + + @Override + public @NotNull Runnable take() throws InterruptedException { + return this.src.take(); + } + + @Override + public Runnable poll(final long timeout, + final TimeUnit unit + ) throws InterruptedException { + return this.src.poll(timeout, unit); + } + + @Override + public Runnable remove() { + return this.src.remove(); + } + + @Override + public Runnable peek() { + return this.src.peek(); + } + + @Override + public int size() { + return this.src.size(); + } + + @Override + public void clear() { + this.src.clear(); + } + + @Override + public int remainingCapacity() { + return this.src.remainingCapacity(); + } + + @Override + public boolean remove(final Object o) { + return this.src.remove(o); + } + + @Override + public DelayedRunnable element() { + return this.src.element(); + } + + @Override + public boolean isEmpty() { + return this.src.isEmpty(); + } + + @Override + public boolean contains(final Object o) { + return this.src.contains(o); + } + + @Override + public int drainTo(final Collection c) { + return this.src.drainTo(c); + } + + @Override + public int drainTo(final Collection c, + final int maxElements) { + return this.src.drainTo(c, maxElements); + } + + @Override + public boolean containsAll(final Collection c) { + return this.src.containsAll(c); + } + + @Override + public boolean addAll(final Collection c) { + Objects.requireNonNull(c); + if (c == this) + throw new IllegalArgumentException(); + boolean modified = false; + for (Runnable e : c) + if (add(e)) + modified = true; + return modified; + } + + @Override + public boolean removeAll(final Collection c) { + return this.src.removeAll(c); + } + + @Override + public boolean retainAll(final Collection c) { + return this.src.retainAll(c); + } + + @Override + public Object[] toArray() { + return this.src.toArray(); + } + + @Override + public T[] toArray(final T[] a) { + return this.src.toArray(a); + } + + @Override + public T[] toArray(final IntFunction generator) { + return this.src.toArray(generator); + } + + @Override + public Iterator iterator() { + return new Itr<>(this.src.iterator()); + } + + @Override + public boolean equals(final Object o) { + return this.src.equals(o); + } + + @Override + public String toString() { + return this.src.toString(); + } + + private record Itr( + Iterator src + ) implements Iterator { + + @Override + public boolean hasNext() { + return this.src.hasNext(); + } + + @Override + public Runnable next() { + return this.src.next(); + } + + @Override + public void remove() { + this.src.remove(); + } + + @Override + public void forEachRemaining(final Consumer action) { + this.src.forEachRemaining(action); + } } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java deleted file mode 100644 index 0357dee209b..00000000000 --- a/src/main/java/org/spongepowered/common/scheduler/DelayQueueAsRunnable.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.scheduler; - -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Objects; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Delayed; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.IntFunction; - -@SuppressWarnings("unchecked") -public class DelayQueueAsRunnable - implements BlockingQueue { - - private final BlockingQueue delayQueue; - - public DelayQueueAsRunnable(BlockingQueue delayQueue) { - this.delayQueue = delayQueue; - } - @Override - public Runnable poll() { - return this.delayQueue.poll(); - } - - @Override - public boolean add(Runnable runnable) { - return this.delayQueue.add((E) runnable); - } - - @Override - public boolean offer(Runnable runnable) { - return this.delayQueue.offer((E) runnable); - } - - @Override - public void put(Runnable runnable) throws InterruptedException { - this.delayQueue.put((E) runnable); - } - - @Override - public boolean offer(Runnable runnable, long timeout, TimeUnit unit) throws InterruptedException { - return this.delayQueue.offer((E)runnable, timeout, unit); - } - - @Override - public @NotNull E take() throws InterruptedException { - return this.delayQueue.take(); - } - - @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { - return this.delayQueue.poll(timeout, unit); - } - - @Override - public E remove() { - return this.delayQueue.remove(); - } - - @Override - public E peek() { - return this.delayQueue.peek(); - } - - @Override - public int size() { - return this.delayQueue.size(); - } - - @Override - public void clear() { - this.delayQueue.clear(); - } - - @Override - public int remainingCapacity() { - return this.delayQueue.remainingCapacity(); - } - - @Override - public boolean remove(Object o) { - return this.delayQueue.remove(o); - } - - @Override - public E element() { - return this.delayQueue.element(); - } - - @Override - public boolean isEmpty() { - return this.delayQueue.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return this.delayQueue.contains(o); - } - - - @Override - public int drainTo(Collection c) { - return this.delayQueue.drainTo(c); - } - - @Override - public int drainTo(Collection c, int maxElements) { - return this.delayQueue.drainTo(c, maxElements); - } - - @Override - public boolean containsAll(Collection c) { - return this.delayQueue.containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - Objects.requireNonNull(c); - if (c == this) - throw new IllegalArgumentException(); - boolean modified = false; - for (Runnable e : c) - if (add(e)) - modified = true; - return modified; - } - - @Override - public boolean removeAll(Collection c) { - return this.delayQueue.removeAll(c); - } - - @Override - public boolean retainAll(Collection c) { - return this.delayQueue.retainAll(c); - } - - @Override - public Object[] toArray() { - return this.delayQueue.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return this.delayQueue.toArray(a); - } - @Override - public T[] toArray(IntFunction generator) { - return this.delayQueue.toArray(generator); - } - - @Override - public Iterator iterator() { - return new Itr<>(this.delayQueue.iterator()); - } - - @Override - public boolean equals(Object o) { - return this.delayQueue.equals(o); - } - @Override - public int hashCode() { - return this.delayQueue.hashCode(); - } - - @Override - public String toString() { - return this.delayQueue.toString(); - } - - - private record Itr( - Iterator src - ) implements Iterator { - - @Override - public boolean hasNext() { - return this.src.hasNext(); - } - - @Override - public Runnable next() { - return this.src.next(); - } - - @Override - public void remove() { - this.src.remove(); - } - - @Override - public void forEachRemaining(Consumer action) { - this.src.forEachRemaining(action); - } - - } -} diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java index 52bcab7ae3c..d3b4bb8f299 100644 --- a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java +++ b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java @@ -24,10 +24,38 @@ */ package org.spongepowered.common.scheduler; -import org.spongepowered.api.scheduler.ScheduledTask; +import org.jetbrains.annotations.NotNull; import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +public interface DelayedRunnable extends Delayed, Runnable { -public interface DelayedRunnable extends Delayed, Runnable, ScheduledTask { boolean isPeriodic(); + + + @Override + default int compareTo(@NotNull final Delayed other) { + return other == this ? 0 : Long.compare( + this.getDelay(TimeUnit.NANOSECONDS), + other.getDelay(TimeUnit.NANOSECONDS) + ); + } + record NoDelayRunnable(Runnable origin) implements DelayedRunnable { + + @Override + public void run() { + this.origin.run(); + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return 0; + } + + @Override + public boolean isPeriodic() { + return false; + } + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java index 20a9946645e..2ff280a6145 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java @@ -27,7 +27,6 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; @@ -39,26 +38,20 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.UUID; -import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; -public class SpongeScheduledTask implements ScheduledTask, DelayedRunnable { - private final SpongeScheduler scheduler; +public final class SpongeScheduledTask implements ScheduledTask, DelayedRunnable { + private final Scheduler scheduler; private final String name; private final UUID uuid; - private final SpongeTask src; + private final TaskExecutor src; private volatile boolean cancelled = false; - - /* - * Negative value - time in ticks - * This is done for atomic access of the getDelay() method - */ private volatile long time; private final Cyclic cyclic; - SpongeScheduledTask(final SpongeScheduler scheduler, + SpongeScheduledTask(final Scheduler scheduler, final String name, final UUID uuid, - final SpongeTask src, + final TaskExecutor src, final long start, final Cyclic cyclic) { this.scheduler = scheduler; @@ -72,36 +65,36 @@ public class SpongeScheduledTask implements ScheduledTask, DelayedRunnable { public void run() { if (this.isCancelled()) return; - final SpongeTask x = this.src; - - try (final @Nullable PhaseContext<@NonNull ?> context = PluginPhase.State.SCHEDULED_TASK + final TaskExecutor x = this.src; + try (final PhaseContext<@NonNull ?> context = PluginPhase.State.SCHEDULED_TASK .createPhaseContext(PhaseTracker.getInstance()) .source(this) .container(x.plugin())) { context.buildAndSwitch(); try { - x.executor().accept(this); + x.accept(this); } catch (final Throwable t) { SpongeCommon.logger().error("The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", name, x.plugin().metadata().id(), t); } - } - - if (isPeriodic()) { - final long q = x.interval; - this.time = q < 0 - ? -(this.scheduler.timestamp(true) - q) - : this.scheduler.timestamp(false) + q; - this.cyclic.enqueue(this); - } else { - this.cyclic.finish(); + } finally { + if (isPeriodic()) { + final long q = x.intervalNanos(); + if (x.tickBased()) + this.time += q; + else + this.time = System.nanoTime() + q; + this.cyclic.enqueue(this); + } else { + this.cyclic.finish(); + } } } @Override public long getDelay(TimeUnit unit) { - final long p = this.time, now = this.scheduler.timestamp(p < 0); - return unit.convert(Math.abs(p) - now, NANOSECONDS); + return unit.convert( + this.time - System.nanoTime(), NANOSECONDS); } @Override @@ -128,17 +121,9 @@ public boolean isCancelled() { return this.cancelled; } - @Override - public int compareTo(Delayed other) { - if (other == this) return 0; - return Long.compare( - this.getDelay(TimeUnit.NANOSECONDS), - other.getDelay(TimeUnit.NANOSECONDS)); - } - @Override public boolean isPeriodic() { - return this.src.interval != 0; + return this.src.intervalNanos() != 0; } @Override diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 1ec97f6fa9b..fd29f8ba2e1 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -34,7 +34,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -48,8 +47,8 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public abstract class SpongeScheduler implements Scheduler { private static final AtomicLong TASK_CREATED_COUNTER = new AtomicLong(); @@ -67,29 +66,23 @@ public abstract class SpongeScheduler implements Scheduler { this.tag = tag; } - protected long timestamp(final boolean tickBased) { - return System.nanoTime(); - } - protected abstract BlockingQueue getWorkQueue(); @Override - public DelayedRunnable submit(Task task) { + public SpongeScheduledTask submit(Task task) { final String name = task.plugin().metadata().id() + - "-" + - TASK_CREATED_COUNTER.incrementAndGet(); + "-" + + TASK_CREATED_COUNTER.incrementAndGet(); return this.submit(task, name); } @Override - public DelayedRunnable submit(Task task, String name) { + public SpongeScheduledTask submit(Task task, String name) { final long number = this.sequenceNumber.getAndIncrement(); final SpongeTask sp = (SpongeTask) task; - final long start = sp.delay < 0 - ? -(timestamp(true) - sp.delay) - : timestamp(false) + sp.delay; + final long start = System.nanoTime() + sp.delayNanos(); final UUID uuid = new UUID(number, System.identityHashCode(this)); final SpongeScheduledTask sc = new SpongeScheduledTask(this, @@ -119,18 +112,15 @@ public Optional findTask(final UUID id) { @Override public Set findTasks(final String pattern) { - final Pattern searchPattern = Pattern.compile(Objects.requireNonNull(pattern, "pattern")); - final Set matchingTasks = this.tasks(); - - final Iterator it = matchingTasks.iterator(); - while (it.hasNext()) { - final Matcher matcher = searchPattern.matcher(it.next().name()); - if (!matcher.matches()) { - it.remove(); - } - } - - return matchingTasks; + final Pattern searchPattern = Pattern.compile( + Objects.requireNonNull(pattern, "pattern")); + + return cachedTasks + .values() + .stream() + .filter(x -> { + return searchPattern.matcher(x.name()).matches(); + }).collect(Collectors.toSet()); } @Override @@ -140,19 +130,16 @@ public Set tasks() { @Override public Set tasks(final PluginContainer plugin) { - final String testOwnerId = Objects.requireNonNull(plugin, "plugin").metadata().id(); - - final Set allTasks = this.tasks(); - final Iterator it = allTasks.iterator(); - - while (it.hasNext()) { - final String taskOwnerId = it.next().task().plugin().metadata().id(); - if (!testOwnerId.equals(taskOwnerId)) { - it.remove(); - } - } - - return allTasks; + final String testOwnerId = Objects + .requireNonNull(plugin, "plugin") + .metadata().id(); + return cachedTasks + .values() + .stream() + .filter(x -> { + final String taskOwnerId = x.task().plugin().metadata().id(); + return testOwnerId.equals(taskOwnerId); + }).collect(Collectors.toSet()); } @Override public TaskExecutorService executor(PluginContainer plugin) { diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index e8a6657391a..3626a43852d 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -37,13 +37,13 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -public class SpongeTask implements Task { +public final class SpongeTask implements TaskExecutor { private final PluginContainer plugin; private final Consumer executor; - final long delay; // nanos - final long interval; // nanos + private final long delay; // nanos + private final long interval; // nanos SpongeTask(final PluginContainer plugin, final Consumer executor, @@ -60,28 +60,36 @@ public PluginContainer plugin() { } @Override - public Duration delay() { - return Duration.ofNanos(Math.abs(this.delay)); + public long delayNanos() { + return this.delay; } @Override - public Duration interval() { - return Duration.ofNanos(Math.abs(this.interval)); + public long intervalNanos() { + return Math.abs(this.interval); } - public Consumer executor() { - return this.executor; + @Override + public void accept(ScheduledTask scheduledTask) { + this.executor.accept(scheduledTask); + } + + @Override + public boolean tickBased() { + return this.interval < 0; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("plugin", this.plugin.metadata().id()) - .add("delay", this.delay) - .add("interval", this.interval) + .add("delay", this.delay().toNanos()) + .add("interval", this.interval().toNanos()) .toString(); } + + public static final class BuilderImpl implements Task.Builder { private @Nullable Consumer executor; @@ -122,7 +130,7 @@ public Task.Builder delay(final Ticks delay) { if (delay.ticks() < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = -delay.ticks() * SpongeScheduler.TICK_DURATION_NS; + this.delay = delay.ticks() * SpongeScheduler.TICK_DURATION_NS; return this; } @@ -162,7 +170,7 @@ public Task.Builder interval(final Ticks interval) { if (interval.ticks() < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = -(interval.ticks() * SpongeScheduler.TICK_DURATION_NS); + this.interval = -interval.ticks() * SpongeScheduler.TICK_DURATION_NS; return this; } @@ -174,7 +182,6 @@ public Task.Builder plugin(final PluginContainer plugin) { @Override public Task.Builder from(final Task value) { - final SpongeTask task = (SpongeTask) Objects.requireNonNull(value, "value"); this.executor = task.executor; this.plugin = task.plugin(); diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java index 817e74517f5..151f4e2499e 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java @@ -24,7 +24,6 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.ScheduledTaskFuture; @@ -43,7 +42,9 @@ import java.util.concurrent.TimeoutException; import java.util.function.Supplier; -class SpongeTaskExecutorService extends AbstractExecutorService implements TaskExecutorService { +class SpongeTaskExecutorService + extends AbstractExecutorService + implements TaskExecutorService { private final Supplier taskBuilderProvider; private final SpongeScheduler scheduler; @@ -67,7 +68,7 @@ public void shutdown() { @Override public List shutdownNow() { - return ImmutableList.of(); + return List.of(); } @Override @@ -154,7 +155,7 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final DelayedRunnable scheduledTask = this.submitTask(task); + final SpongeScheduledTask scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); return new SpongeScheduledFuture<>(runnable, scheduledTask); @@ -167,7 +168,7 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final DelayedRunnable scheduledTask = this.submitTask(task); + final SpongeScheduledTask scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); return new SpongeScheduledFuture<>(runnable, scheduledTask); @@ -189,17 +190,17 @@ private Task.Builder createTask(final Runnable command) { return this.taskBuilderProvider.get().execute(command); } - private DelayedRunnable submitTask(final Task task) { + private SpongeScheduledTask submitTask(final Task task) { return this.scheduler.submit(task); } private static class SpongeScheduledFuture implements org.spongepowered.api.scheduler.ScheduledTaskFuture { private final FutureTask runnable; - private final DelayedRunnable task; + private final SpongeScheduledTask task; SpongeScheduledFuture(final FutureTask runnable, - final DelayedRunnable task) { + final SpongeScheduledTask task) { this.runnable = runnable; this.task = task; } @@ -250,12 +251,15 @@ public boolean isDone() { @Override public V get() throws InterruptedException, ExecutionException { + System.out.println("LOOOCK"); return this.runnable.get(); } @Override public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + System.out.println("LCOK TIMEOUR"); return this.runnable.get(timeout, unit); + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 4454c940417..b64b8733c79 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -24,13 +24,14 @@ */ package org.spongepowered.common.scheduler; + import java.util.concurrent.BlockingQueue; import java.util.concurrent.DelayQueue; public abstract class SyncScheduler extends SpongeScheduler { - private final DelayQueue workQueue = new DelayQueue<>(); - private volatile long timestamp; + private final BlockingQueue workQueue + = new DelayQueue<>(); SyncScheduler(final String tag) { super(tag); @@ -45,14 +46,8 @@ protected BlockingQueue getWorkQueue() { * The hook to update the Ticks known by the SyncScheduler. */ public void tick() { - this.timestamp = System.nanoTime(); - for (Runnable task; (task = this.workQueue.poll()) != null; task.run()); - } - - @Override - protected long timestamp(final boolean tickBased) { - // The task is based on minecraft ticks, so we generate - // a timestamp based on the elapsed ticks - return tickBased ? this.timestamp : super.timestamp(false); + for (Runnable task; + (task = this.workQueue.poll()) != null; + task.run()); } } diff --git a/src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java b/src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java new file mode 100644 index 00000000000..0498f5ce829 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java @@ -0,0 +1,51 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import org.spongepowered.api.scheduler.ScheduledTask; +import org.spongepowered.api.scheduler.Task; + +import java.time.Duration; +import java.util.function.Consumer; + +public interface TaskExecutor + extends Consumer, Task { + + @Override + default Duration delay() { + return Duration.ofNanos(this.delayNanos()); + } + + @Override + default Duration interval() { + return Duration.ofNanos(this.intervalNanos()); + } + + long delayNanos(); + + long intervalNanos(); + + boolean tickBased(); +} From 3e94b73b981df481a86bd4a555464beb899f54d3 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Tue, 5 Mar 2024 18:52:57 +0300 Subject: [PATCH 07/14] cleanup --- ...Cyclic.java => AbstractScheduledTask.java} | 8 +- .../common/scheduler/AbstractScheduler.java | 39 +++++++++ .../common/scheduler/AsyncScheduler.java | 43 +++++----- .../common/scheduler/DelayedRunnable.java | 7 -- .../common/scheduler/SpongeScheduledTask.java | 70 ++++++---------- .../common/scheduler/SpongeScheduler.java | 73 ++++++----------- .../common/scheduler/SpongeTask.java | 79 +++++++++++-------- .../scheduler/SpongeTaskExecutorService.java | 30 +++---- .../common/scheduler/SyncScheduler.java | 9 ++- .../{TaskExecutor.java => TaskProcedure.java} | 21 +---- 10 files changed, 177 insertions(+), 202 deletions(-) rename src/main/java/org/spongepowered/common/scheduler/{Cyclic.java => AbstractScheduledTask.java} (88%) create mode 100644 src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java rename src/main/java/org/spongepowered/common/scheduler/{TaskExecutor.java => TaskProcedure.java} (76%) diff --git a/src/main/java/org/spongepowered/common/scheduler/Cyclic.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java similarity index 88% rename from src/main/java/org/spongepowered/common/scheduler/Cyclic.java rename to src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java index 175a97e98b9..fb28f7ec460 100644 --- a/src/main/java/org/spongepowered/common/scheduler/Cyclic.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java @@ -24,10 +24,10 @@ */ package org.spongepowered.common.scheduler; -public interface Cyclic { +import org.spongepowered.api.scheduler.ScheduledTask; - void enqueue(DelayedRunnable command); +public interface AbstractScheduledTask + extends ScheduledTask, DelayedRunnable { - - void finish(); + boolean isPeriodic(); } diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java new file mode 100644 index 00000000000..8643c65338e --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -0,0 +1,39 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +import org.spongepowered.api.scheduler.Scheduler; +import org.spongepowered.api.scheduler.Task; + +public interface AbstractScheduler extends Scheduler, AutoCloseable { + + void submit(DelayedRunnable task); + + @Override + AbstractScheduledTask submit(Task task); + + @Override + AbstractScheduledTask submit(Task task, String name); +} diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index c77050d5d8e..4023fefa159 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -38,7 +38,7 @@ import java.util.function.Consumer; import java.util.function.IntFunction; -public final class AsyncScheduler extends SpongeScheduler implements AutoCloseable { +public final class AsyncScheduler extends SpongeScheduler { private static final int NCPU = Runtime.getRuntime().availableProcessors(); private static final long KEEP_ALIVE_MILLIS = 10L; private final ThreadPoolExecutor executor; @@ -57,26 +57,22 @@ public AsyncScheduler() { } @Override - protected BlockingQueue getWorkQueue() { - return this.workQueue; + public void submit(DelayedRunnable task) { + workQueue.add(task); } @Override - public SpongeScheduledTask submit(Task task) { - SpongeScheduledTask sst = super.submit(task); + public AbstractScheduledTask submit(Task task) { + AbstractScheduledTask sst = super.submit(task); this.executor.prestartCoreThread(); return sst; } public CompletableFuture submit(final Callable callable) { - return this.asyncFailableFuture(callable, this.executor); - } - - private CompletableFuture asyncFailableFuture(Callable call, Executor exec) { final CompletableFuture ret = new CompletableFuture<>(); - exec.execute(() -> { + execute(() -> { try { - ret.complete(call.call()); + ret.complete(callable.call()); } catch (final Throwable e) { ret.completeExceptionally(e); } @@ -109,6 +105,7 @@ public void close() { } } + private record DelayQueueAsRunnable( BlockingQueue src ) implements BlockingQueue { @@ -145,6 +142,18 @@ public boolean offer(final Runnable runnable, timeout, unit); } + @Override + public boolean addAll(final Collection c) { + Objects.requireNonNull(c); + if (c == this) + throw new IllegalArgumentException(); + boolean modified = false; + for (Runnable e : c) + if (add(e)) + modified = true; + return modified; + } + @Override public @NotNull Runnable take() throws InterruptedException { return this.src.take(); @@ -218,18 +227,6 @@ public boolean containsAll(final Collection c) { return this.src.containsAll(c); } - @Override - public boolean addAll(final Collection c) { - Objects.requireNonNull(c); - if (c == this) - throw new IllegalArgumentException(); - boolean modified = false; - for (Runnable e : c) - if (add(e)) - modified = true; - return modified; - } - @Override public boolean removeAll(final Collection c) { return this.src.removeAll(c); diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java index d3b4bb8f299..07271717ac0 100644 --- a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java +++ b/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java @@ -31,8 +31,6 @@ public interface DelayedRunnable extends Delayed, Runnable { - boolean isPeriodic(); - @Override default int compareTo(@NotNull final Delayed other) { @@ -52,10 +50,5 @@ public void run() { public long getDelay(@NotNull TimeUnit unit) { return 0; } - - @Override - public boolean isPeriodic() { - return false; - } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java index 2ff280a6145..e47169b58b1 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java @@ -26,75 +26,60 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; import org.spongepowered.common.SpongeCommon; -import org.spongepowered.common.event.tracking.PhaseContext; -import org.spongepowered.common.event.tracking.PhaseTracker; -import org.spongepowered.common.event.tracking.phase.plugin.PluginPhase; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.UUID; import java.util.concurrent.TimeUnit; -public final class SpongeScheduledTask implements ScheduledTask, DelayedRunnable { - private final Scheduler scheduler; +public class SpongeScheduledTask implements AbstractScheduledTask { + private final AbstractScheduler scheduler; private final String name; private final UUID uuid; - private final TaskExecutor src; + private final TaskProcedure src; private volatile boolean cancelled = false; - private volatile long time; - private final Cyclic cyclic; + private long time; - SpongeScheduledTask(final Scheduler scheduler, + SpongeScheduledTask(final AbstractScheduler scheduler, final String name, final UUID uuid, - final TaskExecutor src, - final long start, - final Cyclic cyclic) { + final TaskProcedure src, + final long start) { this.scheduler = scheduler; this.name = name; this.uuid = uuid; this.src = src; this.time = start; - this.cyclic = cyclic; } @Override public void run() { - if (this.isCancelled()) + if (this.isCancelled()) { return; - final TaskExecutor x = this.src; - try (final PhaseContext<@NonNull ?> context = PluginPhase.State.SCHEDULED_TASK - .createPhaseContext(PhaseTracker.getInstance()) - .source(this) - .container(x.plugin())) { - context.buildAndSwitch(); - try { - x.accept(this); - } catch (final Throwable t) { - SpongeCommon.logger().error("The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", - name, x.plugin().metadata().id(), t); - } + } + final TaskProcedure x = this.src; + try { + x.execute(this); + } catch (final Exception ex) { + SpongeCommon.logger().error( + "The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", + name(), x.plugin().metadata().id(), + ex); } finally { if (isPeriodic()) { - final long q = x.intervalNanos(); - if (x.tickBased()) - this.time += q; - else - this.time = System.nanoTime() + q; - this.cyclic.enqueue(this); - } else { - this.cyclic.finish(); + this.time += x.interval().toNanos(); + VarHandle.releaseFence(); + this.scheduler.submit(this); } } } @Override public long getDelay(TimeUnit unit) { - return unit.convert( - this.time - System.nanoTime(), NANOSECONDS); + VarHandle.acquireFence(); + final long r = this.time; + return unit.convert(r - System.nanoTime(), NANOSECONDS); } @Override @@ -109,11 +94,8 @@ public Task task() { @Override public boolean cancel() { - final boolean cancelled = !this.cancelled || + return !(boolean) CANCELLED.getOpaque(this) || CANCELLED.compareAndSet(this, false, true); - if (cancelled) - this.cyclic.finish(); - return cancelled; } @Override @@ -123,7 +105,7 @@ public boolean isCancelled() { @Override public boolean isPeriodic() { - return this.src.intervalNanos() != 0; + return !this.src.interval().isZero(); } @Override @@ -136,7 +118,7 @@ public String name() { return this.name; } - + // VarHandle mechanic private static final VarHandle CANCELLED; static { try { diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index fd29f8ba2e1..c05b30cfb03 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -25,40 +25,21 @@ package org.spongepowered.common.scheduler; import org.spongepowered.api.scheduler.ScheduledTask; -import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.scheduler.TaskExecutorService; import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.PluginContainer; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import java.util.stream.Collectors; -public abstract class SpongeScheduler implements Scheduler { +public abstract class SpongeScheduler implements AbstractScheduler { private static final AtomicLong TASK_CREATED_COUNTER = new AtomicLong(); - - private static final int TICK_DURATION_MS = 50; - static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS - .convert(SpongeScheduler.TICK_DURATION_MS, TimeUnit.MILLISECONDS); - private final String tag; - private final ConcurrentMap cachedTasks = + private final ConcurrentMap cachedTasks = new ConcurrentHashMap<>(); private final AtomicLong sequenceNumber = new AtomicLong(); @@ -66,10 +47,8 @@ public abstract class SpongeScheduler implements Scheduler { this.tag = tag; } - protected abstract BlockingQueue getWorkQueue(); - @Override - public SpongeScheduledTask submit(Task task) { + public AbstractScheduledTask submit(Task task) { final String name = task.plugin().metadata().id() + "-" + @@ -77,31 +56,33 @@ public SpongeScheduledTask submit(Task task) { return this.submit(task, name); } + @Override - public SpongeScheduledTask submit(Task task, String name) { + public AbstractScheduledTask submit(Task task, String name) { final long number = this.sequenceNumber.getAndIncrement(); - final SpongeTask sp = (SpongeTask) task; + final TaskProcedure sp = (TaskProcedure) task; - final long start = System.nanoTime() + sp.delayNanos(); + final long start = System.nanoTime() + sp.delay().toNanos(); final UUID uuid = new UUID(number, System.identityHashCode(this)); - final SpongeScheduledTask sc = new SpongeScheduledTask(this, - "%s-%s-#%s".formatted(name, this.tag, number), uuid, - sp, start, - new Cyclic() { - @Override - public void enqueue(DelayedRunnable command) { - getWorkQueue().add(command); - } + final AbstractScheduledTask scheduledTask = + new SpongeScheduledTask(this, + "%s-%s-#%s".formatted(name, this.tag, number), uuid, + sp, start) { @Override - public void finish() { - cachedTasks.remove(uuid); + public void run() { + if (!this.isCancelled()) { + super.run(); + if (this.isPeriodic()) + return; + } + cachedTasks.remove(uniqueId()); } - }); - if (getWorkQueue().add(sc)) - cachedTasks.put(uuid, sc); - return sc; + }; + cachedTasks.put(uuid, scheduledTask); + submit(scheduledTask); + return scheduledTask; } @Override @@ -118,9 +99,8 @@ public Set findTasks(final String pattern) { return cachedTasks .values() .stream() - .filter(x -> { - return searchPattern.matcher(x.name()).matches(); - }).collect(Collectors.toSet()); + .filter(x -> searchPattern.matcher(x.name()).matches()) + .collect(Collectors.toSet()); } @Override @@ -163,5 +143,4 @@ public Future execute(final Callable callable) { public Future execute(final Runnable runnable) { return this.execute(Executors.callable(runnable)); } - } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index 3626a43852d..08e08ae6987 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -29,25 +29,29 @@ import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.util.Ticks; +import org.spongepowered.common.event.tracking.PhaseContext; +import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.event.tracking.phase.plugin.PluginPhase; import org.spongepowered.plugin.PluginContainer; import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -public final class SpongeTask implements TaskExecutor { +public final class SpongeTask implements TaskProcedure { private final PluginContainer plugin; private final Consumer executor; + private final Duration delay, interval; - private final long delay; // nanos - private final long interval; // nanos SpongeTask(final PluginContainer plugin, final Consumer executor, - final long delay, final long interval) { + final Duration delay, + final Duration interval) { this.plugin = plugin; this.executor = executor; this.delay = delay; @@ -55,28 +59,33 @@ public final class SpongeTask implements TaskExecutor { } @Override - public PluginContainer plugin() { - return this.plugin; - } - - @Override - public long delayNanos() { - return this.delay; + public void execute(ScheduledTask scheduledTask) throws ExecutionException { + try (final PhaseContext context = PluginPhase.State.SCHEDULED_TASK + .createPhaseContext(PhaseTracker.getInstance()) + .source(scheduledTask) + .container(this.plugin())) { + context.buildAndSwitch(); + try { + this.executor.accept(scheduledTask); + } catch (final Throwable t) { + throw new ExecutionException(t); + } + } } @Override - public long intervalNanos() { - return Math.abs(this.interval); + public PluginContainer plugin() { + return this.plugin; } @Override - public void accept(ScheduledTask scheduledTask) { - this.executor.accept(scheduledTask); + public Duration delay() { + return this.delay; } @Override - public boolean tickBased() { - return this.interval < 0; + public Duration interval() { + return this.interval; } @Override @@ -88,15 +97,13 @@ public String toString() { .toString(); } - - public static final class BuilderImpl implements Task.Builder { - + private static final int TICK_DURATION_MS = 50; + private static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS + .convert(TICK_DURATION_MS, TimeUnit.MILLISECONDS); private @Nullable Consumer executor; private @Nullable PluginContainer plugin; - - private long delay; - private long interval; + private Duration delay = Duration.ZERO, interval = Duration.ZERO; @Override public Task.Builder execute(final Consumer executor) { @@ -110,7 +117,7 @@ public Task.Builder delay(final long delay, final TemporalUnit unit) { if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Objects.requireNonNull(unit, "unit").getDuration().toNanos() * delay; + this.delay = Duration.of(delay, unit); return this; } @@ -120,7 +127,7 @@ public Task.Builder delay(final long delay, final TimeUnit unit) { if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Objects.requireNonNull(unit, "unit").toNanos(delay); + this.delay = Duration.of(delay, unit.toChronoUnit()); return this; } @@ -130,37 +137,39 @@ public Task.Builder delay(final Ticks delay) { if (delay.ticks() < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = delay.ticks() * SpongeScheduler.TICK_DURATION_NS; + this.delay = Duration.ofNanos(delay.ticks() * TICK_DURATION_NS); return this; } @Override public Task.Builder delay(final Duration delay) { - this.delay = Objects.requireNonNull(delay, "delay").toNanos(); + this.delay = Objects.requireNonNull(delay, "delay"); return this; } @Override public Task.Builder interval(final Duration interval) { - this.interval = Objects.requireNonNull(interval, "interval").toNanos(); + this.interval = Objects.requireNonNull(interval, "interval"); return this; } @Override - public Task.Builder interval(final long delay, final TemporalUnit unit) { - if (delay < 0) { + public Task.Builder interval(final long interval, final TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); + if (interval < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.interval = Objects.requireNonNull(unit, "unit").getDuration().toNanos() * delay; + this.interval = Duration.of(interval, unit); return this; } @Override public Task.Builder interval(final long interval, final TimeUnit unit) { + Objects.requireNonNull(unit, "unit"); if (interval < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = Objects.requireNonNull(unit, "unit").toNanos(interval); + this.interval = Duration.of(interval, unit.toChronoUnit()); return this; } @@ -170,7 +179,7 @@ public Task.Builder interval(final Ticks interval) { if (interval.ticks() < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = -interval.ticks() * SpongeScheduler.TICK_DURATION_NS; + this.interval = Duration.ofNanos(interval.ticks() * TICK_DURATION_NS); return this; } @@ -194,8 +203,8 @@ public Task.Builder from(final Task value) { public Task.Builder reset() { this.executor = null; this.plugin = null; - this.interval = 0; - this.delay = 0; + this.interval = Duration.ZERO; + this.delay = Duration.ZERO; return this; } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java index 151f4e2499e..43847ce9f35 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java @@ -25,21 +25,11 @@ package org.spongepowered.common.scheduler; import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.api.scheduler.ScheduledTask; -import org.spongepowered.api.scheduler.ScheduledTaskFuture; -import org.spongepowered.api.scheduler.Task; -import org.spongepowered.api.scheduler.TaskExecutorService; -import org.spongepowered.api.scheduler.TaskFuture; +import org.spongepowered.api.scheduler.*; import java.time.temporal.TemporalUnit; import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.Callable; -import java.util.concurrent.Delayed; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.function.Supplier; class SpongeTaskExecutorService @@ -47,9 +37,9 @@ class SpongeTaskExecutorService implements TaskExecutorService { private final Supplier taskBuilderProvider; - private final SpongeScheduler scheduler; + private final AbstractScheduler scheduler; - SpongeTaskExecutorService(final Supplier taskBuilderProvider, final SpongeScheduler scheduler) { + SpongeTaskExecutorService(final Supplier taskBuilderProvider, final AbstractScheduler scheduler) { this.taskBuilderProvider = taskBuilderProvider; this.scheduler = scheduler; } @@ -155,7 +145,7 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final SpongeScheduledTask scheduledTask = this.submitTask(task); + final AbstractScheduledTask scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); return new SpongeScheduledFuture<>(runnable, scheduledTask); @@ -168,7 +158,7 @@ public ScheduledTaskFuture scheduleAtFixedRate(final Runnable command, final .delay(initialDelay, unit) .interval(period, unit) .build(); - final SpongeScheduledTask scheduledTask = this.submitTask(task); + final AbstractScheduledTask scheduledTask = this.submitTask(task); // A repeatable task needs to be able to cancel itself runnable.setTask(scheduledTask); return new SpongeScheduledFuture<>(runnable, scheduledTask); @@ -190,17 +180,17 @@ private Task.Builder createTask(final Runnable command) { return this.taskBuilderProvider.get().execute(command); } - private SpongeScheduledTask submitTask(final Task task) { + private AbstractScheduledTask submitTask(final Task task) { return this.scheduler.submit(task); } private static class SpongeScheduledFuture implements org.spongepowered.api.scheduler.ScheduledTaskFuture { private final FutureTask runnable; - private final SpongeScheduledTask task; + private final AbstractScheduledTask task; SpongeScheduledFuture(final FutureTask runnable, - final SpongeScheduledTask task) { + final AbstractScheduledTask task) { this.runnable = runnable; this.task = task; } @@ -251,13 +241,11 @@ public boolean isDone() { @Override public V get() throws InterruptedException, ExecutionException { - System.out.println("LOOOCK"); return this.runnable.get(); } @Override public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - System.out.println("LCOK TIMEOUR"); return this.runnable.get(timeout, unit); } diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index b64b8733c79..0e41f5062c6 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -38,8 +38,8 @@ public abstract class SyncScheduler extends SpongeScheduler { } @Override - protected BlockingQueue getWorkQueue() { - return workQueue; + public void submit(DelayedRunnable task) { + this.workQueue.add(task); } /** @@ -50,4 +50,9 @@ public void tick() { (task = this.workQueue.poll()) != null; task.run()); } + + @Override + public void close() throws Exception { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java similarity index 76% rename from src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java rename to src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java index 0498f5ce829..0539d70dfdf 100644 --- a/src/main/java/org/spongepowered/common/scheduler/TaskExecutor.java +++ b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java @@ -27,25 +27,8 @@ import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Task; -import java.time.Duration; -import java.util.function.Consumer; -public interface TaskExecutor - extends Consumer, Task { +public interface TaskProcedure extends Task { - @Override - default Duration delay() { - return Duration.ofNanos(this.delayNanos()); - } - - @Override - default Duration interval() { - return Duration.ofNanos(this.intervalNanos()); - } - - long delayNanos(); - - long intervalNanos(); - - boolean tickBased(); + void execute(ScheduledTask task) throws Exception; } From cffa4758c5a458c28eb51605f124d6b0e90b12ff Mon Sep 17 00:00:00 2001 From: sunmisc Date: Tue, 5 Mar 2024 18:55:30 +0300 Subject: [PATCH 08/14] Simplified and fixed delayed remove --- .../spongepowered/common/scheduler/SpongeScheduler.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index c05b30cfb03..eddb51c77ff 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -72,12 +72,9 @@ public AbstractScheduledTask submit(Task task, String name) { sp, start) { @Override public void run() { - if (!this.isCancelled()) { - super.run(); - if (this.isPeriodic()) - return; - } - cachedTasks.remove(uniqueId()); + super.run(); + if (this.isCancelled() || !this.isPeriodic()) + cachedTasks.remove(uniqueId()); } }; cachedTasks.put(uuid, scheduledTask); From 498a31286f88feec277c35c3fdc6d1962de4d0d3 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Sun, 10 Mar 2024 21:06:34 +0300 Subject: [PATCH 09/14] refresh scheduler --- .../scheduler/AbstractScheduledTask.java | 15 +- .../common/scheduler/AbstractScheduler.java | 13 +- .../common/scheduler/AsyncScheduler.java | 225 +----------------- .../spongepowered/common/scheduler/Clock.java | 34 +++ ...edTask.java => ScheduledTaskEnvelope.java} | 81 ++----- .../common/scheduler/SpongeScheduler.java | 111 +++++---- .../common/scheduler/SpongeTask.java | 63 ++--- .../scheduler/SpongeTaskExecutorService.java | 30 ++- .../common/scheduler/SyncScheduler.java | 75 +++++- .../common/scheduler/TaskProcedure.java | 17 ++ .../{DelayedRunnable.java => Time.java} | 39 +-- 11 files changed, 327 insertions(+), 376 deletions(-) create mode 100644 src/main/java/org/spongepowered/common/scheduler/Clock.java rename src/main/java/org/spongepowered/common/scheduler/{SpongeScheduledTask.java => ScheduledTaskEnvelope.java} (61%) rename src/main/java/org/spongepowered/common/scheduler/{DelayedRunnable.java => Time.java} (70%) diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java index fb28f7ec460..d7a4b151da4 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java @@ -24,10 +24,21 @@ */ package org.spongepowered.common.scheduler; +import org.jetbrains.annotations.NotNull; import org.spongepowered.api.scheduler.ScheduledTask; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + public interface AbstractScheduledTask - extends ScheduledTask, DelayedRunnable { + extends ScheduledTask, Delayed { - boolean isPeriodic(); + @Override + default int compareTo(@NotNull Delayed other) { + if (other == this) + return 0; + return Long.compare( + this.getDelay(TimeUnit.NANOSECONDS), + other.getDelay(TimeUnit.NANOSECONDS)); + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java index 8643c65338e..237113f8089 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -27,13 +27,24 @@ import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + public interface AbstractScheduler extends Scheduler, AutoCloseable { - void submit(DelayedRunnable task); + ScheduledFuture scheduleAtTick(Runnable command, long ticks); + + ScheduledFuture scheduleAtTime(Runnable command, long nanos); + @Override AbstractScheduledTask submit(Task task); @Override AbstractScheduledTask submit(Task task, String name); + + default ScheduledFuture + scheduleAtTime(Runnable command, long time, TimeUnit unit) { + return scheduleAtTime(command, unit.convert(time, TimeUnit.NANOSECONDS)); + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index 4023fefa159..6e540e75460 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -26,46 +26,35 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.logging.log4j.Level; -import org.jetbrains.annotations.NotNull; -import org.spongepowered.api.scheduler.Task; import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.Collection; -import java.util.Iterator; -import java.util.Objects; import java.util.concurrent.*; -import java.util.function.Consumer; -import java.util.function.IntFunction; -public final class AsyncScheduler extends SpongeScheduler { +public class AsyncScheduler extends SpongeScheduler { private static final int NCPU = Runtime.getRuntime().availableProcessors(); - private static final long KEEP_ALIVE_MILLIS = 10L; - private final ThreadPoolExecutor executor; - private final BlockingQueue workQueue - = new DelayQueue<>(); + private final ScheduledExecutorService scheduler; public AsyncScheduler() { super("A"); - this.executor = new ThreadPoolExecutor(NCPU, Integer.MAX_VALUE, - KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS, - new DelayQueueAsRunnable(workQueue), - new ThreadFactoryBuilder() + final ScheduledThreadPoolExecutor scheduler = + new ScheduledThreadPoolExecutor(NCPU, new ThreadFactoryBuilder() .setNameFormat("Sponge-AsyncScheduler-%d") .build() - ); + ); + scheduler.setRemoveOnCancelPolicy(true); + this.scheduler = scheduler; } @Override - public void submit(DelayedRunnable task) { - workQueue.add(task); + public ScheduledFuture scheduleAtTick(Runnable command, long ticks) { + return scheduler.schedule(command, + ticks * TICK_DURATION_MS, TimeUnit.MILLISECONDS); } @Override - public AbstractScheduledTask submit(Task task) { - AbstractScheduledTask sst = super.submit(task); - this.executor.prestartCoreThread(); - return sst; + public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { + return scheduler.schedule(command, nanos, TimeUnit.NANOSECONDS); } public CompletableFuture submit(final Callable callable) { @@ -79,10 +68,9 @@ public CompletableFuture submit(final Callable callable) { }); return ret; } - @Override public void close() { - final ExecutorService scheduler = this.executor; + final ExecutorService scheduler = this.scheduler; if (scheduler.isTerminated()) { return; } @@ -105,191 +93,4 @@ public void close() { } } - - private record DelayQueueAsRunnable( - BlockingQueue src - ) implements BlockingQueue { - - @Override - public Runnable poll() { - return this.src.poll(); - } - - @Override - public boolean add(final Runnable runnable) { - return this.src.add( - new DelayedRunnable.NoDelayRunnable(runnable)); - } - - @Override - public boolean offer(final Runnable runnable) { - return this.src.offer( - new DelayedRunnable.NoDelayRunnable(runnable)); - } - - @Override - public void put(final Runnable runnable) throws InterruptedException { - this.src.put(new DelayedRunnable.NoDelayRunnable(runnable)); - } - - @Override - public boolean offer(final Runnable runnable, - final long timeout, - final TimeUnit unit - ) throws InterruptedException { - return this.src.offer( - new DelayedRunnable.NoDelayRunnable(runnable), - timeout, unit); - } - - @Override - public boolean addAll(final Collection c) { - Objects.requireNonNull(c); - if (c == this) - throw new IllegalArgumentException(); - boolean modified = false; - for (Runnable e : c) - if (add(e)) - modified = true; - return modified; - } - - @Override - public @NotNull Runnable take() throws InterruptedException { - return this.src.take(); - } - - @Override - public Runnable poll(final long timeout, - final TimeUnit unit - ) throws InterruptedException { - return this.src.poll(timeout, unit); - } - - @Override - public Runnable remove() { - return this.src.remove(); - } - - @Override - public Runnable peek() { - return this.src.peek(); - } - - @Override - public int size() { - return this.src.size(); - } - - @Override - public void clear() { - this.src.clear(); - } - - @Override - public int remainingCapacity() { - return this.src.remainingCapacity(); - } - - @Override - public boolean remove(final Object o) { - return this.src.remove(o); - } - - @Override - public DelayedRunnable element() { - return this.src.element(); - } - - @Override - public boolean isEmpty() { - return this.src.isEmpty(); - } - - @Override - public boolean contains(final Object o) { - return this.src.contains(o); - } - - @Override - public int drainTo(final Collection c) { - return this.src.drainTo(c); - } - - @Override - public int drainTo(final Collection c, - final int maxElements) { - return this.src.drainTo(c, maxElements); - } - - @Override - public boolean containsAll(final Collection c) { - return this.src.containsAll(c); - } - - @Override - public boolean removeAll(final Collection c) { - return this.src.removeAll(c); - } - - @Override - public boolean retainAll(final Collection c) { - return this.src.retainAll(c); - } - - @Override - public Object[] toArray() { - return this.src.toArray(); - } - - @Override - public T[] toArray(final T[] a) { - return this.src.toArray(a); - } - - @Override - public T[] toArray(final IntFunction generator) { - return this.src.toArray(generator); - } - - @Override - public Iterator iterator() { - return new Itr<>(this.src.iterator()); - } - - @Override - public boolean equals(final Object o) { - return this.src.equals(o); - } - - @Override - public String toString() { - return this.src.toString(); - } - - private record Itr( - Iterator src - ) implements Iterator { - - @Override - public boolean hasNext() { - return this.src.hasNext(); - } - - @Override - public Runnable next() { - return this.src.next(); - } - - @Override - public void remove() { - this.src.remove(); - } - - @Override - public void forEachRemaining(final Consumer action) { - this.src.forEachRemaining(action); - } - } - } } diff --git a/src/main/java/org/spongepowered/common/scheduler/Clock.java b/src/main/java/org/spongepowered/common/scheduler/Clock.java new file mode 100644 index 00000000000..0c23d9a44f9 --- /dev/null +++ b/src/main/java/org/spongepowered/common/scheduler/Clock.java @@ -0,0 +1,34 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.common.scheduler; + +@FunctionalInterface +public interface Clock { + + + long currentNanos(); + + Clock REAL_TIME = System::nanoTime; +} diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java similarity index 61% rename from src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java rename to src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java index e47169b58b1..6bea595909a 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java @@ -24,72 +24,32 @@ */ package org.spongepowered.common.scheduler; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - +import org.jetbrains.annotations.NotNull; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; -import org.spongepowered.common.SpongeCommon; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.UUID; +import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; -public class SpongeScheduledTask implements AbstractScheduledTask { - private final AbstractScheduler scheduler; +public class ScheduledTaskEnvelope implements AbstractScheduledTask { + private final Scheduler scheduler; + + private final Task task; private final String name; private final UUID uuid; - private final TaskProcedure src; - private volatile boolean cancelled = false; - private long time; + private volatile boolean cancelled; + volatile Delayed delayed; - SpongeScheduledTask(final AbstractScheduler scheduler, - final String name, final UUID uuid, - final TaskProcedure src, - final long start) { + public ScheduledTaskEnvelope(Scheduler scheduler, + Task task, + String name, UUID uuid) { this.scheduler = scheduler; + this.task = task; this.name = name; this.uuid = uuid; - this.src = src; - this.time = start; - } - @Override - public void run() { - if (this.isCancelled()) { - return; - } - final TaskProcedure x = this.src; - try { - x.execute(this); - } catch (final Exception ex) { - SpongeCommon.logger().error( - "The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", - name(), x.plugin().metadata().id(), - ex); - } finally { - if (isPeriodic()) { - this.time += x.interval().toNanos(); - VarHandle.releaseFence(); - this.scheduler.submit(this); - } - } - } - - @Override - public long getDelay(TimeUnit unit) { - VarHandle.acquireFence(); - final long r = this.time; - return unit.convert(r - System.nanoTime(), NANOSECONDS); - } - - @Override - public Scheduler scheduler() { - return this.scheduler; - } - - @Override - public Task task() { - return this.src; } @Override @@ -104,8 +64,13 @@ public boolean isCancelled() { } @Override - public boolean isPeriodic() { - return !this.src.interval().isZero(); + public Scheduler scheduler() { + return this.scheduler; + } + + @Override + public Task task() { + return this.task; } @Override @@ -118,12 +83,18 @@ public String name() { return this.name; } + @Override + public long getDelay(@NotNull TimeUnit unit) { + return this.delayed.getDelay(unit); + } + // VarHandle mechanic private static final VarHandle CANCELLED; + static { try { MethodHandles.Lookup l = MethodHandles.lookup(); - CANCELLED = l.findVarHandle(SpongeScheduledTask.class, + CANCELLED = l.findVarHandle(ScheduledTaskEnvelope.class, "cancelled", boolean.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index eddb51c77ff..42385fe1dce 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -27,6 +27,7 @@ import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.scheduler.TaskExecutorService; +import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.PluginContainer; @@ -37,51 +38,22 @@ import java.util.stream.Collectors; public abstract class SpongeScheduler implements AbstractScheduler { + private static final AtomicLong TASK_CREATED_COUNTER = new AtomicLong(); - private final String tag; - private final ConcurrentMap cachedTasks = - new ConcurrentHashMap<>(); + static final int TICK_DURATION_MS = 50; + static final long TICK_DURATION_NS = TimeUnit + .NANOSECONDS + .convert(TICK_DURATION_MS, TimeUnit.MILLISECONDS); + private final AtomicLong sequenceNumber = new AtomicLong(); + private final String tag; + private final ConcurrentMap cachedTasks + = new ConcurrentHashMap<>(); - SpongeScheduler(final String tag) { + protected SpongeScheduler(final String tag) { this.tag = tag; } - @Override - public AbstractScheduledTask submit(Task task) { - final String name = - task.plugin().metadata().id() + - "-" + - TASK_CREATED_COUNTER.incrementAndGet(); - return this.submit(task, name); - } - - - @Override - public AbstractScheduledTask submit(Task task, String name) { - final long number = this.sequenceNumber.getAndIncrement(); - final TaskProcedure sp = (TaskProcedure) task; - - final long start = System.nanoTime() + sp.delay().toNanos(); - - final UUID uuid = new UUID(number, System.identityHashCode(this)); - - final AbstractScheduledTask scheduledTask = - new SpongeScheduledTask(this, - "%s-%s-#%s".formatted(name, this.tag, number), uuid, - sp, start) { - @Override - public void run() { - super.run(); - if (this.isCancelled() || !this.isPeriodic()) - cachedTasks.remove(uniqueId()); - } - }; - cachedTasks.put(uuid, scheduledTask); - submit(scheduledTask); - return scheduledTask; - } - @Override public Optional findTask(final UUID id) { Objects.requireNonNull(id, "id"); @@ -124,10 +96,69 @@ public TaskExecutorService executor(PluginContainer plugin) { return new SpongeTaskExecutorService(() -> Task.builder().plugin(plugin), this); } + @Override + public AbstractScheduledTask submit(Task task) { + final String name = + task.plugin().metadata().id() + + "-" + + TASK_CREATED_COUNTER.incrementAndGet(); + return this.submit(task, name); + } + @Override + public AbstractScheduledTask submit(Task task, String name) { + final SpongeTask st = (SpongeTask) task; + + final long number = this.sequenceNumber.getAndIncrement(); + + final UUID uuid = new UUID(number, System.identityHashCode(this)); + + final ScheduledTaskEnvelope sched = new ScheduledTaskEnvelope( + this, task, + "%s-%s-#%s".formatted(name, this.tag, number), uuid) { + @Override + public boolean cancel() { + boolean cancelled = super.cancel(); + if (cancelled) + cachedTasks.remove(uniqueId()); + return cancelled; + } + }; + cachedTasks.put(sched.uniqueId(), sched); + exec(sched, st, true); + return sched; + } + private void exec(ScheduledTaskEnvelope task, SpongeTask procedure, boolean first) { + if (task.isCancelled()) { + return; + } + final Time time = first ? procedure.delayTime() : procedure.intervalTime(); + final long nanos = time.timeNanos();; + if (!first && nanos == 0) { + this.cachedTasks.remove(task.uniqueId()); + return; + } + final Runnable command = () -> { + if (task.isCancelled()) { + return; + } + try { + procedure.execute(task); + this.exec(task, procedure, false); + } catch (final ExecutionException ex) { + SpongeCommon.logger().error( + "The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", + task.name(), procedure.plugin().metadata().id(), + ex); + } + }; + + task.delayed = time.tickBased() + ? scheduleAtTick(command, nanos) + : scheduleAtTime(command, nanos); + } protected Collection activeTasks() { return Collections.unmodifiableCollection(this.cachedTasks.values()); } - public Future execute(final Callable callable) { final FutureTask runnable = new FutureTask<>(callable); this.submit(new SpongeTask.BuilderImpl() diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index 08e08ae6987..7237f172fbf 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -45,13 +45,12 @@ public final class SpongeTask implements TaskProcedure { private final PluginContainer plugin; private final Consumer executor; - private final Duration delay, interval; + private final Time delay, interval; SpongeTask(final PluginContainer plugin, final Consumer executor, - final Duration delay, - final Duration interval) { + final Time delay, final Time interval) { this.plugin = plugin; this.executor = executor; this.delay = delay; @@ -79,13 +78,13 @@ public PluginContainer plugin() { } @Override - public Duration delay() { - return this.delay; + public Time intervalTime() { + return this.interval; } @Override - public Duration interval() { - return this.interval; + public Time delayTime() { + return this.delay; } @Override @@ -98,12 +97,12 @@ public String toString() { } public static final class BuilderImpl implements Task.Builder { - private static final int TICK_DURATION_MS = 50; - private static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS - .convert(TICK_DURATION_MS, TimeUnit.MILLISECONDS); + private @Nullable Consumer executor; private @Nullable PluginContainer plugin; - private Duration delay = Duration.ZERO, interval = Duration.ZERO; + + private Time delay = Time.ZERO; + private Time interval = Time.ZERO; @Override public Task.Builder execute(final Consumer executor) { @@ -113,53 +112,57 @@ public Task.Builder execute(final Consumer executor) { @Override public Task.Builder delay(final long delay, final TemporalUnit unit) { - Objects.requireNonNull(unit); + Objects.requireNonNull(unit, "unit"); if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Duration.of(delay, unit); + this.delay = new Time.RealTime(unit.getDuration().toNanos() * delay); return this; } @Override public Task.Builder delay(final long delay, final TimeUnit unit) { - Objects.requireNonNull(unit); + Objects.requireNonNull(unit, "unit"); if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Duration.of(delay, unit.toChronoUnit()); + this.delay = new Time.RealTime(unit.toNanos(delay)); return this; } @Override public Task.Builder delay(final Ticks delay) { - Objects.requireNonNull(delay); + Objects.requireNonNull(delay, "delay"); if (delay.ticks() < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.delay = Duration.ofNanos(delay.ticks() * TICK_DURATION_NS); + this.delay = new Time.TickTime( + delay.ticks() * SpongeScheduler.TICK_DURATION_NS); return this; } @Override public Task.Builder delay(final Duration delay) { - this.delay = Objects.requireNonNull(delay, "delay"); + Objects.requireNonNull(delay, "delay"); + this.delay = new Time.RealTime(delay.toNanos()); return this; } @Override public Task.Builder interval(final Duration interval) { - this.interval = Objects.requireNonNull(interval, "interval"); + Objects.requireNonNull(interval, "interval"); + this.interval = new Time.RealTime(interval.toNanos()); return this; } @Override - public Task.Builder interval(final long interval, final TemporalUnit unit) { + public Task.Builder interval(final long delay, final TemporalUnit unit) { Objects.requireNonNull(unit, "unit"); - if (interval < 0) { + if (delay < 0) { throw new IllegalArgumentException("Delay must be equal to or greater than zero!"); } - this.interval = Duration.of(interval, unit); + this.interval = new Time.RealTime( + unit.getDuration().toNanos() * delay); return this; } @@ -169,7 +172,7 @@ public Task.Builder interval(final long interval, final TimeUnit unit) { if (interval < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = Duration.of(interval, unit.toChronoUnit()); + this.interval = new Time.RealTime(unit.toNanos(interval)); return this; } @@ -179,7 +182,8 @@ public Task.Builder interval(final Ticks interval) { if (interval.ticks() < 0) { throw new IllegalArgumentException("Interval must be equal to or greater than zero!"); } - this.interval = Duration.ofNanos(interval.ticks() * TICK_DURATION_NS); + this.interval = new Time.TickTime( + interval.ticks() * SpongeScheduler.TICK_DURATION_NS); return this; } @@ -191,7 +195,9 @@ public Task.Builder plugin(final PluginContainer plugin) { @Override public Task.Builder from(final Task value) { - final SpongeTask task = (SpongeTask) Objects.requireNonNull(value, "value"); + + final SpongeTask task = (SpongeTask) + Objects.requireNonNull(value, "value"); this.executor = task.executor; this.plugin = task.plugin(); this.interval = task.interval; @@ -203,8 +209,8 @@ public Task.Builder from(final Task value) { public Task.Builder reset() { this.executor = null; this.plugin = null; - this.interval = Duration.ZERO; - this.delay = Duration.ZERO; + this.interval = Time.ZERO; + this.delay = Time.ZERO; return this; } @@ -213,7 +219,8 @@ public Task build() { Objects.requireNonNull(this.executor, "executor"); Objects.requireNonNull(this.plugin, "plugin"); - return new SpongeTask(this.plugin, this.executor, this.delay, this.interval); + return new SpongeTask(this.plugin, this.executor, + this.delay, this.interval); } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java index 43847ce9f35..a3d40f24c79 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTaskExecutorService.java @@ -24,22 +24,31 @@ */ package org.spongepowered.common.scheduler; +import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.api.scheduler.*; +import org.spongepowered.api.scheduler.ScheduledTask; +import org.spongepowered.api.scheduler.ScheduledTaskFuture; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.scheduler.TaskExecutorService; +import org.spongepowered.api.scheduler.TaskFuture; import java.time.temporal.TemporalUnit; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Supplier; -class SpongeTaskExecutorService - extends AbstractExecutorService - implements TaskExecutorService { +class SpongeTaskExecutorService extends AbstractExecutorService implements TaskExecutorService { private final Supplier taskBuilderProvider; - private final AbstractScheduler scheduler; + private final SpongeScheduler scheduler; - SpongeTaskExecutorService(final Supplier taskBuilderProvider, final AbstractScheduler scheduler) { + SpongeTaskExecutorService(final Supplier taskBuilderProvider, final SpongeScheduler scheduler) { this.taskBuilderProvider = taskBuilderProvider; this.scheduler = scheduler; } @@ -58,7 +67,7 @@ public void shutdown() { @Override public List shutdownNow() { - return List.of(); + return ImmutableList.of(); } @Override @@ -202,7 +211,7 @@ public ScheduledTask task() { @Override public boolean isPeriodic() { - return this.task.isPeriodic(); + return !this.task.task().interval().isZero(); } @Override @@ -222,7 +231,7 @@ public void run() { @Override public boolean cancel(final boolean mayInterruptIfRunning) { - this.task.cancel(); // Ensure Sponge is not going to try to run a cancelled task. + this.task.cancel(); //Ensure Sponge is not going to try to run a cancelled task. return this.runnable.cancel(mayInterruptIfRunning); } @@ -247,7 +256,6 @@ public V get() throws InterruptedException, ExecutionException { @Override public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return this.runnable.get(timeout, unit); - } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 0e41f5062c6..0497539d1de 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -24,35 +24,86 @@ */ package org.spongepowered.common.scheduler; +import org.jetbrains.annotations.NotNull; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.DelayQueue; +import java.util.concurrent.*; -public abstract class SyncScheduler extends SpongeScheduler { +public class SyncScheduler extends SpongeScheduler { - private final BlockingQueue workQueue - = new DelayQueue<>(); + private final BlockingQueue> + ticksQueue = new DelayQueue<>(), + timedQueue = new DelayQueue<>(); + private long timestamp; - SyncScheduler(final String tag) { + + protected SyncScheduler(final String tag) { super(tag); } @Override - public void submit(DelayedRunnable task) { - this.workQueue.add(task); + public ScheduledFuture scheduleAtTick(Runnable command, long ticks) { + final RunnableScheduledFuture f = new SchedFutureTask<>( + command, null, + () -> timestamp, + ticks * TICK_DURATION_NS); + this.ticksQueue.add(f); + return f; + } + + @Override + public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { + final RunnableScheduledFuture f = new SchedFutureTask<>( + command, null, + Clock.REAL_TIME, + nanos); + this.timedQueue.add(f); + return f; } - /** - * The hook to update the Ticks known by the SyncScheduler. - */ public void tick() { + timestamp += TICK_DURATION_NS; + for (Runnable task; + (task = this.ticksQueue.poll()) != null; + task.run()); for (Runnable task; - (task = this.workQueue.poll()) != null; + (task = this.timedQueue.poll()) != null; task.run()); } + @Override public void close() throws Exception { throw new UnsupportedOperationException(); } + + + private static class SchedFutureTask + extends FutureTask + implements RunnableScheduledFuture { + private final Clock clock; + private final long delay; + SchedFutureTask(Runnable runnable, V result, + Clock clock, long delay) { + super(runnable, result); + this.clock = clock; + this.delay = delay; + } + @Override + public boolean isPeriodic() { + return false; + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return unit.convert( + this.delay - clock.currentNanos(), + TimeUnit.NANOSECONDS); + } + @Override + public int compareTo(@NotNull Delayed o) { + return Long.compare( + this.getDelay(TimeUnit.NANOSECONDS), + o.getDelay(TimeUnit.NANOSECONDS)); + } + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java index 0539d70dfdf..84ec3cf1880 100644 --- a/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java +++ b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java @@ -27,8 +27,25 @@ import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Task; +import java.time.Duration; public interface TaskProcedure extends Task { void execute(ScheduledTask task) throws Exception; + + + + Time intervalTime(); + + Time delayTime(); + + @Override + default Duration delay() { + return delayTime().toDuration(); + } + + @Override + default Duration interval() { + return intervalTime().toDuration(); + } } diff --git a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java b/src/main/java/org/spongepowered/common/scheduler/Time.java similarity index 70% rename from src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java rename to src/main/java/org/spongepowered/common/scheduler/Time.java index 07271717ac0..08625a48e4f 100644 --- a/src/main/java/org/spongepowered/common/scheduler/DelayedRunnable.java +++ b/src/main/java/org/spongepowered/common/scheduler/Time.java @@ -24,31 +24,40 @@ */ package org.spongepowered.common.scheduler; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.Delayed; +import java.time.Duration; import java.util.concurrent.TimeUnit; -public interface DelayedRunnable extends Delayed, Runnable { +public interface Time { + + Time ZERO = new RealTime(0); + + boolean tickBased(); + + + long timeNanos(); - @Override - default int compareTo(@NotNull final Delayed other) { - return other == this ? 0 : Long.compare( - this.getDelay(TimeUnit.NANOSECONDS), - other.getDelay(TimeUnit.NANOSECONDS) - ); + default long convert(TimeUnit unit) { + return unit.convert(timeNanos(), TimeUnit.NANOSECONDS); } - record NoDelayRunnable(Runnable origin) implements DelayedRunnable { + + default Duration toDuration() { + return Duration.ofNanos(timeNanos()); + } + + + record RealTime(long timeNanos) implements Time { @Override - public void run() { - this.origin.run(); + public boolean tickBased() { + return false; } + } + record TickTime(long timeNanos) implements Time { @Override - public long getDelay(@NotNull TimeUnit unit) { - return 0; + public boolean tickBased() { + return true; } } } From 65d0a828632dde2632e9e8b333477bdceb9b1b63 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Mon, 11 Mar 2024 13:31:28 +0300 Subject: [PATCH 10/14] many bugs fixed and cleanup --- .../scheduler/AbstractScheduledTask.java | 14 +---- .../common/scheduler/AbstractScheduler.java | 14 ++--- .../common/scheduler/AsyncScheduler.java | 13 +++-- .../spongepowered/common/scheduler/Clock.java | 34 ------------ .../scheduler/ScheduledTaskEnvelope.java | 21 +++++--- .../common/scheduler/SpongeScheduler.java | 53 +++++++++++-------- .../common/scheduler/SyncScheduler.java | 52 ++++++++++++------ .../spongepowered/common/scheduler/Time.java | 10 ++++ 8 files changed, 108 insertions(+), 103 deletions(-) delete mode 100644 src/main/java/org/spongepowered/common/scheduler/Clock.java diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java index d7a4b151da4..c5d93d4eff0 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java @@ -24,21 +24,9 @@ */ package org.spongepowered.common.scheduler; -import org.jetbrains.annotations.NotNull; import org.spongepowered.api.scheduler.ScheduledTask; import java.util.concurrent.Delayed; -import java.util.concurrent.TimeUnit; -public interface AbstractScheduledTask - extends ScheduledTask, Delayed { - - @Override - default int compareTo(@NotNull Delayed other) { - if (other == this) - return 0; - return Long.compare( - this.getDelay(TimeUnit.NANOSECONDS), - other.getDelay(TimeUnit.NANOSECONDS)); - } +interface AbstractScheduledTask extends ScheduledTask, Delayed { } diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java index 237113f8089..6e8fabca70f 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -30,19 +30,19 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -public interface AbstractScheduler extends Scheduler, AutoCloseable { - - ScheduledFuture scheduleAtTick(Runnable command, long ticks); - - ScheduledFuture scheduleAtTime(Runnable command, long nanos); - - +interface AbstractScheduler extends Scheduler, AutoCloseable { @Override AbstractScheduledTask submit(Task task); @Override AbstractScheduledTask submit(Task task, String name); + + // basic operations + ScheduledFuture scheduleAtTick(Runnable command, long ticks); + + ScheduledFuture scheduleAtTime(Runnable command, long nanos); + default ScheduledFuture scheduleAtTime(Runnable command, long time, TimeUnit unit) { return scheduleAtTime(command, unit.convert(time, TimeUnit.NANOSECONDS)); diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index 6e540e75460..ce1542f17d0 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -29,7 +29,13 @@ import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class AsyncScheduler extends SpongeScheduler { private static final int NCPU = Runtime.getRuntime().availableProcessors(); @@ -47,9 +53,8 @@ public AsyncScheduler() { } @Override - public ScheduledFuture scheduleAtTick(Runnable command, long ticks) { - return scheduler.schedule(command, - ticks * TICK_DURATION_MS, TimeUnit.MILLISECONDS); + public ScheduledFuture scheduleAtTick(Runnable command, long ticksAsNanos) { + return scheduler.schedule(command, ticksAsNanos, TimeUnit.NANOSECONDS); } @Override diff --git a/src/main/java/org/spongepowered/common/scheduler/Clock.java b/src/main/java/org/spongepowered/common/scheduler/Clock.java deleted file mode 100644 index 0c23d9a44f9..00000000000 --- a/src/main/java/org/spongepowered/common/scheduler/Clock.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.common.scheduler; - -@FunctionalInterface -public interface Clock { - - - long currentNanos(); - - Clock REAL_TIME = System::nanoTime; -} diff --git a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java index 6bea595909a..9b7feae2aa4 100644 --- a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java +++ b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java @@ -35,26 +35,25 @@ import java.util.concurrent.TimeUnit; public class ScheduledTaskEnvelope implements AbstractScheduledTask { - private final Scheduler scheduler; + private final AbstractScheduler scheduler; - private final Task task; + private final TaskProcedure task; private final String name; private final UUID uuid; private volatile boolean cancelled; - volatile Delayed delayed; + volatile Delayed delayed; // init ? - public ScheduledTaskEnvelope(Scheduler scheduler, - Task task, - String name, UUID uuid) { + ScheduledTaskEnvelope(AbstractScheduler scheduler, + TaskProcedure task, + String name, UUID uuid) { this.scheduler = scheduler; this.task = task; this.name = name; this.uuid = uuid; } - @Override public boolean cancel() { - return !(boolean) CANCELLED.getOpaque(this) || + return !(boolean) CANCELLED.getOpaque(this) && CANCELLED.compareAndSet(this, false, true); } @@ -88,6 +87,12 @@ public long getDelay(@NotNull TimeUnit unit) { return this.delayed.getDelay(unit); } + @Override + public int compareTo(@NotNull Delayed other) { + return this.delayed.compareTo(other); + } + + // VarHandle mechanic private static final VarHandle CANCELLED; diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 42385fe1dce..61146af9dff 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -31,8 +31,20 @@ import org.spongepowered.common.launch.Launch; import org.spongepowered.plugin.PluginContainer; -import java.util.*; -import java.util.concurrent.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -113,13 +125,15 @@ public AbstractScheduledTask submit(Task task, String name) { final UUID uuid = new UUID(number, System.identityHashCode(this)); final ScheduledTaskEnvelope sched = new ScheduledTaskEnvelope( - this, task, + this, st, "%s-%s-#%s".formatted(name, this.tag, number), uuid) { @Override public boolean cancel() { boolean cancelled = super.cancel(); - if (cancelled) + if (cancelled) { + // fast remove cachedTasks.remove(uniqueId()); + } return cancelled; } }; @@ -127,32 +141,29 @@ public boolean cancel() { exec(sched, st, true); return sched; } - private void exec(ScheduledTaskEnvelope task, SpongeTask procedure, boolean first) { - if (task.isCancelled()) { - return; - } - final Time time = first ? procedure.delayTime() : procedure.intervalTime(); - final long nanos = time.timeNanos();; - if (!first && nanos == 0) { - this.cachedTasks.remove(task.uniqueId()); + private void exec(final ScheduledTaskEnvelope sched, + final TaskProcedure task, + final boolean first) { + final Time time = first ? task.delayTime() : task.intervalTime(); + final long nanos = time.timeNanos(); + if ((!first && nanos == 0) || sched.isCancelled()) { + this.cachedTasks.remove(sched.uniqueId()); return; } final Runnable command = () -> { - if (task.isCancelled()) { - return; - } try { - procedure.execute(task); - this.exec(task, procedure, false); - } catch (final ExecutionException ex) { + if (!sched.isCancelled()) + task.execute(sched); + } catch (final Exception ex) { SpongeCommon.logger().error( "The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", - task.name(), procedure.plugin().metadata().id(), + sched.name(), task.plugin().metadata().id(), ex); + } finally { + this.exec(sched, task, false); } }; - - task.delayed = time.tickBased() + sched.delayed = time.tickBased() ? scheduleAtTick(command, nanos) : scheduleAtTime(command, nanos); } diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 0497539d1de..3ae1d8bff8a 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -26,42 +26,61 @@ import org.jetbrains.annotations.NotNull; -import java.util.concurrent.*; +import java.lang.invoke.VarHandle; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableScheduledFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class SyncScheduler extends SpongeScheduler { - private final BlockingQueue> ticksQueue = new DelayQueue<>(), timedQueue = new DelayQueue<>(); private long timestamp; + private final Time ticked = new Time() { + @Override + public boolean tickBased() { + return true; + } + + @Override + public long timeNanos() { + VarHandle.acquireFence(); + return timestamp; + } + }; protected SyncScheduler(final String tag) { super(tag); } @Override - public ScheduledFuture scheduleAtTick(Runnable command, long ticks) { + public ScheduledFuture scheduleAtTick(Runnable command, long ticksAsNanos) { + final Time clock = this.ticked; final RunnableScheduledFuture f = new SchedFutureTask<>( command, null, - () -> timestamp, - ticks * TICK_DURATION_NS); + clock, ticksAsNanos + clock.timeNanos()); this.ticksQueue.add(f); return f; } @Override public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { + final Time clock = Time.REAL_TIME; final RunnableScheduledFuture f = new SchedFutureTask<>( command, null, - Clock.REAL_TIME, - nanos); + clock, nanos + clock.timeNanos()); this.timedQueue.add(f); return f; } public void tick() { timestamp += TICK_DURATION_NS; + VarHandle.releaseFence(); for (Runnable task; (task = this.ticksQueue.poll()) != null; task.run()); @@ -70,23 +89,22 @@ public void tick() { task.run()); } - @Override public void close() throws Exception { throw new UnsupportedOperationException(); } - private static class SchedFutureTask extends FutureTask implements RunnableScheduledFuture { - private final Clock clock; - private final long delay; + private final Time clock; + private final long timeStamp; + SchedFutureTask(Runnable runnable, V result, - Clock clock, long delay) { + Time clock, long timeStamp) { super(runnable, result); this.clock = clock; - this.delay = delay; + this.timeStamp = timeStamp; } @Override public boolean isPeriodic() { @@ -96,14 +114,16 @@ public boolean isPeriodic() { @Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert( - this.delay - clock.currentNanos(), + this.timeStamp - this.clock.timeNanos(), TimeUnit.NANOSECONDS); } @Override - public int compareTo(@NotNull Delayed o) { + public int compareTo(@NotNull Delayed other) { + if (other == this) + return 0; return Long.compare( this.getDelay(TimeUnit.NANOSECONDS), - o.getDelay(TimeUnit.NANOSECONDS)); + other.getDelay(TimeUnit.NANOSECONDS)); } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/Time.java b/src/main/java/org/spongepowered/common/scheduler/Time.java index 08625a48e4f..4c55fbc59ff 100644 --- a/src/main/java/org/spongepowered/common/scheduler/Time.java +++ b/src/main/java/org/spongepowered/common/scheduler/Time.java @@ -30,6 +30,16 @@ public interface Time { Time ZERO = new RealTime(0); + Time REAL_TIME = new Time() { + @Override + public boolean tickBased() { + return false; + } + @Override + public long timeNanos() { + return System.nanoTime(); + } + }; boolean tickBased(); From abe50ba177b8df5f890ad8489be89b7ea2da462d Mon Sep 17 00:00:00 2001 From: sunmisc Date: Mon, 11 Mar 2024 16:10:45 +0300 Subject: [PATCH 11/14] cleanup --- .../common/scheduler/AsyncScheduler.java | 25 +++++++------------ .../common/scheduler/SpongeScheduler.java | 12 +-------- .../common/scheduler/SpongeTask.java | 12 +++++---- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index ce1542f17d0..5e07b148e9f 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -29,27 +29,20 @@ import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class AsyncScheduler extends SpongeScheduler { private static final int NCPU = Runtime.getRuntime().availableProcessors(); - private final ScheduledExecutorService scheduler; + private final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool( + NCPU, new ThreadFactoryBuilder() + .setNameFormat("Sponge-AsyncScheduler-%d") + .build() + + ); public AsyncScheduler() { super("A"); - final ScheduledThreadPoolExecutor scheduler = - new ScheduledThreadPoolExecutor(NCPU, new ThreadFactoryBuilder() - .setNameFormat("Sponge-AsyncScheduler-%d") - .build() - ); - scheduler.setRemoveOnCancelPolicy(true); - this.scheduler = scheduler; } @Override @@ -91,7 +84,7 @@ public void close() { scheduler.shutdownNow(); } - } catch (InterruptedException e) { + } catch (final InterruptedException e) { SpongeCommon.logger().error("The async scheduler was interrupted while awaiting shutdown!"); Thread.currentThread().interrupt(); diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 61146af9dff..d45c3cd6720 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -126,17 +126,7 @@ public AbstractScheduledTask submit(Task task, String name) { final ScheduledTaskEnvelope sched = new ScheduledTaskEnvelope( this, st, - "%s-%s-#%s".formatted(name, this.tag, number), uuid) { - @Override - public boolean cancel() { - boolean cancelled = super.cancel(); - if (cancelled) { - // fast remove - cachedTasks.remove(uniqueId()); - } - return cancelled; - } - }; + "%s-%s-#%s".formatted(name, this.tag, number), uuid); cachedTasks.put(sched.uniqueId(), sched); exec(sched, st, true); return sched; diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index 7237f172fbf..c185c3b24ff 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -24,7 +24,6 @@ */ package org.spongepowered.common.scheduler; -import com.google.common.base.MoreObjects; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scheduler.ScheduledTask; import org.spongepowered.api.scheduler.Task; @@ -37,6 +36,7 @@ import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.Objects; +import java.util.StringJoiner; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -89,11 +89,13 @@ public Time delayTime() { @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("plugin", this.plugin.metadata().id()) - .add("delay", this.delay().toNanos()) - .add("interval", this.interval().toNanos()) + return new StringJoiner(", ", + SpongeTask.class.getSimpleName() + "[", "]") + .add("plugin=" + this.plugin.metadata().id()) + .add("delay=" + this.delay().toNanos()) + .add("interval=" + this.interval().toNanos()) .toString(); + } public static final class BuilderImpl implements Task.Builder { From 9cc33ac306645cb78135f4a5a44fe2d26976e847 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Mon, 11 Mar 2024 20:58:31 +0300 Subject: [PATCH 12/14] fixed formatting --- .../common/scheduler/AbstractScheduler.java | 2 +- .../common/scheduler/AsyncScheduler.java | 27 ++++++++++--------- .../scheduler/ScheduledTaskEnvelope.java | 1 - .../common/scheduler/SpongeScheduler.java | 17 ++++++------ .../common/scheduler/SpongeTask.java | 4 +-- .../common/scheduler/SyncScheduler.java | 11 ++++---- .../common/scheduler/TaskProcedure.java | 8 +++--- .../spongepowered/common/scheduler/Time.java | 2 +- 8 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java index 6e8fabca70f..9a72dde565e 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -45,6 +45,6 @@ interface AbstractScheduler extends Scheduler, AutoCloseable { default ScheduledFuture scheduleAtTime(Runnable command, long time, TimeUnit unit) { - return scheduleAtTime(command, unit.convert(time, TimeUnit.NANOSECONDS)); + return this.scheduleAtTime(command, unit.convert(time, TimeUnit.NANOSECONDS)); } } diff --git a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java index 5e07b148e9f..4d54639d072 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AsyncScheduler.java @@ -29,35 +29,36 @@ import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.util.PrettyPrinter; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class AsyncScheduler extends SpongeScheduler { private static final int NCPU = Runtime.getRuntime().availableProcessors(); private final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool( - NCPU, new ThreadFactoryBuilder() - .setNameFormat("Sponge-AsyncScheduler-%d") - .build() - - ); - + Executors.newScheduledThreadPool(NCPU, new ThreadFactoryBuilder() + .setNameFormat("Sponge-AsyncScheduler-%d") + .build()); public AsyncScheduler() { super("A"); } @Override public ScheduledFuture scheduleAtTick(Runnable command, long ticksAsNanos) { - return scheduler.schedule(command, ticksAsNanos, TimeUnit.NANOSECONDS); + return this.scheduler.schedule(command, ticksAsNanos, TimeUnit.NANOSECONDS); } @Override public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { - return scheduler.schedule(command, nanos, TimeUnit.NANOSECONDS); + return this.scheduler.schedule(command, nanos, TimeUnit.NANOSECONDS); } public CompletableFuture submit(final Callable callable) { final CompletableFuture ret = new CompletableFuture<>(); - execute(() -> { + super.execute(() -> { try { ret.complete(callable.call()); } catch (final Throwable e) { @@ -68,7 +69,7 @@ public CompletableFuture submit(final Callable callable) { } @Override public void close() { - final ExecutorService scheduler = this.scheduler; + final ScheduledExecutorService scheduler = this.scheduler; if (scheduler.isTerminated()) { return; } @@ -77,7 +78,7 @@ public void close() { if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { new PrettyPrinter() .add("Sponge async scheduler failed to shut down in 5 seconds! Tasks that may have been active:") - .addWithIndices(activeTasks()) + .addWithIndices(super.activeTasks()) .add() .add("We will now attempt immediate shutdown.") .log(SpongeCommon.logger(), Level.WARN); diff --git a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java index 9b7feae2aa4..7aca611309c 100644 --- a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java +++ b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java @@ -92,7 +92,6 @@ public int compareTo(@NotNull Delayed other) { return this.delayed.compareTo(other); } - // VarHandle mechanic private static final VarHandle CANCELLED; diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index d45c3cd6720..2c3a40034ac 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -53,10 +53,8 @@ public abstract class SpongeScheduler implements AbstractScheduler { private static final AtomicLong TASK_CREATED_COUNTER = new AtomicLong(); static final int TICK_DURATION_MS = 50; - static final long TICK_DURATION_NS = TimeUnit - .NANOSECONDS + static final long TICK_DURATION_NS = TimeUnit.NANOSECONDS .convert(TICK_DURATION_MS, TimeUnit.MILLISECONDS); - private final AtomicLong sequenceNumber = new AtomicLong(); private final String tag; private final ConcurrentMap cachedTasks @@ -77,7 +75,7 @@ public Set findTasks(final String pattern) { final Pattern searchPattern = Pattern.compile( Objects.requireNonNull(pattern, "pattern")); - return cachedTasks + return this.cachedTasks .values() .stream() .filter(x -> searchPattern.matcher(x.name()).matches()) @@ -86,7 +84,7 @@ public Set findTasks(final String pattern) { @Override public Set tasks() { - return new HashSet<>(cachedTasks.values()); + return new HashSet<>(this.cachedTasks.values()); } @Override @@ -94,7 +92,7 @@ public Set tasks(final PluginContainer plugin) { final String testOwnerId = Objects .requireNonNull(plugin, "plugin") .metadata().id(); - return cachedTasks + return this.cachedTasks .values() .stream() .filter(x -> { @@ -127,8 +125,8 @@ public AbstractScheduledTask submit(Task task, String name) { final ScheduledTaskEnvelope sched = new ScheduledTaskEnvelope( this, st, "%s-%s-#%s".formatted(name, this.tag, number), uuid); - cachedTasks.put(sched.uniqueId(), sched); - exec(sched, st, true); + this.cachedTasks.put(sched.uniqueId(), sched); + this.exec(sched, st, true); return sched; } private void exec(final ScheduledTaskEnvelope sched, @@ -142,8 +140,9 @@ private void exec(final ScheduledTaskEnvelope sched, } final Runnable command = () -> { try { - if (!sched.isCancelled()) + if (!sched.isCancelled()) { task.execute(sched); + } } catch (final Exception ex) { SpongeCommon.logger().error( "The Scheduler tried to run the task '{}' owned by '{}' but an error occurred.", diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java index c185c3b24ff..e18180d18ca 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeTask.java @@ -47,7 +47,6 @@ public final class SpongeTask implements TaskProcedure { private final Consumer executor; private final Time delay, interval; - SpongeTask(final PluginContainer plugin, final Consumer executor, final Time delay, final Time interval) { @@ -221,8 +220,7 @@ public Task build() { Objects.requireNonNull(this.executor, "executor"); Objects.requireNonNull(this.plugin, "plugin"); - return new SpongeTask(this.plugin, this.executor, - this.delay, this.interval); + return new SpongeTask(this.plugin, this.executor, this.delay, this.interval); } } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 3ae1d8bff8a..df0afaccd6f 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -37,7 +37,8 @@ public class SyncScheduler extends SpongeScheduler { private final BlockingQueue> - ticksQueue = new DelayQueue<>(), + ticksQueue = new DelayQueue<>(); + private final BlockingQueue> timedQueue = new DelayQueue<>(); private long timestamp; @@ -50,7 +51,7 @@ public boolean tickBased() { @Override public long timeNanos() { VarHandle.acquireFence(); - return timestamp; + return SyncScheduler.this.timestamp; } }; @@ -79,7 +80,7 @@ public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { } public void tick() { - timestamp += TICK_DURATION_NS; + this.timestamp += TICK_DURATION_NS; VarHandle.releaseFence(); for (Runnable task; (task = this.ticksQueue.poll()) != null; @@ -119,9 +120,7 @@ public long getDelay(@NotNull TimeUnit unit) { } @Override public int compareTo(@NotNull Delayed other) { - if (other == this) - return 0; - return Long.compare( + return other == this ? 0 : Long.compare( this.getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS)); } diff --git a/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java index 84ec3cf1880..397463a0957 100644 --- a/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java +++ b/src/main/java/org/spongepowered/common/scheduler/TaskProcedure.java @@ -29,23 +29,21 @@ import java.time.Duration; -public interface TaskProcedure extends Task { +interface TaskProcedure extends Task { void execute(ScheduledTask task) throws Exception; - - Time intervalTime(); Time delayTime(); @Override default Duration delay() { - return delayTime().toDuration(); + return this.delayTime().toDuration(); } @Override default Duration interval() { - return intervalTime().toDuration(); + return this.intervalTime().toDuration(); } } diff --git a/src/main/java/org/spongepowered/common/scheduler/Time.java b/src/main/java/org/spongepowered/common/scheduler/Time.java index 4c55fbc59ff..3d3bf6a38a9 100644 --- a/src/main/java/org/spongepowered/common/scheduler/Time.java +++ b/src/main/java/org/spongepowered/common/scheduler/Time.java @@ -27,7 +27,7 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; -public interface Time { +interface Time { Time ZERO = new RealTime(0); Time REAL_TIME = new Time() { From 8e79e34697c0f68683849874ec8f4bd06c28bb25 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Mon, 11 Mar 2024 23:51:19 +0300 Subject: [PATCH 13/14] code is simplified --- .../scheduler/AbstractScheduledTask.java | 2 +- .../common/scheduler/AbstractScheduler.java | 7 ++- .../scheduler/ScheduledTaskEnvelope.java | 26 ++------- .../common/scheduler/SpongeScheduler.java | 2 +- .../common/scheduler/SyncScheduler.java | 55 +++++++------------ .../spongepowered/common/scheduler/Time.java | 2 +- 6 files changed, 31 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java index c5d93d4eff0..38162116e6e 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduledTask.java @@ -28,5 +28,5 @@ import java.util.concurrent.Delayed; -interface AbstractScheduledTask extends ScheduledTask, Delayed { +public interface AbstractScheduledTask extends ScheduledTask, Delayed { } diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java index 9a72dde565e..79e7e3e39bd 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -27,6 +27,7 @@ import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; +import java.util.concurrent.Delayed; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -39,11 +40,11 @@ interface AbstractScheduler extends Scheduler, AutoCloseable { // basic operations - ScheduledFuture scheduleAtTick(Runnable command, long ticks); + Delayed scheduleAtTick(Runnable command, long ticks); - ScheduledFuture scheduleAtTime(Runnable command, long nanos); + Delayed scheduleAtTime(Runnable command, long nanos); - default ScheduledFuture + default Delayed scheduleAtTime(Runnable command, long time, TimeUnit unit) { return this.scheduleAtTime(command, unit.convert(time, TimeUnit.NANOSECONDS)); } diff --git a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java index 7aca611309c..70a262cc4be 100644 --- a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java +++ b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java @@ -27,20 +27,17 @@ import org.jetbrains.annotations.NotNull; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; import java.util.UUID; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; public class ScheduledTaskEnvelope implements AbstractScheduledTask { private final AbstractScheduler scheduler; - private final TaskProcedure task; private final String name; private final UUID uuid; - private volatile boolean cancelled; + private final AtomicBoolean cancelled = new AtomicBoolean(); volatile Delayed delayed; // init ? ScheduledTaskEnvelope(AbstractScheduler scheduler, @@ -53,13 +50,13 @@ public class ScheduledTaskEnvelope implements AbstractScheduledTask { } @Override public boolean cancel() { - return !(boolean) CANCELLED.getOpaque(this) && - CANCELLED.compareAndSet(this, false, true); + return !this.cancelled.getOpaque() && + this.cancelled.compareAndSet(false, true); } @Override public boolean isCancelled() { - return this.cancelled; + return this.cancelled.get(); } @Override @@ -91,17 +88,4 @@ public long getDelay(@NotNull TimeUnit unit) { public int compareTo(@NotNull Delayed other) { return this.delayed.compareTo(other); } - - // VarHandle mechanic - private static final VarHandle CANCELLED; - - static { - try { - MethodHandles.Lookup l = MethodHandles.lookup(); - CANCELLED = l.findVarHandle(ScheduledTaskEnvelope.class, - "cancelled", boolean.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } } diff --git a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java index 2c3a40034ac..1ccae585ee8 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SpongeScheduler.java @@ -120,7 +120,7 @@ public AbstractScheduledTask submit(Task task, String name) { final long number = this.sequenceNumber.getAndIncrement(); - final UUID uuid = new UUID(number, System.identityHashCode(this)); + final UUID uuid = UUID.randomUUID(); final ScheduledTaskEnvelope sched = new ScheduledTaskEnvelope( this, st, diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index df0afaccd6f..3e31aa89814 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -25,22 +25,14 @@ package org.spongepowered.common.scheduler; import org.jetbrains.annotations.NotNull; - -import java.lang.invoke.VarHandle; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Delayed; -import java.util.concurrent.FutureTask; -import java.util.concurrent.RunnableScheduledFuture; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class SyncScheduler extends SpongeScheduler { - private final BlockingQueue> + private final BlockingQueue ticksQueue = new DelayQueue<>(); - private final BlockingQueue> + private final BlockingQueue timedQueue = new DelayQueue<>(); - private long timestamp; + private volatile long timestamp; private final Time ticked = new Time() { @Override @@ -50,7 +42,6 @@ public boolean tickBased() { @Override public long timeNanos() { - VarHandle.acquireFence(); return SyncScheduler.this.timestamp; } }; @@ -60,28 +51,27 @@ protected SyncScheduler(final String tag) { } @Override - public ScheduledFuture scheduleAtTick(Runnable command, long ticksAsNanos) { + public Delayed scheduleAtTick(Runnable command, long ticksAsNanos) { final Time clock = this.ticked; - final RunnableScheduledFuture f = new SchedFutureTask<>( - command, null, - clock, ticksAsNanos + clock.timeNanos()); + final SchedFutureTask f = new SchedFutureTask( + command, clock, + ticksAsNanos + clock.timeNanos()); this.ticksQueue.add(f); return f; } @Override - public ScheduledFuture scheduleAtTime(Runnable command, long nanos) { + public Delayed scheduleAtTime(Runnable command, long nanos) { final Time clock = Time.REAL_TIME; - final RunnableScheduledFuture f = new SchedFutureTask<>( - command, null, - clock, nanos + clock.timeNanos()); + final SchedFutureTask f = new SchedFutureTask( + command, clock, + nanos + clock.timeNanos()); this.timedQueue.add(f); return f; } public void tick() { this.timestamp += TICK_DURATION_NS; - VarHandle.releaseFence(); for (Runnable task; (task = this.ticksQueue.poll()) != null; task.run()); @@ -95,21 +85,14 @@ public void close() throws Exception { throw new UnsupportedOperationException(); } - private static class SchedFutureTask - extends FutureTask - implements RunnableScheduledFuture { - private final Time clock; - private final long timeStamp; - - SchedFutureTask(Runnable runnable, V result, - Time clock, long timeStamp) { - super(runnable, result); - this.clock = clock; - this.timeStamp = timeStamp; - } + private record SchedFutureTask( + Runnable command, + Time clock, + long timeStamp + ) implements Runnable, Delayed { @Override - public boolean isPeriodic() { - return false; + public void run() { + this.command.run(); } @Override diff --git a/src/main/java/org/spongepowered/common/scheduler/Time.java b/src/main/java/org/spongepowered/common/scheduler/Time.java index 3d3bf6a38a9..4c55fbc59ff 100644 --- a/src/main/java/org/spongepowered/common/scheduler/Time.java +++ b/src/main/java/org/spongepowered/common/scheduler/Time.java @@ -27,7 +27,7 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; -interface Time { +public interface Time { Time ZERO = new RealTime(0); Time REAL_TIME = new Time() { From 5401a7750577b5d5b12ad03150afec8f5a1d9233 Mon Sep 17 00:00:00 2001 From: sunmisc Date: Mon, 11 Mar 2024 23:56:23 +0300 Subject: [PATCH 14/14] formatted --- .../spongepowered/common/scheduler/AbstractScheduler.java | 1 - .../common/scheduler/ScheduledTaskEnvelope.java | 1 + .../org/spongepowered/common/scheduler/SyncScheduler.java | 6 +++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java index 79e7e3e39bd..739f44d0f9e 100644 --- a/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/AbstractScheduler.java @@ -28,7 +28,6 @@ import org.spongepowered.api.scheduler.Task; import java.util.concurrent.Delayed; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; interface AbstractScheduler extends Scheduler, AutoCloseable { diff --git a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java index 70a262cc4be..36ab7938a77 100644 --- a/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java +++ b/src/main/java/org/spongepowered/common/scheduler/ScheduledTaskEnvelope.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; + import java.util.UUID; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java index 3e31aa89814..e1b2f99951b 100644 --- a/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java +++ b/src/main/java/org/spongepowered/common/scheduler/SyncScheduler.java @@ -25,7 +25,11 @@ package org.spongepowered.common.scheduler; import org.jetbrains.annotations.NotNull; -import java.util.concurrent.*; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; public class SyncScheduler extends SpongeScheduler { private final BlockingQueue