Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with Response OutputStream#close() rethrowing same EofException instance #11736

Closed
cdivilly opened this issue May 2, 2024 · 7 comments · Fixed by #11759
Closed

Issue with Response OutputStream#close() rethrowing same EofException instance #11736

cdivilly opened this issue May 2, 2024 · 7 comments · Fixed by #11759
Assignees
Labels
Bug For general bugs on Jetty side

Comments

@cdivilly
Copy link

cdivilly commented May 2, 2024

Jetty version(s)

Jetty 12.0.7

Jetty Environment

core

Java version/vendor (use: java -version)

java version "21.0.1" 2023-10-17 LTS
Java(TM) SE Runtime Environment (build 21.0.1+12-LTS-29)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.1+12-LTS-29, mixed mode, sharing)

OS type/version

ProductName: macOS
ProductVersion: 14.4.1
BuildVersion: 23E224

Description

In the case where a client hangs up before a Jetty Handler has written a response, an EofException is raised when attempting to write to the response output stream.

This is expected.

It is best practice to acquire and release the response output stream using with the try-with-resources idiom:

                   public boolean handle(Request request, Response response, Callback callback) {
                        try (final var body = Content.Sink.asOutputStream(response)) {
                            body.write("Hello World".getBytes(StandardCharsets.UTF_8));
                            callback.succeeded();
                        } catch (IOException e) {
                            callback.failed(e);
                        }
                        return true;
                    }

Note that the try with resources block is translated into a regular try block per the Java Language Specification, so when the block completes the OutputStream#close() method will be invoked.

The generated try block has the following form:

{
    final {VariableModifierNoFinal} R Identifier = Expression;
    Throwable #primaryExc = null;

    try ResourceSpecification_tail
        Block
    catch (Throwable #t) {
        #primaryExc = #t;
        throw #t;
    } finally {
        if (Identifier != null) {
            if (#primaryExc != null) {
                try {
                    Identifier.close();
                } catch (Throwable #suppressedExc) {
                    #primaryExc.addSuppressed(#suppressedExc);
                }
            } else {
                Identifier.close();
            }
        }
    }
}
  • Note the line: #primaryExc.addSuppressed(#suppressedExc);

The logic of Throwable#addSuppressed is:

public final synchronized void addSuppressed(Throwable exception) {
        if (exception == this)
            throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

        Objects.requireNonNull(exception, NULL_CAUSE_MESSAGE);

        if (suppressedExceptions == null) // Suppressed exceptions not recorded
            return;

        if (suppressedExceptions == SUPPRESSED_SENTINEL)
            suppressedExceptions = new ArrayList<>(1);

        suppressedExceptions.add(exception);
    }
  • If the suppressed exception is the same instance as this then raise an IlegalArgumentException (to prevent an exception referring to itself as suppressed)

In Jetty 12 (to the best of my recollection this was not the case in earlier Jetty versions, but have not confirmed this), the OutputStream#close() call rethrows the exact same instance of EofException as was raised when the call to OutputStream#write() was invoked.

This causes the generated try block to end up raising an IllegalArgumentException due to Throwable#addSuppressed() refusing to add itself as a suppressed exception.

Whether intentional or not, the generated try block excludes the possibility that the close() method may throw the exact same exception instance which originally caused the try with resources block to be exited.

I cannot think of a good reason for the close() method to rethrow the same exception instance. I think a failure in the close() method is semantically different to the failure in the write() method and should result in a different exception instance (if any) being thrown.

In this specific case, where the client has hung-up, I think the appropriate behaviour of close() is to complete without raising any error.

How to reproduce?

See the test case below:

package com.example.jetty;

import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;

import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class Main {

    private static void sleep(final int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String... args) throws Exception {
        final var server = new Server();
        final var connector = new ServerConnector(server, 1, 1);
        server.addConnector(connector);
        final var context = new ContextHandler("/");

        final var clientResponse = new AtomicReference<CompletableFuture<HttpResponse<String>>>();

        context.setHandler(
                new Handler.Abstract() {

                    @Override
                    public boolean handle(Request request, Response response, Callback callback) {
                        try (final var body = Content.Sink.asOutputStream(response)) {
                            // simulate the client hanging up
                            if (clientResponse.get() != null) {
                                clientResponse.get().cancel(true);
                            }
                            sleep(2);

                            // Raises EofException because client hung up
                            body.write("Hello World".getBytes(StandardCharsets.UTF_8));
                            callback.succeeded();
                        } catch (IOException e) {
                            // Does NOT reach here, because the OutputStream#close()
                            // rethrows same EofException instance, which causes
                            // try with resources block call to: Throwable#addSuppresed() to
                            // fail with java.lang.IllegalArgumentException: Self-suppression not
                            // permitted
                            callback.failed(e);
                        }
                        return true;
                    }
                });
        server.setHandler(context);

        server.start();

        clientResponse.set(
                HttpClient.newHttpClient()
                        .sendAsync(
                                HttpRequest.newBuilder().GET().uri(server.getURI()).build(),
                                HttpResponse.BodyHandlers.ofString()));
        sleep(2);
        server.stop();
    }
}
@cdivilly cdivilly added the Bug For general bugs on Jetty side label May 2, 2024
@cdivilly
Copy link
Author

