Skip to content

Commit ada9744

Browse files
committed
ThrottledInputStream.throttle() doesn't preserve the original
InterruptedException as the cause of its InterruptedIOException ThrottledInputStream.throttle() now restores the current thread's interrupt flag when catching InterruptedException.
1 parent b83664f commit ada9744

5 files changed

Lines changed: 63 additions & 12 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ The <action> type attribute can be add,update,fix,remove.
5656
<action type="fix" dev="ggregory" due-to="Gary Gregory">FileCleaningTracker now restores the current thread's interrupt flag when catching InterruptedException.</action>
5757
<action type="fix" dev="ggregory" due-to="Gary Gregory">ThreadMonitor.run() now restores the current thread's interrupt flag when catching InterruptedException.</action>
5858
<action type="fix" dev="ggregory" due-to="Gary Gregory">ThrottledInputStream.throttle() now restores the current thread's interrupt flag when catching InterruptedException.</action>
59+
<action type="fix" dev="ggregory" due-to="Gary Gregory">ThrottledInputStream.throttle() doesn't preserve the original InterruptedException as the cause of its InterruptedIOException.</action>
5960
<!-- ADD -->
6061
<action type="add" dev="ggregory" due-to="Gary Gregory, Piotr P. Karwasz">Add and use IOUtils.closeQuietlySuppress(Closeable, Throwable) #818.</action>
6162
<!-- UPDATE -->

src/main/java/org/apache/commons/io/input/Input.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
package org.apache.commons.io.input;
1919

2020
import java.io.IOException;
21+
import java.io.InterruptedIOException;
2122

2223
/**
2324
* Package-wide internals for input.
2425
*/
25-
class Input {
26+
final class Input {
2627

2728
/**
2829
* Throws an IOException on false input.
@@ -36,4 +37,21 @@ static void checkOpen(final boolean isOpen) throws IOException {
3637
}
3738
}
3839

40+
/**
41+
* Converts an InterruptedException to an InterruptedIOException.
42+
* <p>
43+
* The cause of the returned InterruptedIOException is set to the original.
44+
* </p>
45+
*
46+
* @param e The InterruptedException to convert.
47+
* @return The converted InterruptedIOException.
48+
* @see InterruptedIOException
49+
* @see Throwable#initCause(Throwable)
50+
* @see Throwable#getCause()
51+
*/
52+
static InterruptedIOException toInterruptedIOException(final InterruptedException e) {
53+
final InterruptedIOException iio = new InterruptedIOException(e.getMessage());
54+
iio.initCause(e);
55+
return iio;
56+
}
3957
}

src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.io.FilterInputStream;
2121
import java.io.IOException;
2222
import java.io.InputStream;
23-
import java.io.InterruptedIOException;
2423
import java.nio.ByteBuffer;
2524
import java.util.Objects;
2625
import java.util.concurrent.ExecutorService;
@@ -299,9 +298,7 @@ public void close() throws IOException {
299298
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
300299
} catch (final InterruptedException e) {
301300
Thread.currentThread().interrupt();
302-
final InterruptedIOException iio = new InterruptedIOException(e.getMessage());
303-
iio.initCause(e);
304-
throw iio;
301+
throw Input.toInterruptedIOException(e);
305302
} finally {
306303
if (isSafeToCloseUnderlyingInputStream) {
307304
super.close();
@@ -559,9 +556,7 @@ private void waitForAsyncReadComplete() throws IOException {
559556
}
560557
} catch (final InterruptedException e) {
561558
Thread.currentThread().interrupt();
562-
final InterruptedIOException iio = new InterruptedIOException(e.getMessage());
563-
iio.initCause(e);
564-
throw iio;
559+
throw Input.toInterruptedIOException(e);
565560
} finally {
566561
try {
567562
isWaiting.set(false);

src/main/java/org/apache/commons/io/input/ThrottledInputStream.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,21 +245,38 @@ private long getBytesPerSecond() {
245245
return getByteCount() / elapsedSeconds;
246246
}
247247

248-
// package private for testing.
248+
/**
249+
* Gets the maximum bytes per second.
250+
* <p>
251+
* Package private for testing.
252+
* </p>
253+
*
254+
* @return The maximum bytes per second.
255+
*/
249256
double getMaxBytesPerSecond() {
250257
return maxBytesPerSecond;
251258
}
252259

253-
private long getSleepMillis() {
260+
/**
261+
* Gets the number of milliseconds to sleep to match to the maximum bytes per second.
262+
* <p>
263+
* Package private for testing.
264+
* </p>
265+
*
266+
* @return the number of milliseconds to sleep to match to the maximum bytes per second.
267+
*/
268+
long getSleepMillis() {
254269
return toSleepMillis(getByteCount(), System.currentTimeMillis() - startTime, maxBytesPerSecond);
255270
}
256271

257272
/**
258273
* Gets the total duration spent in sleep.
274+
* <p>
275+
* Package private for testing
276+
* </p>
259277
*
260278
* @return Duration spent in sleep.
261279
*/
262-
// package private for testing
263280
Duration getTotalSleepDuration() {
264281
return totalSleepDuration;
265282
}
@@ -272,7 +289,7 @@ private void throttle() throws InterruptedIOException {
272289
TimeUnit.MILLISECONDS.sleep(sleepMillis);
273290
} catch (final InterruptedException e) {
274291
Thread.currentThread().interrupt();
275-
throw new InterruptedIOException("Thread aborted");
292+
throw Input.toInterruptedIOException(e);
276293
}
277294
}
278295
}

src/test/java/org/apache/commons/io/input/ThrottledInputStreamTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
package org.apache.commons.io.input;
1919

2020
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
2122
import static org.junit.jupiter.api.Assertions.assertThrows;
2223
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
2324
import static org.junit.jupiter.api.Assertions.assertTrue;
25+
import static org.mockito.Mockito.spy;
26+
import static org.mockito.Mockito.when;
2427

2528
import java.io.IOException;
2629
import java.io.InputStream;
30+
import java.io.InterruptedIOException;
2731
import java.time.Duration;
2832
import java.time.temporal.ChronoUnit;
2933
import java.util.concurrent.atomic.AtomicBoolean;
@@ -188,4 +192,20 @@ void testGet() throws IOException {
188192
}
189193
}
190194

195+
@Test
196+
synchronized void testReadInterrupt() throws IOException {
197+
try (ThrottledInputStream inputStream = ThrottledInputStream.builder()
198+
// @formatter:off
199+
.setInputStream(createOriginInputStream())
200+
.setMaxBytes(1, ChronoUnit.HOURS)
201+
.get()) {
202+
// @formatter:on
203+
final ThrottledInputStream spy = spy(inputStream);
204+
when(spy.getSleepMillis()).thenReturn(1L);
205+
Thread.currentThread().interrupt();
206+
assertInstanceOf(InterruptedException.class, assertThrows(InterruptedIOException.class, spy::read).getCause());
207+
assertTrue(Thread.interrupted());
208+
}
209+
}
210+
191211
}

0 commit comments

Comments
 (0)