This is an automated email from the ASF dual-hosted git repository. daim pushed a commit to branch OAK-12076 in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit feb3258703d59ed7ffb0da2713875a556b029380 Author: rishabhdaim <[email protected]> AuthorDate: Tue Jan 27 19:34:37 2026 +0530 OAK-12076 : added sleepUninterruptibly() in oak-commons --- .../internal/concurrent/UninterruptibleUtils.java | 46 +++++++++++ .../concurrent/UninterruptibleUtilsTest.java | 88 ++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtils.java b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtils.java index dfd908469e..6dcb24125b 100644 --- a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtils.java +++ b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtils.java @@ -20,6 +20,7 @@ package org.apache.jackrabbit.oak.commons.internal.concurrent; import java.util.Objects; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Utility methods for waiting on synchronization primitives without @@ -64,4 +65,49 @@ public class UninterruptibleUtils { } } } + + /** + * Causes the current thread to sleep for the specified duration, + * ignoring {@link InterruptedException} until the full sleep time + * has elapsed, then restores the interrupted status before returning. + * <p> + * This behaves like Guava's + * {@code Uninterruptibles.sleepUninterruptibly(long, TimeUnit)}: + * it repeatedly invokes {@link TimeUnit#sleep(long)} until the + * requested time has passed, catching and recording interruptions + * and recomputing the remaining time from a fixed deadline. + * + * @param sleep the time to sleep; must be non-negative + * @param unit the time unit of the {@code sleep} argument; must not be {@code null} + * @throws NullPointerException if {@code unit} is {@code null} + * @throws IllegalArgumentException if {@code sleep} is negative + */ + public static void sleepUninterruptibly(final long sleep, final TimeUnit unit) { + + Objects.requireNonNull(unit, "timeunit is null"); + + if (sleep < 0L) { + throw new IllegalArgumentException("sleep must be >= 0"); + } + + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(sleep); + long end = System.nanoTime() + remainingNanos; + for (;;) { + try { + // TimeUnit.sleep() treats negative timeouts just like zero. + TimeUnit.NANOSECONDS.sleep(remainingNanos); + return; + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } } diff --git a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtilsTest.java b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtilsTest.java index 45f945c9a1..1029a34a96 100644 --- a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtilsTest.java +++ b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtilsTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Unit cases for {@link UninterruptibleUtils} @@ -74,4 +75,91 @@ public class UninterruptibleUtilsTest { Assert.assertFalse(t.isAlive()); } + @Test + public void testNullTimeUnit() { + Assert.assertThrows(NullPointerException.class, () -> UninterruptibleUtils.sleepUninterruptibly(1L, null)); + } + + @Test(expected = IllegalArgumentException.class) + public void sleepUninterruptibly_negativeSleepThrowsIae() { + UninterruptibleUtils.sleepUninterruptibly(-1L, TimeUnit.MILLISECONDS); + } + + @Test + public void testSleepsForAtLeastRequestedTime() { + long sleepMillis = 20L; + long start = System.nanoTime(); + + UninterruptibleUtils.sleepUninterruptibly(sleepMillis, TimeUnit.MILLISECONDS); + + long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + + Assert.assertTrue("Elapsed should be at least requested sleep", + elapsedMillis >= sleepMillis - 10); // small margin + } + + @Test + public void testIgnoresInterruptsButRestoresFlag() throws Exception { + final long sleepMillis = 20L; + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + UninterruptibleUtils.sleepUninterruptibly(sleepMillis, TimeUnit.MILLISECONDS); + // After returning, interrupted flag should be set + Assert.assertTrue("Interrupt flag should be restored", + Thread.currentThread().isInterrupted()); + } + }); + + t.start(); + + // Let the thread enter sleep + Thread.sleep(5); + + // Interrupt during sleep + t.interrupt(); + + t.join(20); + + Assert.assertFalse("Thread should have completed sleep", t.isAlive()); + } + + @Test + public void testMultipleInterruptsStillCompleteAndRestoreFlag() throws Exception { + final long sleepMillis = 20L; + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + UninterruptibleUtils.sleepUninterruptibly(sleepMillis, TimeUnit.MILLISECONDS); + Assert.assertTrue("Interrupt flag should be restored after multiple interrupts", + Thread.currentThread().isInterrupted()); + } + }); + + t.start(); + + // Interrupt the thread multiple times while it is sleeping + for (int i = 0; i < 3; i++) { + Thread.sleep(5); + t.interrupt(); + } + + t.join(20); + + Assert.assertFalse("Thread should have completed sleep", t.isAlive()); + } + + @Test + public void testZeroSleepReturnsQuickly() { + long start = System.nanoTime(); + + UninterruptibleUtils.sleepUninterruptibly(0L, TimeUnit.MILLISECONDS); + + long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + + Assert.assertTrue("Zero sleep should return quickly", elapsedMillis < 50L); + } + } \ No newline at end of file
