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");
+    }
+
 }

Reply via email to