From 04b3a82861ae0d2a6317cf4466e3d7e021d1beaa Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Fri, 8 Sep 2023 12:00:30 +1000 Subject: [PATCH] Don't clear interrupt with Thread.interrupted Allows caller to spawn a thread, execute, interrupt, and join. Fixes #1991 --- CHANGES | 5 ++++ .../internal/ConstrainableInputStream.java | 4 +-- .../java/org/jsoup/integration/ConnectIT.java | 25 ++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 551aac72b4..4a44e0f44e 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,11 @@ Release 1.16.2 [PENDING] ASCII. + * Bugfix: in Jsoup.connect(url), the ConstrainableInputStream would clear Thread interrupts when reading the body. + This precluded callers from spawning a thread, running a number of requests for a length of time, then joining that + thread after interrupting it. + + * Change: removed previously deprecated methods Document#normalise, Element#forEach(org.jsoup.helper.Consumer<>), Node#forEach(org.jsoup.helper.Consumer<>), and the org.jsoup.helper.Consumer interface; the latter being a previously required compatibility shim prior to Android's de-sugaring support. diff --git a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java index 54928f4e49..aa2c275127 100644 --- a/src/main/java/org/jsoup/internal/ConstrainableInputStream.java +++ b/src/main/java/org/jsoup/internal/ConstrainableInputStream.java @@ -49,8 +49,8 @@ public static ConstrainableInputStream wrap(InputStream in, int bufferSize, int public int read(byte[] b, int off, int len) throws IOException { if (interrupted || capped && remaining <= 0) return -1; - if (Thread.interrupted()) { - // interrupted latches, because parse() may call twice (and we still want the thread interupt to clear) + if (Thread.currentThread().isInterrupted()) { + // interrupted latches, because parse() may call twice interrupted = true; return -1; } diff --git a/src/test/java/org/jsoup/integration/ConnectIT.java b/src/test/java/org/jsoup/integration/ConnectIT.java index 84315b4a82..c37893574a 100644 --- a/src/test/java/org/jsoup/integration/ConnectIT.java +++ b/src/test/java/org/jsoup/integration/ConnectIT.java @@ -14,6 +14,7 @@ import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.*; @@ -70,7 +71,29 @@ public void canInterruptDocumentRead() throws InterruptedException { assertTrue(runner.isInterrupted()); runner.join(); - assertEquals(0, body[0].length()); // doesn't ready a failed doc + assertEquals(0, body[0].length()); // doesn't read a failed doc + } + + @Test public void canInterruptThenJoinASpawnedThread() throws InterruptedException { + // https://github.com/jhy/jsoup/issues/1991 + AtomicBoolean ioException = new AtomicBoolean(); + Thread runner = new Thread(() -> { + try { + while (!Thread.currentThread().isInterrupted()) { + Document doc = Jsoup.connect(SlowRider.Url) + .timeout(4000) + .get(); + } + } catch (IOException e) { + ioException.set(true); // don't expect to catch, because the outer sleep will complete before this timeout + } + }); + + runner.start(); + Thread.sleep(2 * 1000); + runner.interrupt(); + runner.join(); + assertFalse(ioException.get()); } @Test