Skip to content

[BUG] azure-core-http-netty ignores response timeout when write timeout is disabled #50795

Closed

Description

@bca111

Describe the bug

With azure-core-http-netty 1.16.5, setting writeTimeout(Duration.ZERO) also prevents the configured response timeout from firing.

I reproduced this with a WireMock endpoint that matches the request body and delays its response by one second. With a 100 ms response timeout and write timeout disabled, the request succeeds after about one second. Setting a positive write timeout makes the same request time out after about 100 ms.

Disabling the write timeout should only disable the write timeout, not the response timeout.

Exception or Stack Trace

No SDK exception is thrown in the disabled-write case. The reproducing test fails with this assertion:

org.opentest4j.AssertionFailedError: Expected java.lang.RuntimeException to be thrown, but nothing was thrown.
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
	at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:73)
	at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
	at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3128)
	at AzureNettyResponseTimeoutWireMockTest.responseTimeoutShouldNotDependOnWriteTimeout(AzureNettyResponseTimeoutWireMockTest.java:53)

To Reproduce

  1. In a Java test project using azure-core-http-netty:1.16.5 and JUnit Jupiter, add the WireMock test dependency below if it is not already available.
  2. Copy the test class below into src/test/java/AzureNettyResponseTimeoutWireMockTest.java.
  3. Run mvn -Dtest=AzureNettyResponseTimeoutWireMockTest test (from the HTTP Netty module if using the SDK repository).
  4. Observe that the writeTimeoutMillis=0 case fails because no exception is thrown; the writeTimeoutMillis=5000 control case passes with the expected SDK response-timeout exception. No Azure credentials or service are required.

Verified output:

Tests run: 2, Failures: 1, Errors: 0, Skipped: 0

Maven exits with code 1 because the disabled-write case reproduces the bug.

Code Snippet

WireMock test dependency, if needed. The standalone artifact avoids changing the project's Jetty dependencies:

<dependency>
  <groupId>org.wiremock</groupId>
  <artifactId>wiremock-standalone</artifactId>
  <version>3.13.1</version>
  <scope>test</scope>
</dependency>
import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpMethod;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpResponse;
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
import com.github.tomakehurst.wiremock.WireMockServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.time.Duration;
import java.util.concurrent.TimeoutException;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AzureNettyResponseTimeoutWireMockTest {
    private WireMockServer wireMock;

    @BeforeEach
    void setUp() {
        wireMock = new WireMockServer(wireMockConfig().bindAddress("127.0.0.1").dynamicPort());
        wireMock.start();
        wireMock.stubFor(post(urlEqualTo("/timeout"))
            .withRequestBody(equalTo("payload"))
            .willReturn(aResponse().withStatus(200).withFixedDelay(1_000)));
    }

    @AfterEach
    void tearDown() {
        if (wireMock != null) wireMock.stop();
    }

    @ParameterizedTest(name = "writeTimeoutMillis={0}")
    @ValueSource(longs = {0, 5_000})
    @Timeout(10)
    void responseTimeoutShouldNotDependOnWriteTimeout(long writeTimeoutMillis) {
        HttpClient client = new NettyAsyncHttpClientBuilder()
            .writeTimeout(Duration.ofMillis(writeTimeoutMillis))
            .responseTimeout(Duration.ofMillis(100))
            .build();
        HttpRequest request = new HttpRequest(HttpMethod.POST, "http://127.0.0.1:" + wireMock.port() + "/timeout")
            .setBody("payload");

        RuntimeException failure = assertThrows(RuntimeException.class, () -> {
            try (HttpResponse ignored = client.send(request).block(Duration.ofSeconds(3))) {
                // Returning normally means the configured response timeout did not fire.
            }
        });
        Throwable root = failure;
        while (root.getCause() != null) root = root.getCause();
        assertInstanceOf(TimeoutException.class, root);
        assertTrue(root.getMessage().contains("Channel response timed out"));
    }
}

Only writeTimeout and responseTimeout are configured on the SDK client; all other client settings retain their defaults. The three-second blocking timeout is just a test safety bound, and the assertion specifically checks the SDK's response-timeout exception.

Expected behavior

Both parameterized cases should pass: with a 100 ms response timeout and a one-second server response delay, the SDK should throw a response-timeout exception whether the write timeout is enabled or disabled.

Screenshots

Not applicable.

Setup (please complete the following information):

  • OS: macOS 26.5
  • IDE: Not applicable; reproduced from the terminal using Maven.
  • Library/Libraries:
    • com.azure:azure-core-http-netty:1.16.5
    • com.azure:azure-core:1.58.1 (SDK transitive dependency)
    • io.projectreactor.netty:reactor-netty-http:1.2.18 (SDK transitive dependency)
    • org.junit.jupiter:junit-jupiter:5.13.4 (test dependency)
    • org.wiremock:wiremock-standalone:3.13.1 (test dependency)
  • Java version: Temurin OpenJDK 26.0.1+8. The reproduction compiles with Java release 17.
  • App Server/Environment: Standalone Maven/JUnit test with a local HTTP server on 127.0.0.1; no application server or Azure service.
  • Frameworks: JUnit Jupiter and WireMock. No Spring Boot or other application framework.