cdivilly commented May 2, 2024

This is the output of running the above test case:

[main] INFO org.eclipse.jetty.server.Server - jetty-12.0.7; built: 2024-02-29T21:19:41.771Z; git: c89aca8fd34083befd79f328a3b8b6ffff04347e; jvm 21.0.1+12-LTS-29
[main] INFO org.eclipse.jetty.server.handler.ContextHandler - Started oejsh.ContextHandler@cb644e{ROOT,/,b=null,a=AVAILABLE,h=cej.Main$@56ef9176{STARTED}}
[main] INFO org.eclipse.jetty.server.AbstractConnector - Started ServerConnector@56235b8e{HTTP/1.1, (http/1.1)}{0.0.0.0:57211}
[main] INFO org.eclipse.jetty.server.Server - Started oejs.Server@7921b0a2{STARTING}[12.0.7,sto=0] @186ms
[main] INFO org.eclipse.jetty.server.Server - Stopped oejs.Server@7921b0a2{STOPPING}[12.0.7,sto=0]
[main] INFO org.eclipse.jetty.server.AbstractConnector - Stopped ServerConnector@56235b8e{HTTP/1.1, (http/1.1)}{0.0.0.0:0}
[qtp1282788025-25] WARN org.eclipse.jetty.server.Response - writeError: status=500, message=java.lang.IllegalArgumentException: Self-suppression not permitted, response=org.eclipse.jetty.server.handler.ContextResponse@7ad5cbae
java.lang.IllegalArgumentException: Self-suppression not permitted
	at java.base/java.lang.Throwable.addSuppressed(Throwable.java:1096)
	at com.example.jetty.rethrown.exception.issue/com.example.jetty.Main$1.handle(Main.java:44)
	at [email protected]/org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:765)
	at [email protected]/org.eclipse.jetty.server.Server.handle(Server.java:179)
	at [email protected]/org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:619)
	at [email protected]/org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:411)
	at [email protected]/org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at [email protected]/org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at [email protected]/org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at [email protected]/org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:971)
	at [email protected]/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1201)
	at [email protected]/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1156)
	at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.eclipse.jetty.io.EofException
	at [email protected]/org.eclipse.jetty.server.internal.HttpConnection$SendCallback.reset(HttpConnection.java:739)
	at [email protected]/org.eclipse.jetty.server.internal.HttpConnection$HttpStreamOverHTTP1.send(HttpConnection.java:1418)
	at [email protected]/org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1246)
	at [email protected]/org.eclipse.jetty.server.Response$Wrapper.write(Response.java:751)
	at [email protected]/org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)
	at [email protected]/org.eclipse.jetty.io.content.ContentSinkOutputStream.write(ContentSinkOutputStream.java:53)
	at java.base/java.io.OutputStream.write(OutputStream.java:124)
	at com.example.jetty.rethrown.exception.issue/com.example.jetty.Main$1.handle(Main.java:52)
	... 11 more
