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/29 05:50:15 UTC

(jackrabbit-oak) branch OAK-12077 created (now 0b41916e5d)

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

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


      at 0b41916e5d OAK-12077 : added joinUninterruptibly() in oak-commons

This branch includes the following new commits:

     new 0b41916e5d OAK-12077 : added joinUninterruptibly() 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-12077 : added joinUninterruptibly() 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-12077
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit 0b41916e5d9a6a1a9445e4e1e683bac2c7b3a097
Author: rishabhdaim <ri...@gmail.com>
AuthorDate: Thu Jan 29 11:20:00 2026 +0530

    OAK-12077 : added joinUninterruptibly() in oak-commons
---
 .../internal/concurrent/UninterruptibleUtils.java  |  46 ++++++++
 .../concurrent/UninterruptibleUtilsTest.java       | 123 +++++++++++++++++++++
 2 files changed, 169 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 6dcb24125b..784480db5d 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
@@ -110,4 +110,50 @@ public class UninterruptibleUtils {
             }
         }
     }
+
+    /**
+     * Invokes {@link TimeUnit#timedJoin(Thread, long)} uninterruptibly.
+     * <p>
+     * This method repeatedly calls {@link TimeUnit#timedJoin(Thread, long)} until the
+     * specified timeout has elapsed or the target thread terminates, ignoring
+     * {@link InterruptedException} but remembering that an interruption
+     * occurred. When the method finally returns, it restores the current
+     * thread's interrupted status if any interruptions were detected.
+     *
+     * @param toJoin the thread to wait for; 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}
+     * @throws NullPointerException if {@code toJoin} or {@code unit} is {@code null}
+     * @throws IllegalArgumentException if {@code timeout} is negative
+     */
+    public static void joinUninterruptibly(final Thread toJoin, final long timeout, final TimeUnit unit) {
+
+        Objects.requireNonNull(toJoin, "thread to join is null");
+        Objects.requireNonNull(unit, "timeunit 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 {
+                    TimeUnit.NANOSECONDS.timedJoin(toJoin, remainingNanos);
+                    return; // either timeout elapsed or thread finished
+                } 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 1029a34a96..1a127cffa0 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
@@ -162,4 +162,127 @@ public class UninterruptibleUtilsTest {
         Assert.assertTrue("Zero sleep should return quickly", elapsedMillis < 50L);
     }
 
+    @Test
+    public void testNullThread() {
+        Assert.assertThrows(NullPointerException.class,
+                () -> UninterruptibleUtils.joinUninterruptibly(null, 1L, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testNullUnit() {
+        Assert.assertThrows(NullPointerException.class,
+                () -> UninterruptibleUtils.joinUninterruptibly(Thread.currentThread(), 1L, null));
+    }
+
+    @Test
+    public void testNegativeTimeout() {
+        Assert.assertThrows(IllegalArgumentException.class,
+                () -> UninterruptibleUtils.joinUninterruptibly(Thread.currentThread(), -1L, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testReturnsWhenThreadFinishesBeforeTimeout() throws Exception {
+        final long workMillis = 10L;
+        final Thread worker = new Thread(() -> {
+            try {
+                Thread.sleep(workMillis);
+            } catch (InterruptedException ignored) {}
+        });
+
+        worker.start();
+
+        long start = System.nanoTime();
+        UninterruptibleUtils.joinUninterruptibly(worker, 100L, TimeUnit.MILLISECONDS);
+        long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
+
+        Assert.assertFalse("Worker should be finished", worker.isAlive());
+        Assert.assertTrue("Join should not take excessively long",
+                elapsedMillis >= workMillis && elapsedMillis < 100L);
+    }
+
+    @Test
+    public void testTimesOutIfThreadDoesNotFinish() {
+        final Thread worker = new Thread(() -> {
+            // Run longer than the join timeout
+            try {
+                Thread.sleep(20L);
+            } catch (InterruptedException ignored) {
+            }
+        });
+
+        worker.start();
+
+        long timeoutMillis = 10L;
+        long start = System.nanoTime();
+        UninterruptibleUtils.joinUninterruptibly(worker, timeoutMillis, TimeUnit.MILLISECONDS);
+        long elapsedMillis =
+                TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
+
+        // Worker may still be alive (likely), but we at least checked the timeout behavior
+        Assert.assertTrue("Join should respect timeout",
+                elapsedMillis >= timeoutMillis && elapsedMillis < 20L);
+    }
+
+    @Test
+    public void testJoinUninterruptiblyIgnoresInterruptsButRestoresFlag() throws Exception {
+        final Thread worker = new Thread(() -> {
+            try {
+                Thread.sleep(20L);
+            } catch (InterruptedException ignored) {
+            }
+        });
+
+        worker.start();
+
+        Thread joiningThread = new Thread(() -> {
+            UninterruptibleUtils.joinUninterruptibly(worker, 50L, TimeUnit.MILLISECONDS);
+            // After returning, interrupted flag should be set if we interrupted during join
+            Assert.assertTrue("Interrupt flag should be restored", Thread.currentThread().isInterrupted());
+        });
+
+        joiningThread.start();
+
+        // Let the joining thread enter join
+        Thread.sleep(50L);
+
+        // Interrupt while it is joining
+        joiningThread.interrupt();
+
+        joiningThread.join(200L);
+
+        Assert.assertFalse("Joining thread should have completed", joiningThread.isAlive());
+        Assert.assertFalse("Worker should have completed", worker.isAlive());
+    }
+
+    @Test
+    public void testJoinUninterruptiblyMultipleInterruptsStillCompleteAndRestoreFlag() throws Exception {
+        final Thread worker = new Thread(() -> {
+            try {
+                Thread.sleep(30L);
+            } catch (InterruptedException ignored) {
+            }
+        });
+
+        worker.start();
+
+        Thread joiningThread = new Thread(() -> {
+            UninterruptibleUtils.joinUninterruptibly(worker, 1L, TimeUnit.SECONDS);
+            Assert.assertTrue("Interrupt flag should be restored after multiple interrupts",
+                    Thread.currentThread().isInterrupted());
+        });
+
+        joiningThread.start();
+
+        // Interrupt the joining thread multiple times while it is waiting
+        for (int i = 0; i < 3; i++) {
+            Thread.sleep(50L);
+            joiningThread.interrupt();
+        }
+
+        joiningThread.join(2000L);
+
+        Assert.assertFalse("Joining thread should have completed", joiningThread.isAlive());
+        Assert.assertFalse("Worker should have completed", worker.isAlive());
+    }
+
 }
\ No newline at end of file