I'm already looking at this. Something very strange is happening; I have a TCP stream open in Wireshark right now showing that the server sent back a 400, but the client never stopped writing its request entity. Either the client isn't seeing the server's response somehow (I can see from logging breakpoints that `inputStream.available()` just keeps returning 0), or the `400 Bad Request` is not triggering a `ResponseOutOfOrderException`, or the exception is somehow getting swallowed.
On Sat, Dec 27, 2025 at 11:39 AM Oleg Kalnichevski <[email protected]> wrote: > Hi Ryan > > The test still fails in some CI environments. > > > https://github.com/apache/httpcomponents-core/actions/runs/20535102490/job/58992266978 > > Any theory as to why it has started failing out of the sudden? The test > was contributed in 2020 and I do not remember it causing any trouble. > > Oleg > > > On 12/27/2025 06:48, [email protected] wrote: > > This is an automated email from the ASF dual-hosted git repository. > > > > rschmitt pushed a commit to branch master > > in repository > https://gitbox.apache.org/repos/asf/httpcomponents-core.git > > > > > > The following commit(s) were added to refs/heads/master by this push: > > new 9d954702b MonitoringResponseOutOfOrderStrategyIntegrationTest: > Fix deadlock > > 9d954702b is described below > > > > commit 9d954702b2527dc0eac5adf8ca9284b0150bb21e > > Author: Ryan Schmitt <[email protected]> > > AuthorDate: Sat Dec 27 00:16:39 2025 -0500 > > > > MonitoringResponseOutOfOrderStrategyIntegrationTest: Fix deadlock > > > > This test can deadlock: both the client and the server can fill up > their > > respective output buffers and block indefinitely while > simultaneously > > trying to write to each other. > > > > It's clear why the client in this test sends a large request > entity, but > > I'm not able to identify a reason why the test server is also > sending a > > large response entity. To prevent the deadlock, I've changed the > test > > server to send a small response instead, one that will trivially > fit in > > the output buffer. > > > > I've also added an assertion to verify that the client's request was > > truncated upon receipt of the server's response, since this seems > to be > > the actual functionality we are testing. This new assertion fails if > > `MonitoringResponseOutOfOrderStrategy` is not provided. > > --- > > ...ringResponseOutOfOrderStrategyIntegrationTest.java | 19 > ++++++++----------- > > 1 file changed, 8 insertions(+), 11 deletions(-) > > > > diff --git > a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java > b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java > > index 81ff0e804..dddd39696 100644 > > --- > a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java > > +++ > b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/MonitoringResponseOutOfOrderStrategyIntegrationTest.java > > @@ -47,6 +47,7 @@ > > import org.apache.hc.core5.http.io.SocketConfig; > > import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; > > import org.apache.hc.core5.http.io.entity.EntityUtils; > > +import org.apache.hc.core5.http.io.entity.StringEntity; > > import org.apache.hc.core5.http.message.BasicClassicHttpRequest; > > import org.apache.hc.core5.http.protocol.HttpCoreContext; > > import org.apache.hc.core5.testing.SSLTestContexts; > > @@ -55,12 +56,10 @@ > > import org.apache.hc.core5.util.Timeout; > > import org.junit.jupiter.api.Assertions; > > import org.junit.jupiter.api.Test; > > +import org.junit.jupiter.api.Timeout.ThreadMode; > > import org.junit.jupiter.api.extension.RegisterExtension; > > > > abstract class MonitoringResponseOutOfOrderStrategyIntegrationTest { > > - > > - // Use a 16k buffer for consistent results across systems > > - private static final int BUFFER_SIZE = 16 * 1024; > > private static final Timeout TIMEOUT = Timeout.ofSeconds(3); > > > > private final URIScheme scheme; > > @@ -78,14 +77,12 @@ public > MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme schem > > .setSslContext(scheme == URIScheme.HTTPS ? > SSLTestContexts.createServerSSLContext() : null) > > .setSocketConfig(SocketConfig.custom() > > .setSoTimeout(TIMEOUT) > > - .setSndBufSize(BUFFER_SIZE) > > - .setRcvBufSize(BUFFER_SIZE) > > .setSoKeepAlive(false) > > .build()) > > > .setRequestRouter(RequestRouter.<HttpRequestHandler>builder() > > .addRoute(RequestRouter.LOCAL_AUTHORITY, "*", > (request, response, context) -> { > > response.setCode(400); > > - response.setEntity(new > AllOnesHttpEntity(200000)); > > + response.setEntity(new > StringEntity("stop")); > > }) > > > .resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER) > > .build())); > > @@ -95,8 +92,6 @@ public > MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme schem > > > .setSslContext(SSLTestContexts.createClientSSLContext()) > > .setSocketConfig(SocketConfig.custom() > > .setSoTimeout(TIMEOUT) > > - .setRcvBufSize(BUFFER_SIZE) > > - .setSndBufSize(BUFFER_SIZE) > > .setSoKeepAlive(false) > > .build()) > > > .setConnectionFactory(DefaultBHttpClientConnectionFactory.builder() > > @@ -105,7 +100,7 @@ public > MonitoringResponseOutOfOrderStrategyIntegrationTest(final URIScheme schem > > } > > > > @Test > > - @org.junit.jupiter.api.Timeout(value = 1, unit = > TimeUnit.MINUTES)// Failures may hang > > + @org.junit.jupiter.api.Timeout(value = 1, unit = TimeUnit.MINUTES, > threadMode = ThreadMode.SEPARATE_THREAD) > > void testResponseOutOfOrderWithDefaultStrategy() throws Exception { > > final HttpServer server = serverResource.start(); > > final HttpRequester requester = clientResource.start(); > > @@ -114,16 +109,18 @@ void testResponseOutOfOrderWithDefaultStrategy() > throws Exception { > > final HttpHost host = new HttpHost(scheme.id, "localhost", > server.getLocalPort()); > > > > final ClassicHttpRequest post = new > BasicClassicHttpRequest(Method.POST, "/"); > > - post.setEntity(new AllOnesHttpEntity(200000)); > > + final AllOnesHttpEntity requestEntity = new > AllOnesHttpEntity(20 * 1024 * 1024); > > + post.setEntity(requestEntity); > > > > try (final ClassicHttpResponse response = > requester.execute(host, post, TIMEOUT, context)) { > > Assertions.assertEquals(400, response.getCode()); > > EntityUtils.consumeQuietly(response.getEntity()); > > } > > + Assertions.assertTrue(requestEntity.remaining > 0, "Client > should have stopped sending data"); > > } > > > > private static final class AllOnesHttpEntity extends > AbstractHttpEntity { > > - private long remaining; > > + long remaining; > > > > protected AllOnesHttpEntity(final long length) { > > super(ContentType.APPLICATION_OCTET_STREAM, null, true); > > > >
