This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new fa3924049 [LANG-1817] UncheckedFutureImpl clears thread interrupt
status when wrapping InterruptedException.
fa3924049 is described below
commit fa392404994e3e505317f2babf78560363da2330
Author: Gary Gregory <[email protected]>
AuthorDate: Sat Feb 7 14:56:12 2026 -0500
[LANG-1817] UncheckedFutureImpl clears thread interrupt status when
wrapping InterruptedException.
Partial application of patch #1590.
---
src/changes/changes.xml | 1 +
.../lang3/concurrent/UncheckedFutureImpl.java | 2 +
.../lang3/concurrent/UncheckedFutureTest.java | 64 ++++++++++++++++++++++
3 files changed, 67 insertions(+)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index f50bd0b2d..959376748 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -114,6 +114,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Ivan
Ponomarev, Gary Gregory">Fix StringIndexOutOfBoundsException message in
StrBuilder.append(char[], int, int).</action>
<action issue="LANG-1818" type="fix" dev="ggregory" due-to="Ivan
Ponomarev, Gary Gregory">Fix ClassUtils.getShortClassName(Class) to correctly
handle $ in valid class names #1591.</action>
<action type="fix" dev="ggregory" due-to="Ivan
Ponomarev, Gary Gregory">ThreadUtils.sleepQuietly(Duration) now restores the
current thread's interrupt flag when catching InterruptedException.</action>
+ <action issue="LANG-1817" type="fix" dev="ggregory" due-to="Ivan
Ponomarev, Gary Gregory">UncheckedFutureImpl clears thread interrupt status
when wrapping InterruptedException #1590.</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add JavaVersion.JAVA_27.</action>
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add SystemUtils.IS_JAVA_27.</action>
diff --git
a/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java
b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java
index 26c42fc34..6b92e8e71 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/UncheckedFutureImpl.java
@@ -42,6 +42,7 @@ public V get() {
try {
return super.get();
} catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
throw new UncheckedInterruptedException(e);
} catch (final ExecutionException e) {
throw new UncheckedExecutionException(e);
@@ -53,6 +54,7 @@ public V get(final long timeout, final TimeUnit unit) {
try {
return super.get(timeout, unit);
} catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
throw new UncheckedInterruptedException(e);
} catch (final ExecutionException e) {
throw new UncheckedExecutionException(e);
diff --git
a/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java
b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java
index ab6704804..db8f59fda 100644
--- a/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java
+++ b/src/test/java/org/apache/commons/lang3/concurrent/UncheckedFutureTest.java
@@ -18,14 +18,20 @@
package org.apache.commons.lang3.concurrent;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.lang3.AbstractLangTest;
@@ -121,4 +127,62 @@ void testOnFuture() {
assertEquals("Z", UncheckedFuture.on(new TestFuture<>("Z")).get());
}
+
+ @Test
+ void interruptFlagIsPreservedOnGet() throws Exception {
+ assertInterruptPreserved(UncheckedFuture::get);
+ }
+
+ @Test
+ void interruptFlagIsPreservedOnGetWithTimeout() throws Exception {
+ assertInterruptPreserved(uf -> uf.get(1, TimeUnit.DAYS));
+ }
+
+ private static void
assertInterruptPreserved(Consumer<UncheckedFuture<Integer>> call) throws
Exception {
+ final CountDownLatch enteredGet = new CountDownLatch(1);
+ final Future<Integer> blockingFuture = new
AbstractFutureProxy<Integer>(ConcurrentUtils.constantFuture(42)) {
+ private final CountDownLatch neverRelease = new CountDownLatch(1);
+
+ @Override
+ public Integer get() throws InterruptedException {
+ enteredGet.countDown();
+ neverRelease.await();
+ throw new AssertionError("We should not get here");
+ }
+
+ @Override
+ public Integer get(long timeout, TimeUnit unit) throws
InterruptedException {
+ enteredGet.countDown();
+ neverRelease.await();
+ throw new AssertionError("We should not get here");
+ }
+
+ @Override
+ public boolean isDone() {
+ return false;
+ }
+
+ };
+ final UncheckedFuture<Integer> uf = UncheckedFuture.on(blockingFuture);
+ final AtomicReference<Throwable> thrown = new AtomicReference<>();
+ final AtomicBoolean interruptObserved = new AtomicBoolean(false);
+ final Thread worker = new Thread(() -> {
+ try {
+ call.accept(uf);
+ thrown.set(new AssertionError("We should not get here"));
+ } catch (Throwable e) {
+ interruptObserved.set(Thread.currentThread().isInterrupted());
+ thrown.set(e);
+ }
+ }, "unchecked-future-test-worker");
+ worker.start();
+ assertTrue(enteredGet.await(2, TimeUnit.SECONDS), "Worker did not
enter Future.get() in time");
+ worker.interrupt();
+ worker.join();
+ final Throwable t = thrown.get();
+ assertInstanceOf(UncheckedInterruptedException.class, t, "Unexpected
exception: " + t);
+ assertInstanceOf(InterruptedException.class, t.getCause(), "Cause
should be InterruptedException");
+ assertTrue(interruptObserved.get(), "Interrupt flag was not restored
by the wrapper");
+ }
+
}