From 5fa946d612b4a3f542f1590bd2d921a743cd614e Mon Sep 17 00:00:00 2001 From: Project516 <138796702+Project516@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:32:39 -0600 Subject: [PATCH] Re-arm host timers for waits longer than a timer can hold, so park, wait and sleep with huge timeouts no longer wake early or spin --- src/natives/lang.ts | 11 +++++--- test/java/LongTimeouts.java | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 test/java/LongTimeouts.java diff --git a/src/natives/lang.ts b/src/natives/lang.ts index bafd353..687319e 100644 --- a/src/natives/lang.ts +++ b/src/natives/lang.ts @@ -43,13 +43,18 @@ export function interruptibleWait(t: JThread, ms: number | null, register?: (wak }; t.wake = done; register?.(done); - if (ms !== null) { - timer = setTimeout(done, Math.max(0, ms)); + // timers fire at once past MAX_DELAY, so a longer wait re-arms + const arm = (left: number) => { + const step = Math.min(Math.max(0, left), MAX_DELAY); + timer = setTimeout(step < left ? () => { t.vm.timers.delete(timer!); arm(left - step); } : done, step); t.vm.timers.add(timer); - } + }; + if (ms !== null) arm(ms); }); } +const MAX_DELAY = 0x7fffffff; + function* waitImpl(t: JThread, self: JObject, ms: bigint): NativeGen { const vm = t.vm; const mon = self.mon; diff --git a/test/java/LongTimeouts.java b/test/java/LongTimeouts.java new file mode 100644 index 0000000..246b07d --- /dev/null +++ b/test/java/LongTimeouts.java @@ -0,0 +1,50 @@ +import java.util.concurrent.locks.LockSupport; + +// Timeouts longer than a host timer can hold must not wake early. +public class LongTimeouts { + static volatile boolean interrupting; + + static String how(boolean interrupted) { + return interrupting && interrupted ? "interrupt" : "early"; + } + + public static void main(String[] args) throws Exception { + String[] result = new String[4]; + Object lock = new Object(); + Thread[] threads = { + new Thread(() -> { + LockSupport.parkNanos(Long.MAX_VALUE); + result[0] = how(Thread.currentThread().isInterrupted()); + }), + new Thread(() -> { + LockSupport.parkUntil(Long.MAX_VALUE); + result[1] = how(Thread.currentThread().isInterrupted()); + }), + new Thread(() -> { + synchronized (lock) { + try { + lock.wait(Long.MAX_VALUE / 2); + result[2] = "early"; + } catch (InterruptedException e) { + result[2] = how(true); + } + } + }), + new Thread(() -> { + try { + Thread.sleep(Long.MAX_VALUE); + result[3] = "early"; + } catch (InterruptedException e) { + result[3] = how(true); + } + }), + }; + for (Thread t : threads) t.start(); + Thread.sleep(300); + interrupting = true; + for (Thread t : threads) t.interrupt(); + for (Thread t : threads) t.join(); + String[] names = { "parkNanos", "parkUntil", "wait", "sleep" }; + for (int i = 0; i < 4; i++) System.out.println(names[i] + ": " + result[i]); + } +}