From ef1c56c1e034d85ccd37a46ac96b2d20e403a3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mat=C4=9Bj=C4=8Dek?= Date: Thu, 23 Jul 2026 20:26:38 +0200 Subject: [PATCH 1/3] Added log to TestHelper to separate logs of generated tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prints ie. this: Jul 23, 2026 7:29:03 PM org.glassfish.jersey.test.spi.TestHelper lambda$toTestContainer$3 INFO: Invoking test jaxbTest (SimpleBean, JsonbTestProvider).test - Before that it was impossible to find out which test failed. - Useful for PR #6035 Signed-off-by: David Matějček --- .../main/java/org/glassfish/jersey/test/spi/TestHelper.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestHelper.java b/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestHelper.java index 0aabca53a8b..080cbdcea0c 100644 --- a/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestHelper.java +++ b/test-framework/core/src/main/java/org/glassfish/jersey/test/spi/TestHelper.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026 Contributors to the Eclipse Foundation. * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -21,6 +22,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicTest; @@ -32,6 +35,7 @@ * @author Michal Gajdos */ public final class TestHelper { + private static final Logger LOG = Logger.getLogger(TestHelper.class.getName()); /** * Create a human readable string from given URI. This method replaces {@code 0} port (start container at first available @@ -65,6 +69,7 @@ public static DynamicContainer toTestContainer(Object test, String displayName) for (Method method : testMethods) { children.add(DynamicTest.dynamicTest(method.getName(), () -> { try { + LOG.log(Level.INFO, "Invoking test " + displayName + "." + method.getName()); for (Method beforeEachMethod : beforeEachMethods) { beforeEachMethod.invoke(test); } From 49e7f5da194ec2afe05a229095084f639151af1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mat=C4=9Bj=C4=8Dek?= Date: Fri, 24 Jul 2026 08:47:37 +0200 Subject: [PATCH 2/3] Refactored ChunkedInputClosedOnErrorTest to give us more info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The test randomly failed and it still does, however now it checks the exception, runs more iterations (100 or more instead of 10), and is more readable. Signed-off-by: David Matějček --- .../ChunkedInputClosedOnErrorTest.java | 223 +++++++++--------- 1 file changed, 116 insertions(+), 107 deletions(-) diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java index 32cd0898000..f48f999dc02 100644 --- a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java +++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026 Contributors to the Eclipse Foundation. * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -16,18 +17,9 @@ package org.glassfish.jersey.netty.connector; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.handler.stream.ChunkedInput; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.client.ClientRequest; -import org.glassfish.jersey.client.spi.Connector; -import org.glassfish.jersey.client.spi.ConnectorProvider; -import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.test.JerseyTest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import javax.ws.rs.ProcessingException; import javax.ws.rs.WebApplicationException; @@ -38,8 +30,8 @@ import javax.ws.rs.core.Configuration; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import javax.ws.rs.ext.MessageBodyWriter; + import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; @@ -47,118 +39,136 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * Bug 5837 reproducer */ public class ChunkedInputClosedOnErrorTest extends JerseyTest { - private static Client initClient(ConnectorProvider provider) { - ClientConfig defaultConfig = new ClientConfig(); - defaultConfig.property(ClientProperties.CONNECT_TIMEOUT, 10 * 1000); - defaultConfig.property(ClientProperties.READ_TIMEOUT, 10 * 1000); - defaultConfig.connectorProvider(provider); - Client client = ClientBuilder.newBuilder() - .withConfig(defaultConfig) - .build(); - return client; - } - @Override protected Application configure() { return new ResourceConfig(); } @Test - public void testChunkedInputNotStuckedTimes() throws InterruptedException { - for (int i = 0; i != 10; i++) { - boolean ret = testChunkedInputNotStucked(); - Assertions.assertTrue(ret, "JerseyChunkedInput was not closed on error"); + public void testChunkedInputNotStuckedTimes() throws Exception { + long timeout = System.currentTimeMillis() + 100; + int counter = 0; + while (System.currentTimeMillis() < timeout || counter < 300) { + counter++; + TestNettyConnectorProvider provider = new TestNettyConnectorProvider(); + Client client = provider.initClient(); + ProcessingException processingException = assertThrows(ProcessingException.class, + () -> client.register(new MultipartWriter()).target(target().getUri()).request() + .post(Entity.entity(new MultipartWriter(), MediaType.MULTIPART_FORM_DATA_TYPE))); + provider.waitForWriterSet(); + assertAll("Iteration: " + counter, + () -> assertInstanceOf(IllegalArgumentException.class, processingException.getCause()), + () -> assertEquals("TestException", processingException.getCause().getMessage()), + () -> assertTrue(provider.isEndOfInput(), "JerseyChunkedInput was not closed on error") + ); } } - public boolean testChunkedInputNotStucked() throws InterruptedException { - final AtomicReference writer = new AtomicReference<>(); - final CountDownLatch writerSetLatch = new CountDownLatch(1); - final CountDownLatch flushLatch = new CountDownLatch(1); - ConnectorProvider provider = new ConnectorProvider() { - @Override - public Connector getConnector(Client client, Configuration runtimeConfig) { - return new NettyConnector(client, NettyConnectorProvider.config().rw()) { - @Override - NettyEntityWriter nettyEntityWriter(ClientRequest clientRequest, Channel channel, - NettyConnectorProvider.Config.RW config) { - writer.set(super.nettyEntityWriter(clientRequest, channel, config)); - writerSetLatch.countDown(); - return new NettyEntityWriter() { - private boolean slept = false; - - @Override - public void write(Object object) { - writer.get().write(object); - } - - @Override - public void writeAndFlush(Object object) { - writer.get().writeAndFlush(object); - } + private static class TestNettyConnectorProvider implements ConnectorProvider { - @Override - public void flush() throws IOException { - writer.get().flush(); - flushLatch.countDown(); - } + private final AtomicReference writer = new AtomicReference<>(); + private final CountDownLatch writerSetLatch = new CountDownLatch(1); + private final CountDownLatch flushLatch = new CountDownLatch(1); - @Override - public ChunkedInput getChunkedInput() { - for (StackTraceElement element : Thread.currentThread().getStackTrace()) { - // caught from catch block in executorService.execute(new Runnable() { - // "sleep" to simulate race condition - if (element.getClassName().contains("NettyConnector") - && element.getMethodName().equals("run")) { - try { - flushLatch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - return writer.get().getChunkedInput(); - } + Client initClient() { + ClientConfig defaultConfig = new ClientConfig(); + defaultConfig.property(ClientProperties.CONNECT_TIMEOUT, 1_000); + defaultConfig.property(ClientProperties.READ_TIMEOUT, 10_000); + defaultConfig.connectorProvider(this); + return ClientBuilder.newBuilder().withConfig(defaultConfig).build(); + } - @Override - public OutputStream getOutputStream() { - return writer.get().getOutputStream(); - } + void waitForWriterSet() throws InterruptedException { + writerSetLatch.await(); + } - @Override - public long getLength() { - return writer.get().getLength(); - } + boolean isEndOfInput() throws Exception { + return writer.get().getChunkedInput().isEndOfInput(); + } - @Override - public Type getType() { - return writer.get().getType(); + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + return new NettyConnector(client, NettyConnectorProvider.config().rw()) { + + @Override + NettyEntityWriter nettyEntityWriter(ClientRequest clientRequest, Channel channel, + NettyConnectorProvider.Config.RW config) { + writer.set(super.nettyEntityWriter(clientRequest, channel, config)); + writerSetLatch.countDown(); + return new NettyEntityWriter() { + + @Override + public void write(Object object) { + writer.get().write(object); + } + + @Override + public void writeAndFlush(Object object) { + writer.get().writeAndFlush(object); + } + + @Override + public void flush() throws IOException { + writer.get().flush(); + flushLatch.countDown(); + } + + @Override + public ChunkedInput getChunkedInput() { + for (StackTraceElement element : Thread.currentThread().getStackTrace()) { + // caught from catch block in executorService.execute(new Runnable() + // { + // "sleep" to simulate race condition + if (element.getClassName().contains("NettyConnector") + && element.getMethodName().equals("run")) { + try { + flushLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } } - }; - } - }; - } - }; - - Client client = initClient(provider); - try { - Response r = client - .register(new MultipartWriter()) - .target(target().getUri()).request() - .post(Entity.entity(new MultipartWriter(), MediaType.MULTIPART_FORM_DATA_TYPE)); - } catch (ProcessingException expected) { - - } - writerSetLatch.await(); - try { - return writer.get().getChunkedInput().isEndOfInput(); - } catch (Exception e) { - throw new RuntimeException(e); + return writer.get().getChunkedInput(); + } + + @Override + public OutputStream getOutputStream() { + return writer.get().getOutputStream(); + } + + @Override + public long getLength() { + return writer.get().getLength(); + } + + @Override + public Type getType() { + return writer.get().getType(); + } + }; + } + }; } } @@ -170,11 +180,10 @@ public boolean isWriteable(Class type, Type genericType, Annotation[] annotat } @Override - public void writeTo(Object object, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, - MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, - WebApplicationException { + public void writeTo(Object object, Class type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { throw new IllegalArgumentException("TestException"); } } - } From b8317c87c461afb6228face69589bc5018df480a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mat=C4=9Bj=C4=8Dek?= Date: Wed, 22 Jul 2026 13:22:34 +0200 Subject: [PATCH 3/3] Stabilized ChunkedInputClosedOnErrorTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flush doesn't guarantee the end of input, however it should be close. Signed-off-by: David Matějček --- .../netty/connector/ChunkedInputClosedOnErrorTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java index f48f999dc02..86953dc22e5 100644 --- a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java +++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputClosedOnErrorTest.java @@ -104,7 +104,14 @@ void waitForWriterSet() throws InterruptedException { } boolean isEndOfInput() throws Exception { - return writer.get().getChunkedInput().isEndOfInput(); + // getChunkedInput blocks and waits for the flush call, but that doesn't mean + // that the ChunkedInput is closed, so we give it a bit more time. + ChunkedInput chunkedInput = writer.get().getChunkedInput(); + long timeout = System.currentTimeMillis() + 100; + while (!chunkedInput.isEndOfInput() && System.currentTimeMillis() < timeout) { + Thread.yield(); + } + return chunkedInput.isEndOfInput(); } @Override