This is an automated email from the ASF dual-hosted git repository.

daim pushed a commit to branch OAK-12074
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit fe5c6d1d68a08c32fb3d2bde5e4ca20a7f5befa0
Author: rishabhdaim <[email protected]>
AuthorDate: Tue Jan 27 11:34:45 2026 +0530

    OAK-12074 : added awaitUninterruptibly() in oak-commons
---
 .../internal/concurrent/UninterruptibleUtils.java  | 118 +++++++++++++++++
 .../concurrent/UninterruptibleUtilsTest.java       | 146 +++++++++++++++++++++
 2 files changed, 264 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
new file mode 100644
index 0000000000..fa58291620
--- /dev/null
+++ 
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+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
+ * propagating {@link InterruptedException} to callers.
+ */
+public class UninterruptibleUtils {
+
+    private UninterruptibleUtils() {
+        // no instance for you
+    }
+
+    /**
+     * Waits uninterruptibly until the given {@link CountDownLatch} reaches 
zero.
+     * <p>
+     * This method repeatedly invokes {@link CountDownLatch#await()} and
+     * ignores any {@link InterruptedException} that occurs while waiting,
+     * but remembers that an interruption happened. After the latch has
+     * reached zero (or the method otherwise returns), this method restores
+     * the thread's interrupted status if any interruptions were detected
+     * during the wait.
+     *
+     * @param latch the latch to wait on; must not be {@code null}
+     * @throws NullPointerException if {@code latch} is {@code null}
+     */
+    public static void awaitUninterruptibly(CountDownLatch latch) {
+
+        Objects.requireNonNull(latch, "latch is null");
+
+        boolean interrupted = false;
+        try {
+            for (;;) {
+                try {
+                    latch.await();
+                    return;           // completed normally
+                } catch (InterruptedException e) {
+                    interrupted = true; // remember and retry
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt(); // restore flag
+            }
+        }
+    }
+
+    /**
+     * Waits uninterruptibly until either the given {@link CountDownLatch} 
reaches
+     * zero or the specified waiting time elapses.
+     * <p>
+     * This method behaves like {@link CountDownLatch#await(long, TimeUnit)},
+     * except that it does not throw {@link InterruptedException}. Instead, it
+     * continues waiting when interruptions occur, tracking the remaining time
+     * based on a fixed deadline, and restores the thread's interrupted status
+     * before returning if any interruptions were detected.
+     *
+     * @param latch   the latch to wait on; must not be {@code null}
+     * @param timeout the maximum time to wait; must be non-negative
+     * @param unit    the time unit of the {@code timeout} argument; must not 
be {@code null}
+     * @return {@code true} if the latch reached zero before the timeout 
expired;
+     *         {@code false} if the waiting time elapsed before the latch 
reached zero
+     * @throws NullPointerException     if {@code latch} or {@code unit} is 
{@code null}
+     * @throws IllegalArgumentException if {@code timeout} is negative
+     */
+    public static boolean awaitUninterruptibly(final CountDownLatch latch, 
final long timeout, final TimeUnit unit) {
+
+        Objects.requireNonNull(latch, "latch is null");
+        Objects.requireNonNull(unit, "unit is null");
+
+        if (timeout < 0L) {
+            throw new IllegalArgumentException("timeout must be >= 0");
+        }
+
+        boolean interrupted = false;
+        try {
+            long remainingNanos = unit.toNanos(timeout);
+            long end = System.nanoTime() + remainingNanos;
+            for (;;) {
+                try {
+                    return latch.await(remainingNanos, TimeUnit.NANOSECONDS);
+                } catch (InterruptedException e) {
+                    interrupted = true;
+                    remainingNanos = end - System.nanoTime();
+                    if (remainingNanos <= 0L) {
+                        // Time is up: whether we return true or false depends 
on latch state.
+                        return latch.getCount() == 0L;
+                    }
+                }
+            }
+        } 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
new file mode 100644
index 0000000000..cc478fb251
--- /dev/null
+++ 
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/UninterruptibleUtilsTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.commons.internal.concurrent;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit cases for {@link UninterruptibleUtils}
+ */
+public class UninterruptibleUtilsTest {
+
+    @Test
+    public void testNullLatch() {
+        Assert.assertThrows(NullPointerException.class,
+                () -> UninterruptibleUtils.awaitUninterruptibly(null));
+    }
+
+    @Test
+    public void testWaitsUntilLatchReachesZero() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Thread t = new Thread(() -> 
UninterruptibleUtils.awaitUninterruptibly(latch));
+        t.start();
+
+        // Ensure the thread is actually waiting
+        Thread.sleep(5);
+        Assert.assertTrue(t.isAlive());
+
+        latch.countDown();
+        t.join(10);
+
+        Assert.assertFalse(t.isAlive());
+    }
+
+    @Test
+    public void testSwallowInterruptsButRestoreFlag() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Thread t = new Thread(() -> {
+            UninterruptibleUtils.awaitUninterruptibly(latch);
+            // After returning, interrupted flag should be set if we 
interrupted during wait
+            Assert.assertTrue(Thread.currentThread().isInterrupted());
+        });
+
+        t.start();
+        Thread.sleep(5);
+
+        // Interrupt while it's waiting
+        t.interrupt();
+
+        Thread.sleep(5);
+        latch.countDown();
+        t.join(10);
+
+        Assert.assertFalse(t.isAlive());
+    }
+
+    @Test
+    public void testWithNullTimeoutAndLatch() {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Assert.assertThrows(NullPointerException.class,
+                () -> UninterruptibleUtils.awaitUninterruptibly(null, 1, 
TimeUnit.MILLISECONDS));
+
+        Assert.assertThrows(NullPointerException.class,
+                () -> UninterruptibleUtils.awaitUninterruptibly(latch, 1, 
null));
+    }
+
+    @Test
+    public void testWithNegativeTimeout() {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Assert.assertThrows(IllegalArgumentException.class,
+                () -> UninterruptibleUtils.awaitUninterruptibly(latch, -1, 
TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testWithTimeoutReturnsTrueWhenLatchCountsDownInTime() throws 
Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Thread t = new Thread(() -> {
+            boolean result = UninterruptibleUtils.awaitUninterruptibly(latch, 
1, TimeUnit.MILLISECONDS);
+            Assert.assertTrue(result);
+        });
+
+        t.start();
+        Thread.sleep(10);
+        latch.countDown();
+        t.join(20);
+
+        Assert.assertFalse(t.isAlive());
+    }
+
+    @Test
+    public void testWithTimeoutReturnsFalseWhenTimeoutExpires() {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        long start = System.nanoTime();
+        boolean result = UninterruptibleUtils.awaitUninterruptibly(latch, 100, 
TimeUnit.MILLISECONDS);
+        long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - 
start);
+
+        Assert.assertFalse(result);
+        Assert.assertTrue("Elapsed time should be close to timeout", 
elapsedMillis >= 90L);
+    }
+
+    @Test
+    public void testWithTimeoutSwallowInterruptsButRestoreFlag() throws 
Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Thread t = new Thread(() -> {
+            boolean result = UninterruptibleUtils.awaitUninterruptibly(latch, 
500, TimeUnit.MILLISECONDS);
+            // Might be true or false depending on when we count down, but 
interrupt flag must be set.
+            Assert.assertTrue(Thread.currentThread().isInterrupted());
+        });
+
+        t.start();
+        Thread.sleep(5);
+        t.interrupt();
+        Thread.sleep(5);
+        latch.countDown();
+        t.join(20);
+
+        Assert.assertFalse(t.isAlive());
+    }
+
+}
\ No newline at end of file

Reply via email to