You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2026/01/27 06:05:03 UTC

(jackrabbit-oak) branch OAK-12074 created (now fe5c6d1d68)

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

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


      at fe5c6d1d68 OAK-12074 : added awaitUninterruptibly() in oak-commons

This branch includes the following new commits:

     new fe5c6d1d68 OAK-12074 : added awaitUninterruptibly() in oak-commons

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(jackrabbit-oak) 01/01: OAK-12074 : added awaitUninterruptibly() in oak-commons

Posted by da...@apache.org.
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 <ri...@gmail.com>
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