From bdf8de276786a0c4c762cc043bd6de2c2d4eaff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9?= Date: Sun, 1 Nov 2026 19:09:54 +0100 Subject: [PATCH] Http.abort: leave responses that were read to the end alone Their connection is back in the pool and may be carrying another request by then (in Plexamp Lite this cut read-ahead downloads short). From Plexamp Lite 1.14.1. Co-Authored-By: Claude Opus 5.5 --- net/Http.java | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/net/Http.java b/net/Http.java index dfb43f9..00767ab 100644 --- a/net/Http.java +++ b/net/Http.java @@ -451,19 +451,25 @@ public final class Http { // ------------------------------------------------------------------ body streams private static final class FixedStream extends InputStream { - private final Conn c; private long left; private final boolean keepAlive; private boolean closed; + private final Conn c; private long left; private final boolean keepAlive; private volatile boolean closed; FixedStream(Conn c, long len, boolean keepAlive) { this.c = c; this.left = len; this.keepAlive = keepAlive; if (len == 0) finish(true); } - private void finish(boolean complete) { + private synchronized void finish(boolean complete) { if (closed) return; closed = true; if (complete && keepAlive) release(c); else c.close(); } + synchronized void abort() { + if (closed) return; // done: the connection may already be back in the pool, serving someone else + closed = true; + try { c.socket.close(); } catch (Exception ignored) {} + } + @Override public int read() throws IOException { byte[] one = new byte[1]; int n = read(one, 0, 1); @@ -490,16 +496,22 @@ public final class Http { private static final class ChunkedStream extends InputStream { private final Conn c; private final boolean keepAlive; - private long chunkLeft = 0; private boolean eof; private boolean closed; + private long chunkLeft = 0; private boolean eof; private volatile boolean closed; ChunkedStream(Conn c, boolean keepAlive) { this.c = c; this.keepAlive = keepAlive; } - private void finish(boolean complete) { + private synchronized void finish(boolean complete) { if (closed) return; closed = true; if (complete && keepAlive) release(c); else c.close(); } + synchronized void abort() { + if (closed) return; // done: the connection may already be back in the pool, serving someone else + closed = true; + try { c.socket.close(); } catch (Exception ignored) {} + } + private boolean nextChunk() throws IOException { if (eof) return false; String line = readLine(c.in); @@ -551,7 +563,7 @@ public final class Http { } private static final class EofStream extends InputStream { - private final Conn c; private boolean closed; + private final Conn c; private volatile boolean closed; EofStream(Conn c) { this.c = c; } @Override public int read() throws IOException { @@ -568,21 +580,25 @@ public final class Http { return n; } - @Override public void close() { + @Override public synchronized void close() { if (!closed) { closed = true; c.close(); } } - } - /** Close one in-flight response's socket from another thread (used to abort streams). */ - public static void abort(Response r) { - if (r == null) return; - InputStream b = r.body; - Conn c = null; - if (b instanceof FixedStream) c = ((FixedStream) b).c; - else if (b instanceof ChunkedStream) c = ((ChunkedStream) b).c; - else if (b instanceof EofStream) c = ((EofStream) b).c; - if (c != null) { + synchronized void abort() { + if (closed) return; + closed = true; try { c.socket.close(); } catch (Exception ignored) {} } } + + /** Close one in-flight response's socket from another thread (used to abort streams). A response that has + * already been read to the end is left alone: its connection went back to the pool and may be carrying + * another request by now (closing it cut read-ahead downloads short: "Unexpected end of stream"). */ + public static void abort(Response r) { + if (r == null) return; + InputStream b = r.body; + if (b instanceof FixedStream) ((FixedStream) b).abort(); + else if (b instanceof ChunkedStream) ((ChunkedStream) b).abort(); + else if (b instanceof EofStream) ((EofStream) b).abort(); + } }