[qtp1282788025-25] WARN org.eclipse.jetty.server.Response - writeError: status=500, message=java.lang.IllegalArgumentException: Self-suppression not permitted, response=ErrorResponse@7481e31b{500,GET@0 http://10.20.1.40:57211/ HTTP/1.1}
java.lang.IllegalArgumentException: Self-suppression not permitted
	at java.base/java.lang.Throwable.addSuppressed(Throwable.java:1096)
	at com.example.jetty.rethrown.exception.issue/com.example.jetty.Main$1.handle(Main.java:44)
	at [email protected]/org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:765)
	at [email protected]/org.eclipse.jetty.server.Server.handle(Server.java:179)
	at [email protected]/org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:619)
	at [email protected]/org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:411)
	at [email protected]/org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at [email protected]/org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at [email protected]/org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at [email protected]/org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:971)
	at [email protected]/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1201)
	at [email protected]/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1156)
	at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.eclipse.jetty.io.EofException
	at [email protected]/org.eclipse.jetty.server.internal.HttpConnection$SendCallback.reset(HttpConnection.java:739)
	at [email protected]/org.eclipse.jetty.server.internal.HttpConnection$HttpStreamOverHTTP1.send(HttpConnection.java:1418)
	at [email protected]/org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1246)
	at [email protected]/org.eclipse.jetty.server.Response$Wrapper.write(Response.java:751)
	at [email protected]/org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)
	at [email protected]/org.eclipse.jetty.io.content.ContentSinkOutputStream.write(ContentSinkOutputStream.java:53)
	at java.base/java.io.OutputStream.write(OutputStream.java:124)
	at com.example.jetty.rethrown.exception.issue/com.example.jetty.Main$1.handle(Main.java:52)
	... 11 more

@joakime
Copy link
Contributor

joakime commented May 2, 2024

This seems to overlap with another issue that is currently being worked on.

@cdivilly
Copy link
Author

cdivilly commented May 2, 2024

I've reviewed that issue, I'm not sure I see an overlap other than they both relate to EOFException conditions, I think this may be a distinct scenario.

@joakime
Copy link
Contributor

joakime commented May 2, 2024

While the initial description of the issue isn't the same, the root cause, and the fix, seem to be the same.

See PR

@cdivilly
Copy link
Author

cdivilly commented May 3, 2024

I cloned fix/jetty-12/11679/early-eof-jersey-leak and added the following unit test (had to hack in requires java.net.http to org.eclipse.jetty.server's module-info.java):

package org.eclipse.jetty.server;

import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class EofDuringResponseWriteTest {

    private static void sleep(final int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Test
    public void testClientAbortsRequest() throws Exception {
        final var server = new Server();
        final var connector = new ServerConnector(server, 1, 1);
        server.addConnector(connector);
        final var context = new ContextHandler("/");

        final var clientResponse = new AtomicReference<CompletableFuture<HttpResponse<String>>>();

        context.setHandler(
                new Handler.Abstract() {

                    @Override
                    public boolean handle(Request request, Response response, Callback callback) {
                        try (final var body = Content.Sink.asOutputStream(response)) {
                            // simulate the client hanging up
                            if (clientResponse.get() != null) {
                                clientResponse.get().cancel(true);
                            }
                            sleep(2);

                            // Raises EofException because client hung up
                            body.write("Hello World".getBytes(StandardCharsets.UTF_8));
                            callback.succeeded();
                        } catch (IOException e) {
                            // Does NOT reach here, because the OutputStream#close()
                            // rethrows same EofException instance, which causes
                            // try with resources block call to: Throwable#addSuppresed() to
                            // fail with java.lang.IllegalArgumentException: Self-suppression not
                            // permitted
                            callback.failed(e);
                        }
                        return true;
                    }
                });
        server.setHandler(context);

        server.start();

        clientResponse.set(
                HttpClient.newHttpClient()
                        .sendAsync(
                                HttpRequest.newBuilder().GET().uri(server.getURI()).build(),
                                HttpResponse.BodyHandlers.ofString()));
        sleep(2);
        server.stop();
    }
}

This is the output of running the test:

[2024-05-03T11:13:45.284+0100][info][gc] Using G1
WARNING: Unknown module: org.eclipse.jetty.logging specified to --add-reads
java version "17.0.5" 2022-10-18 LTS
Java(TM) SE Runtime Environment (build 17.0.5+9-LTS-191)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.5+9-LTS-191, mixed mode)
2024-05-03 11:13:45.707:INFO :oejs.Server:main: jetty-12.0.9-SNAPSHOT; built: unknown; git: ; jvm 17.0.5+9-LTS-191
2024-05-03 11:13:45.725:INFO :oejsh.ContextHandler:main: Started oejsh.ContextHandler@3e27aa33{ROOT,/,b=null,a=AVAILABLE,h=oejs.EofDuringResponseWriteTest$@2ddc9a9f{STARTED}}
2024-05-03 11:13:45.733:INFO :oejs.AbstractConnector:main: Started ServerConnector@7674f035{HTTP/1.1, (http/1.1)}{0.0.0.0:51255}
2024-05-03 11:13:45.738:INFO :oejs.Server:main: Started oejs.Server@6392827e{STARTING}[12.0.9-SNAPSHOT,sto=0] @459ms
[2024-05-03T11:13:46.052+0100][info][gc] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 62M->6M(4096M) 4.872ms
[2024-05-03T11:13:46.052+0100][info][gc] GC(1) Concurrent Mark Cycle
[2024-05-03T11:13:46.055+0100][info][gc] GC(1) Pause Remark 8M->8M(4096M) 0.765ms
[2024-05-03T11:13:46.055+0100][info][gc] GC(1) Pause Cleanup 8M->8M(4096M) 0.002ms
[2024-05-03T11:13:46.061+0100][info][gc] GC(1) Concurrent Mark Cycle 8.248ms
2024-05-03 11:13:48.066:INFO :oejs.Server:main: Stopped oejs.Server@6392827e{STOPPING}[12.0.9-SNAPSHOT,sto=0]
2024-05-03 11:13:48.075:INFO :oejs.AbstractConnector:main: Stopped ServerConnector@7674f035{HTTP/1.1, (http/1.1)}{0.0.0.0:0}
2024-05-03 11:13:48.117:WARN :oejs.Response:qtp341138954-20: writeError: status=500, message=java.lang.IllegalArgumentException: Self-suppression not permitted, response=org.eclipse.jetty.server.handler.ContextResponse@89800d3
java.lang.IllegalArgumentException: Self-suppression not permitted
	at java.base/java.lang.Throwable.addSuppressed(Throwable.java:1072)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.EofDuringResponseWriteTest$1.handle(EofDuringResponseWriteTest.java:41)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:851)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.Server.handle(Server.java:179)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:635)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:411)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: 