The standalone reproduction uses the SDK's normal transitive dependencies. No NoClassDefFoundError or NoSuchMethodError occurs in this reproduction; a dependency version mismatch is not suspected.

Additional context

The suspected cause is in Azure SDK's own AzureSdkHandler, rather than Netty or Reactor Netty:

sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/implementation/AzureSdkHandler.java

Source, lines 129-138: the write-completion listener is installed only when writeTimeoutMillis > 0 at line 129. That listener also calls startResponseTracking() at line 134. When write timeout is disabled, the branch at line 138 writes without installing this listener.

Information Checklist

  • Bug Description Added
  • Repro Steps Added
  • Setup information Added

Activity

  1. added
    questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that
    ClientThis issue points to a problem in the data-plane of the library.
    customer-reportedIssues that are reported by GitHub users external to the Azure organization.
    needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK team
    on Oct 14, 2026
  2. github-actions commented on Oct 14, 2026

    @github-actions
    Contributor

    Thank you for your feedback. Tagging and routing to the team member(s) best able to assist.

    Generated by Agentic Triage for #50795 · auto · 53.7 AIC · ⌖ 3.23 AIC · ⊞ 16.2K · ◷

  3. github-actions commented on Oct 14, 2026

    @github-actions
    Contributor

    🎯 Agentic Issue Triage

    Summary: When writeTimeout is disabled (Duration.ZERO) on NettyAsyncHttpClientBuilder, the configured responseTimeout is also not enforced, causing requests to hang past the configured response timeout instead of throwing the expected timeout exception.

    📋 Issue Details

    Package: com.azure:azure-core-http-netty:1.16.5 (with com.azure:azure-core:1.58.1 and io.projectreactor.netty:reactor-netty-http:1.2.18 transitive dependencies)
    Affected API: com.azure.core.http.netty.implementation.AzureSdkHandler (used by NettyAsyncHttpClientBuilder)
    Scenarios:

    • Client configured with writeTimeout(Duration.ZERO) (disabled) and responseTimeout(Duration.ofMillis(100)): a server that delays its response by 1 second does not trigger the response timeout, and the request succeeds instead of failing.
    • Client configured with a positive writeTimeout (e.g. 5000 ms) and the same responseTimeout: the response timeout correctly fires after ~100 ms.

    Root ask: Fix AzureSdkHandler so that disabling the write timeout does not also disable the response timeout; the two timeouts should be independent.

    🔎 Debugging / Reproduction Notes

    The issue includes a self-contained WireMock-based JUnit 5 reproduction (no Azure credentials/service required) that parameterizes writeTimeoutMillis over {0, 5000}. The reporter points to AzureSdkHandler.java lines 129–138: the write-completion listener — which also calls startResponseTracking() — is only installed when writeTimeoutMillis > 0. When the write timeout is disabled, the code path that writes the request bypasses this listener entirely, so response tracking never starts.

    A related prior issue (#46847, NettyAsyncHttpClientBuilder overriding custom AddressResolverGroup) touches the same builder/handler area but is a different root cause.

    Suggested investigation steps:

    1. Review AzureSdkHandler around lines 120–140 to confirm startResponseTracking() is only reachable via the write-timeout listener branch.
    2. Refactor so startResponseTracking() (or equivalent response-timeout arming) is invoked unconditionally after the write completes, regardless of whether a write-timeout listener was attached.
    3. Extend the Netty HTTP client's internal test suite with a case mirroring the reporter's WireMock reproduction (disabled write timeout + short response timeout + delayed response) to prevent regression.
    🏷️ Label Confidence
    • Category: Client — the bug is in SDK-owned code (AzureSdkHandler in azure-core-http-netty), not a service/REST API behavior.
    • Service: Azure.Core — the affected module lives under /sdk/core/azure-core-http-netty/, which CODEOWNERS maps to the Azure.Core service label.
    • Confidence: High — the issue explicitly names the package, file path, and line numbers, with no ambiguity across services, and aligns with the pattern seen in reference issue [BUG] NettyAsyncHttpClientBuilder is overriding custom AddressResolverGroup with DefaultAddressResolverGroup.INSTANCE #46847 (Client + Azure.Core, customer-reported, issue-addressed).
    👥 Owner Routing
    • Matched CODEOWNERS entry: # ServiceLabel: %Azure.Core (line 56) — matches because the issue's predicted labels include Azure.Core, and this is the sole entry with that exact ServiceLabel (bottom-to-top scan found no later-appearing combined entry requiring additional labels).
    • AzureSdkOwners: @alzimmermsft
    • ServiceOwners: none listed
    • Routing action: Assigned @alzimmermsft (single AzureSdkOwner); added needs-team-attention since the reporter is an external customer.
    • Scan notes: Other Azure.Core* entries (%Azure.Core.V2 at line 62, %Azure.Core.AMQP at line 68) were considered but require different labels not present on this issue, so they were skipped.

    Generated by Agentic Triage for #50795 · auto · 53.7 AIC · ⌖ 3.23 AIC · ⊞ 16.2K · ◷

  4. alzimmermsft commented on Oct 14, 2026

    @alzimmermsft
    Member

    Good find bca111!

    This is definitely a bug and should be a straightforward fix, just need to change how timeout tracking is bound up in AzureSdkHandler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Azure.Coreazure-coreClientThis issue points to a problem in the data-plane of the library.customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions