Skip to content

Commit 3fb551f

Browse files
committed
Review comments
1 parent 10f2041 commit 3fb551f

File tree

2 files changed

+124
-7
lines changed

2 files changed

+124
-7
lines changed

http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/utils/CancelOnInterruptWrapper.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,36 @@ public boolean isDone() {
4848
return f.isDone();
4949
}
5050

51+
52+
// This method attempts to cancel the wrapped future if the thread is interrupted while blocked on get(). This is done by
53+
// attempting to cancel() the future when InterruptedException is thrown. If the the cancel() is unsuccessful (i.e.
54+
// the future is completed either successfully or exceptionally), then get the result if present and return it.
5155
@Override
5256
public ResultT get() throws InterruptedException, ExecutionException {
53-
return f.get();
57+
try {
58+
return f.get();
59+
} catch (InterruptedException ie) {
60+
if (!cancel(true)) {
61+
try {
62+
// We couldn't cancel so the result will be available or it failed
63+
ResultT entry = f.get();
64+
Thread.currentThread().interrupt();
65+
return entry;
66+
} catch (CancellationException | InterruptedException | ExecutionException e) {
67+
// no-op, let it fall through to throwing the original interrupted exception
68+
}
69+
}
70+
throw ie;
71+
}
5472
}
5573

74+
5675
// This method attempts to cancel the wrapped future if the thread is interrupted while blocked on get(). This is done by
5776
// attempting to cancel() the future when InterruptedException is thrown. If the the cancel() is unsuccessful (i.e.
5877
// the future is completed either successfully or exceptionally), then get the result if present and return it.
5978
@Override
6079
public ResultT get(long timeout, TimeUnit unit)
61-
throws InterruptedException, ExecutionException, TimeoutException {
80+
throws InterruptedException, ExecutionException, TimeoutException {
6281
try {
6382
return f.get(timeout, unit);
6483
} catch (InterruptedException ie) {
@@ -75,4 +94,4 @@ public ResultT get(long timeout, TimeUnit unit)
7594
throw ie;
7695
}
7796
}
78-
}
97+
}

http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/CancelOnInterruptFutureTest.java renamed to http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/utils/CancelOnInterruptWrapperTest.java

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,24 @@
3030
import java.util.concurrent.Future;
3131
import java.util.concurrent.TimeUnit;
3232
import java.util.concurrent.TimeoutException;
33+
import java.util.concurrent.atomic.AtomicBoolean;
34+
import org.junit.jupiter.api.AfterEach;
3335
import org.junit.jupiter.api.BeforeEach;
3436
import org.junit.jupiter.api.Test;
3537

