From 468616267b8def70741ad3d0df1734fd50245a72 Mon Sep 17 00:00:00 2001 From: yunuservices Date: Fri, 20 Feb 2026 15:49:23 +0300 Subject: [PATCH 1/2] Fix scheduler cancellation/state visibility and NUMA mapping --- .../concurrentutil/numa/LinuxNuma.java | 2 +- .../scheduler/EDFSchedulerThreadPool.java | 21 ++++++++++++------- .../scheduler/SchedulableTick.java | 8 +++++-- .../StealingScheduledThreadPool.java | 15 +++++++------ 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/ca/spottedleaf/concurrentutil/numa/LinuxNuma.java b/src/main/java/ca/spottedleaf/concurrentutil/numa/LinuxNuma.java index 0169c09..c6849ed 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/numa/LinuxNuma.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/numa/LinuxNuma.java @@ -56,8 +56,8 @@ public final class LinuxNuma extends OSNuma.PreCalculatedNuma { // it is set, so mark it in the core mapping if (coreToNuma.length <= cpu) { coreToNuma = Arrays.copyOf(coreToNuma, cpu + 1); - coreToNuma[cpu] = node; } + coreToNuma[cpu] = node; } } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java index c818374..d75b0e8 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java @@ -226,6 +226,10 @@ private ScheduledState returnTask(final TickThreadRunner runner, final Scheduled @Override public void schedule(final SchedulableTick task) { synchronized (this.scheduleLock) { + if (task.getScheduledStart() == TimeUtil.DEADLINE_NOT_SET) { + throw new IllegalStateException("Start must be set when scheduling"); + } + final ScheduledState state = new ScheduledState(task); if (!task.setState(state)) { throw new IllegalStateException("Task " + task + " is already scheduled or cancelled"); @@ -284,15 +288,18 @@ public boolean updateTickStartToMax(final ScheduledState task, final long newSta @Override public boolean cancel(final SchedulableTick task) { - if (!(task.state instanceof ScheduledState state)) { - return false; - } - - if (state.schedulerOwnedBy != this) { + if (!(task.getState() instanceof ScheduledState state)) { return false; } synchronized (this.scheduleLock) { + if (state.schedulerOwnedBy != this) { + return false; + } + if (!state.tryMarkCancelled()) { + return false; + } + if (this.queued.remove(state)) { // cancelled, and no runner owns it - so return return true; @@ -315,8 +322,8 @@ public boolean cancel(final SchedulableTick task) { return true; } - // could not find it in queue - return false; + // the task may currently be running; cancellation is applied by state transition + return true; } } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulableTick.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulableTick.java index 1d9676a..986d1f2 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulableTick.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulableTick.java @@ -32,7 +32,11 @@ public abstract class SchedulableTick { long scheduledStart = TimeUtil.DEADLINE_NOT_SET; - Object state; + private volatile Object state; + + final Object getState() { + return this.state; + } boolean setState(final Object state) { synchronized (this) { @@ -79,7 +83,7 @@ protected final void setScheduledStart(final long value) { public String toString() { return "SchedulableTick:{" + "class=" + this.getClass().getName() + "," + - "state=" + this.state + "," + "state=" + this.getState() + "," + "}"; } } diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/StealingScheduledThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/StealingScheduledThreadPool.java index 2ede990..c1ce45a 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/StealingScheduledThreadPool.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/StealingScheduledThreadPool.java @@ -68,7 +68,7 @@ public OSNuma getNuma() { } private static ScheduledState getState(final SchedulableTick tick) { - return (ScheduledState)tick.state; + return (ScheduledState)tick.getState(); } private static Thread[] getThreads(final COWArrayList runners) { @@ -274,7 +274,8 @@ private TickThreadRunner selectRunner(final TickThreadRunner previousRunner, fin int selectedSize = Integer.MAX_VALUE; for (final NodeThreads node : nodes) { - final int distance = this.numa.getNumaDistance(currentNode, node.nodeNumber); + final int rawDistance = this.numa.getNumaDistance(currentNode, node.nodeNumber); + final int distance = rawDistance <= 0 ? Integer.MAX_VALUE : rawDistance; if (distance > selectedDistance) { continue; } @@ -298,7 +299,8 @@ private TickThreadRunner selectRunner(final TickThreadRunner previousRunner, fin int selectedDistance = Integer.MAX_VALUE; for (final NodeThreads node : nodes) { - final int distance = this.numa.getNumaDistance(currentNode, node.nodeNumber); + final int rawDistance = this.numa.getNumaDistance(currentNode, node.nodeNumber); + final int distance = rawDistance <= 0 ? Integer.MAX_VALUE : rawDistance; for (final TickThreadRunner runner : node.threads) { // yes the size is just a rough guess... final int size = runner.tickQueue.size(); @@ -350,14 +352,14 @@ public void schedule(final SchedulableTick tick) { @Override public void notifyTasks(final SchedulableTick tick) { - if (tick.state instanceof ScheduledState state) { + if (tick.getState() instanceof ScheduledState state) { state.scheduleTasks(); } } @Override public boolean cancel(final SchedulableTick tick) { - if (tick.state instanceof ScheduledState state) { + if (tick.getState() instanceof ScheduledState state) { return state.tryCancel(); } else { return false; @@ -915,7 +917,8 @@ private void tryStealTask() { TickThreadRunner selectedRunner = null; for (final NodeThreads node : this.nodes) { - final int distance = this.scheduler.numa.getNumaDistance(this.node.nodeNumber, node.nodeNumber); + final int rawDistance = this.scheduler.numa.getNumaDistance(this.node.nodeNumber, node.nodeNumber); + final int distance = rawDistance <= 0 ? Integer.MAX_VALUE : rawDistance; for (final TickThreadRunner runner : node.threads) { if (runner == this) { From e8972bedfc08cdf4baf9143db701dd3e98f9371b Mon Sep 17 00:00:00 2001 From: yunuservices Date: Sat, 21 Feb 2026 09:56:52 +0300 Subject: [PATCH 2/2] Align EDF cancel fallback with failed state-transition semantics --- .../concurrentutil/scheduler/EDFSchedulerThreadPool.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java index d75b0e8..7f13cf9 100644 --- a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java +++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/EDFSchedulerThreadPool.java @@ -322,8 +322,8 @@ public boolean cancel(final SchedulableTick task) { return true; } - // the task may currently be running; cancellation is applied by state transition - return true; + // could not find it in queue + return false; } }