This is an automated email from the ASF dual-hosted git repository.
daim pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 7febbe51f5 OAK-12077 : added joinUninterruptibly() in oak-commons
(#2711)
7febbe51f5 is described below
commit 7febbe51f5c55d1efbdc82528e307bcab8936ad5
Author: Rishabh Kumar <[email protected]>
AuthorDate: Fri Jan 30 15:49:54 2026 +0530
OAK-12077 : added joinUninterruptibly() in oak-commons (#2711)
* OAK-12077 : added joinUninterruptibly() in oak-commons
* OAK-12077 : added review comments
* OAK-12077 : updated sleep durations to sleep within joining period
* OAK-12077 : fixed assertion errors if occured in joiningThread
---
.../internal/concurrent/UninterruptibleUtils.java | 35 ++++++
.../concurrent/UninterruptibleUtilsTest.java | 123 +++++++++++++++++++++
2 files changed, 158 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..5093ac0de3 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,39 @@ 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}
+ * @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) {
+
+ Objects.requireNonNull(toJoin, "thread to join is null");
+
+ boolean interrupted = false;
+
+ try {
+ for(;;) {
+ try {
+ toJoin.join();
+ return;
+ } catch (InterruptedException var6) {
+ interrupted = true;
+ }
+ }
+ } 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..788fe2c3fa 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
@@ -23,6 +23,7 @@ import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Unit cases for {@link UninterruptibleUtils}
@@ -162,4 +163,126 @@ public class UninterruptibleUtilsTest {
Assert.assertTrue("Zero sleep should return quickly", elapsedMillis <
50L);
}
+ @Test
+ public void testNullThread() {
+ Assert.assertThrows(NullPointerException.class,
+ () -> UninterruptibleUtils.joinUninterruptibly(null));
+ }
+
+ @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);
+ 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 testJoinShouldWaitUntilThreadFinishes() {
+ final Thread worker = new Thread(() -> {
+ try {
+ Thread.sleep(20L);
+ } catch (InterruptedException ignored) {
+ }
+ });
+
+ worker.start();
+
+ long start = System.nanoTime();
+ UninterruptibleUtils.joinUninterruptibly(worker);
+ long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() -
start);
+
+ Assert.assertTrue("Join should respect timeout", elapsedMillis >= 20L
);
+ }
+
+ @Test
+ public void testJoinUninterruptiblyIgnoresInterruptsButRestoresFlag()
throws Exception {
+ final Thread worker = new Thread(() -> {
+ try {
+ Thread.sleep(200L);
+ } catch (InterruptedException ignored) {
+ }
+ });
+
+ worker.start();
+
+ final AtomicReference<Throwable> t = new AtomicReference<>();
+
+ Thread joiningThread = new Thread(() -> {
+ try {
+ UninterruptibleUtils.joinUninterruptibly(worker);
+ // After returning, interrupted flag should be set if we
interrupted during join
+ Assert.assertTrue("Interrupt flag should be restored",
Thread.currentThread().isInterrupted());
+ } catch (Throwable e) {
+ t.set(e);
+ }
+ });
+
+ joiningThread.start();
+
+ // Let the joining thread enter join
+ Thread.sleep(5L);
+
+ // Interrupt while it is joining
+ joiningThread.interrupt();
+
+ joiningThread.join();
+
+ // fail if any exception occurred in the thread
+ if (t.get() != null) {
+ Assert.fail("Got exception: " + t.get());
+ }
+ }
+
+ @Test
+ public void
testJoinUninterruptiblyMultipleInterruptsStillCompleteAndRestoreFlag() throws
Exception {
+ final Thread worker = new Thread(() -> {
+ try {
+ Thread.sleep(300L);
+ } catch (InterruptedException ignored) {
+ }
+ });
+
+ worker.start();
+
+ final AtomicReference<Throwable> t = new AtomicReference<>();
+
+ Thread joiningThread = new Thread(() -> {
+ try {
+ UninterruptibleUtils.joinUninterruptibly(worker);
+ Assert.assertTrue("Interrupt flag should be restored after
multiple interrupts",
+ Thread.currentThread().isInterrupted());
+ } catch (Throwable e) {
+ t.set(e);
+ }
+ });
+
+ joiningThread.start();
+
+ // Interrupt the joining thread multiple times while it is waiting
+ for (int i = 0; i < 3; i++) {
+ Thread.sleep(5L);
+ joiningThread.interrupt();
+ }
+
+ joiningThread.join();
+
+ // fail if any exception occurred in the thread
+ if (t.get() != null) {
+ Assert.fail("Got exception: " + t.get());
+ }
+ }
+
}
\ No newline at end of file