36-
public class CancelOnInterruptFutureTest {
38+
public class CancelOnInterruptWrapperTest {
3739
private Future<String> mockDelegate;
3840

3941
@BeforeEach
4042
void setup() {
4143
mockDelegate = mock(Future.class);
4244
}
4345

46+
@AfterEach
47+
void teardown() {
48+
Thread.interrupted(); // clear the flag if it was set by the last test
49+
}
50+
4451
@Test
4552
void cancel_callsDelegate() {
4653
CancelOnInterruptWrapper<String> wrapper = new CancelOnInterruptWrapper<>(mockDelegate);
@@ -77,7 +84,7 @@ void getTimeout_callsDelegate() throws ExecutionException, InterruptedException,
7784
}
7885

7986
@Test
80-
void get_interrupted_cancelSuccessful_throws() throws ExecutionException, InterruptedException, TimeoutException {
87+
void getTimeout_interrupted_cancelSuccessful_throws() throws ExecutionException, InterruptedException, TimeoutException {
8188
when(mockDelegate.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException("interrupt"));
8289
when(mockDelegate.cancel(eq(true))).thenReturn(true);
8390

@@ -94,7 +101,8 @@ void get_interrupted_cancelSuccessful_throws() throws ExecutionException, Interr
94101
}
95102

96103
@Test
97-
void get_interrupted_cancelUnsuccessful_returnsEntry() throws ExecutionException, InterruptedException, TimeoutException {
104+
void getTimeout_interrupted_cancelUnsuccessful_returnsEntry() throws ExecutionException, InterruptedException,
105+
TimeoutException {
98106
String result = "hello there";
99107

100108
when(mockDelegate.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException("interrupt"));
@@ -107,7 +115,8 @@ void get_interrupted_cancelUnsuccessful_returnsEntry() throws ExecutionException
107115
}
108116

109117
@Test
110-
void get_interrupted_cancelUnsuccessful_getUnsuccessful_rethrowsOriginalIe() throws ExecutionException, InterruptedException, TimeoutException {
118+
void getTimeout_interrupted_cancelUnsuccessful_getUnsuccessful_rethrowsOriginalIe() throws ExecutionException,
119+
InterruptedException, TimeoutException {
111120
InterruptedException interrupt = new InterruptedException("interrupt");
112121

113122
when(mockDelegate.get(anyLong(), any(TimeUnit.class))).thenThrow(interrupt);
@@ -118,4 +127,93 @@ void get_interrupted_cancelUnsuccessful_getUnsuccessful_rethrowsOriginalIe() thr
118127

119128
assertThatThrownBy(() -> wrapper.get(1, TimeUnit.SECONDS)).isSameAs(interrupt);
120129
}
130+
131+
@Test
132+
void get_interrupted_cancelSuccessful_throws() throws ExecutionException, InterruptedException, TimeoutException {
133+
when(mockDelegate.get()).thenThrow(new InterruptedException("interrupt"));
134+
when(mockDelegate.cancel(eq(true))).thenReturn(true);
135+
136+
CancelOnInterruptWrapper<String> wrapper = new CancelOnInterruptWrapper<>(mockDelegate);
137+
138+
assertThatThrownBy(wrapper::get)
139+
.isInstanceOf(InterruptedException.class)
140+
.hasMessage("interrupt");
141+
142+
verify(mockDelegate).get();
143+
verify(mockDelegate).cancel(eq(true));
144+
145+
verifyNoMoreInteractions(mockDelegate);
146+
}
147+
148+
@Test
149+
void get_interrupted_cancelUnsuccessful_returnsEntry() throws ExecutionException, InterruptedException, TimeoutException {
150+
String result = "hello there";
151+
152+
AtomicBoolean first = new AtomicBoolean(true);
153+
when(mockDelegate.get()).thenAnswer(i -> {
154+
if (first.compareAndSet(true, false)) {
155+
throw new InterruptedException("interrupt");
156+
}
157+
return result;
158+
});
159+
when(mockDelegate.cancel(eq(true))).thenReturn(false);
160+
161+
CancelOnInterruptWrapper<String> wrapper = new CancelOnInterruptWrapper<>(mockDelegate);
162+
163+
assertThat(wrapper.get()).isEqualTo(result);
164+
}
165+
166+
@Test
167+
void get_interrupted_cancelUnsuccessful_getUnsuccessful_rethrowsOriginalIe() throws ExecutionException, InterruptedException, TimeoutException {
168+
InterruptedException interrupt = new InterruptedException("interrupt");
169+
170+
AtomicBoolean first = new AtomicBoolean(true);
171+
when(mockDelegate.get()).thenAnswer(i -> {
172+
if (first.compareAndSet(true, false)) {
173+
throw interrupt;
174+
}
175+
throw new CancellationException("cancelled");
176+
});
177+
when(mockDelegate.cancel(eq(true))).thenReturn(false);
178+
179+
CancelOnInterruptWrapper<String> wrapper = new CancelOnInterruptWrapper<>(mockDelegate);
180+
181+
assertThatThrownBy(wrapper::get).isSameAs(interrupt);
182+
}
183+
184+
@Test
185+
void get_interrupted_cancelUnsuccessful_cancelUnsuccessful_preservesInterruptedFlag() throws ExecutionException, InterruptedException {
186+
String result = "hello there";
187+
188+
AtomicBoolean first = new AtomicBoolean(true);
189+
when(mockDelegate.get()).thenAnswer(i -> {
190+
if (first.compareAndSet(true, false)) {
191+
throw new InterruptedException("interrupt");
192+
}
193+
return result;
194+
});
195+
when(mockDelegate.cancel(eq(true))).thenReturn(false);
196+
197+
CancelOnInterruptWrapper<String> wrapper = new CancelOnInterruptWrapper<>(mockDelegate);
198+
199+
wrapper.get();
200+
201+
assertThat(Thread.interrupted()).isTrue();
202+
}
203+
204+
@Test
205+
void getTimeout_interrupted_cancelUnsuccessful_preservesInterruptedFlag() throws ExecutionException, InterruptedException,
206+
TimeoutException {
207+
String result = "hello there";
208+
209+
when(mockDelegate.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException("interrupt"));
210+
when(mockDelegate.cancel(eq(true))).thenReturn(false);
211+
when(mockDelegate.get()).thenReturn(result);
212+
213+
CancelOnInterruptWrapper<String> wrapper = new CancelOnInterruptWrapper<>(mockDelegate);
214+
215+
wrapper.get(1, TimeUnit.SECONDS);
216+
217+
assertThat(Thread.interrupted()).isTrue();
218+
}
121219
}

0 commit comments

Comments
 (0)