From d1ba45efa844de10f630719297184b7c5ccad6f5 Mon Sep 17 00:00:00 2001 From: Jacob Abrams Date: Thu, 1 Dec 2022 11:22:07 -0800 Subject: [PATCH] Support negative delays in ScheduledFutures executed by the DeterministicScheduler --- .../lib/concurrent/DeterministicScheduler.java | 16 +++++++++++++--- .../lib/concurrent/internal/DeltaQueue.java | 6 +++--- .../concurrent/DeterministicSchedulerTests.java | 16 +++++++++++++++- .../lib/concurrent/internal/DeltaQueueTests.java | 4 ++-- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/jmock/src/main/java/org/jmock/lib/concurrent/DeterministicScheduler.java b/jmock/src/main/java/org/jmock/lib/concurrent/DeterministicScheduler.java index bca48b096..b98eb22af 100644 --- a/jmock/src/main/java/org/jmock/lib/concurrent/DeterministicScheduler.java +++ b/jmock/src/main/java/org/jmock/lib/concurrent/DeterministicScheduler.java @@ -27,7 +27,8 @@ */ public class DeterministicScheduler implements ScheduledExecutorService { private final DeltaQueue> deltaQueue = new DeltaQueue>(); - + private long passedTicks = 0; + /** * Runs time forwards by a given duration, executing any commands scheduled for * execution during that time period, and any background tasks spawned by the @@ -39,12 +40,15 @@ public class DeterministicScheduler implements ScheduledExecutorService { */ public void tick(long duration, TimeUnit timeUnit) { long remaining = toTicks(duration, timeUnit); + long total = remaining; do { remaining = deltaQueue.tick(remaining); + passedTicks += (total - remaining); runUntilIdle(); - } while (deltaQueue.isNotEmpty() && remaining > 0); + + passedTicks += remaining; } /** @@ -185,6 +189,7 @@ private final class ScheduledTask implements ScheduledFuture, Runnable { private boolean isDone = false; private T futureResult; private Exception failure = null; + private long ranAtTicks; public ScheduledTask(Callable command) { this.repeatDelay = -1; @@ -210,7 +215,11 @@ public boolean repeats() { } public long getDelay(TimeUnit unit) { - return unit.convert(deltaQueue.delay(this), TimeUnit.MILLISECONDS); + Long delay = deltaQueue.delay(this); + if (delay == null) { + delay = ranAtTicks - passedTicks; + } + return unit.convert(delay, TimeUnit.MILLISECONDS); } public int compareTo(Delayed o) { @@ -247,6 +256,7 @@ public boolean isDone() { } public void run() { + ranAtTicks = passedTicks; try { futureResult = command.call(); } diff --git a/jmock/src/main/java/org/jmock/lib/concurrent/internal/DeltaQueue.java b/jmock/src/main/java/org/jmock/lib/concurrent/internal/DeltaQueue.java index 69e89172a..30929fb47 100644 --- a/jmock/src/main/java/org/jmock/lib/concurrent/internal/DeltaQueue.java +++ b/jmock/src/main/java/org/jmock/lib/concurrent/internal/DeltaQueue.java @@ -30,8 +30,8 @@ public T next() { public long delay() { return head.delay; } - - public long delay(T element) { + + public Long delay(T element) { long ret = 0; Node next = head; while (next != null) { @@ -42,7 +42,7 @@ public long delay(T element) { next = next.next; } if (next == null) { - return -1; + return null; } return ret; } diff --git a/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/DeterministicSchedulerTests.java b/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/DeterministicSchedulerTests.java index 56846dabf..03889ac32 100644 --- a/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/DeterministicSchedulerTests.java +++ b/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/DeterministicSchedulerTests.java @@ -307,7 +307,21 @@ public void testCannotBlockWaitingForFutureResultOfScheduledCallable() throws Ex } catch (UnsupportedSynchronousOperationException expected) {} } - + + public void testCanGetDelayAfterExecution() { + ScheduledFuture task1 = scheduler.schedule(commandA, 1, TimeUnit.SECONDS); + + checking(new Expectations() {{ + oneOf (commandA).run(); + }}); + + scheduler.tick(10, TimeUnit.SECONDS); + + // Per getDelay documentation it returns: the remaining delay; zero or + // negative values indicate that the delay has already elapsed + assertEquals(-9, task1.getDelay(TimeUnit.SECONDS)); + } + private Action schedule(final Runnable command) { return ScheduleOnExecutorAction.schedule(scheduler, command); } diff --git a/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/internal/DeltaQueueTests.java b/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/internal/DeltaQueueTests.java index 94c6efc77..eee5e98d1 100644 --- a/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/internal/DeltaQueueTests.java +++ b/jmock/src/test/java/org/jmock/test/unit/lib/concurrent/internal/DeltaQueueTests.java @@ -168,10 +168,10 @@ public void testReturnsFalseIfElementAlreadyRemoved() { assertFalse(deltaQueue.remove(elementC)); } - public void testDelayIsMinusOneWhenElementIsAlreadyRemoved() { + public void testDelayIsNullWhenElementIsAlreadyRemoved() { deltaQueue.add(1L, elementA); deltaQueue.remove(elementA); - assertEquals("delay", -1, deltaQueue.delay(elementA)); + assertNull("delay", deltaQueue.delay(elementA)); } }