org.eclipse.jetty.io.EofException
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection$SendCallback.reset(HttpConnection.java:748)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection$HttpStreamOverHTTP1.send(HttpConnection.java:1428)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1263)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.Response$Wrapper.write(Response.java:751)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.content.ContentSinkOutputStream.write(ContentSinkOutputStream.java:53)
	at java.base/java.io.OutputStream.write(OutputStream.java:127)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.EofDuringResponseWriteTest$1.handle(EofDuringResponseWriteTest.java:49)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:851)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.Server.handle(Server.java:179)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:635)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:411)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)
	at java.base/java.lang.Thread.run(Thread.java:833)
2024-05-03 11:13:48.120:WARN :oejs.Response:qtp341138954-20: writeError: status=500, message=java.lang.IllegalArgumentException: Self-suppression not permitted, response=ErrorResponse@77e4e62a{500,GET@0 http://10.23.93.246:51255/ HTTP/1.1}
java.lang.IllegalArgumentException: Self-suppression not permitted
	at java.base/java.lang.Throwable.addSuppressed(Throwable.java:1072)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.EofDuringResponseWriteTest$1.handle(EofDuringResponseWriteTest.java:41)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:851)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.Server.handle(Server.java:179)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:635)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:411)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: 
org.eclipse.jetty.io.EofException
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection$SendCallback.reset(HttpConnection.java:748)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection$HttpStreamOverHTTP1.send(HttpConnection.java:1428)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1263)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.Response$Wrapper.write(Response.java:751)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.content.ContentSinkOutputStream.write(ContentSinkOutputStream.java:53)
	at java.base/java.io.OutputStream.write(OutputStream.java:127)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.EofDuringResponseWriteTest$1.handle(EofDuringResponseWriteTest.java:49)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:851)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.Server.handle(Server.java:179)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:635)
	at org.eclipse.jetty.server/org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:411)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
	at org.eclipse.jetty.io/org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)
	at org.eclipse.jetty.util/org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)
	at java.base/java.lang.Thread.run(Thread.java:833)

In other words the issue reported above continues to occur, even with the changes in fix/jetty-12/11679/early-eof-jersey-leak

@cdivilly
Copy link
Author

cdivilly commented May 3, 2024

I think the core issue is that ContentSinkOutputStream#close() blocks unconditionally. I think this should be conditional, it should only block if the Blocker.Shared._callback is not in a failed state.

I think ContentSinkOutputStream#flush() may have a similar issue, it should only attempt to flush if there has not been a prior failure

@lorban
Copy link
Contributor

lorban commented May 3, 2024

I don't think I agree with your statement that ContentSinkOutputStream#close() and ContentSinkOutputStream#flush() should block conditionally.

But it seems that you have found a real issue with our implementation of ContentSinkOutputStream: once it has failed because of an exception, it will throw that same exception for all other method calls, and that is clearly a problem when using try-with-resource blocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side
Projects
No open projects
Status: ✅ Done
Development

Successfully merging a pull request may close this issue.

3 participants