Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [8.0.1] - 2026-04-28

### Fixed
- Fixed double-application of the 30-second client grace period on every request.
In v7.0.6 a 30s grace was added in `ClientImpl.determineTimeout`, but `BaseClient.getContextTimeout`
was already adding the same 30s buffer (`EXTRA_TIMEOUT_FOR_CLIENT_SIDE`). The effective HTTP-client
timeout was `serverTimeout + 60s`; it is now restored to `serverTimeout + 30s`.

## [8.0.0] - 2026-02-04

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;


import org.jetbrains.annotations.NotNull;

import com.azure.core.http.HttpClient;
Expand Down Expand Up @@ -45,7 +44,6 @@ class ClientImpl extends BaseClient {
public static final String MGMT_ENDPOINT_VERSION = "v1";
public static final String QUERY_ENDPOINT_VERSION = "v2";
public static final String STREAMING_VERSION = "v1";
private static final Long CLIENT_GRACE_PERIOD_IN_MILLISECS = TimeUnit.SECONDS.toMillis(30);
private static final Long COMMAND_TIMEOUT_IN_MILLISECS = TimeUnit.MINUTES.toMillis(10);
private static final Long QUERY_TIMEOUT_IN_MILLISECS = TimeUnit.MINUTES.toMillis(4);
private static final Long STREAMING_INGEST_TIMEOUT_IN_MILLISECS = TimeUnit.MINUTES.toMillis(10);
Expand Down Expand Up @@ -412,7 +410,9 @@ private Mono<InputStream> executeStreamingQuery(String clusterEndpoint, KustoReq
"ClientImpl.executeStreamingQuery", updateAndGetExecuteTracingAttributes(kr.getDatabase(), properties)));
}

private long determineTimeout(ClientRequestProperties properties, CommandType commandType, String clusterUrl) {
// Package-private for testability. The 30s client-side grace is applied once in BaseClient.getContextTimeout
// (via EXTRA_TIMEOUT_FOR_CLIENT_SIDE); do not add it here as well or it will be applied twice.
long determineTimeout(ClientRequestProperties properties, CommandType commandType, String clusterUrl) {
Object skipBoolean = properties.getOption(ClientRequestProperties.OPTION_NO_REQUEST_TIMEOUT);
if (skipBoolean instanceof Boolean && (Boolean) skipBoolean) {
return Long.MAX_VALUE;
Expand Down Expand Up @@ -440,7 +440,7 @@ private long determineTimeout(ClientRequestProperties properties, CommandType co
// If we set the timeout ourself, we need to update the server header
properties.setTimeoutInMilliSec(timeoutMs);

return timeoutMs + CLIENT_GRACE_PERIOD_IN_MILLISECS;
return timeoutMs;
}

private Mono<String> getAuthorizationHeaderValueAsync() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.microsoft.azure.kusto.data;

import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;

/**
* Regression tests for the v7.0.6 "double grace period" bug.
* <p>
* In v7.0.6, {@code ClientImpl.determineTimeout} added a 30-second client grace period to
* its return value. However, {@code BaseClient.getContextTimeout} already adds the same
* 30-second buffer ({@code EXTRA_TIMEOUT_FOR_CLIENT_SIDE}) when constructing the HTTP
* request context. The result was that every request used an effective client-side
* timeout of {@code serverTimeout + 60s} instead of {@code serverTimeout + 30s}.
* <p>
* The fix removes the grace from {@code determineTimeout}; this test pins
* {@code determineTimeout} to return the raw timeout (no grace) so the regression cannot
* be reintroduced silently.
*/
public class ClientImplTimeoutTest {

private static final long QUERY_DEFAULT_MS = TimeUnit.MINUTES.toMillis(4);
private static final long ADMIN_DEFAULT_MS = TimeUnit.MINUTES.toMillis(10);
private static final long STREAMING_INGEST_DEFAULT_MS = TimeUnit.MINUTES.toMillis(10);

private ClientImpl newClient() throws URISyntaxException {
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
return (ClientImpl) ClientFactory.createClient(csb);
}

@Test
@DisplayName("determineTimeout returns default query timeout (no grace added at this layer)")
void defaultQueryTimeout() throws URISyntaxException {
ClientImpl client = newClient();
ClientRequestProperties crp = new ClientRequestProperties();
long actual = client.determineTimeout(crp, CommandType.QUERY, "https://testcluster.kusto.windows.net");
Assertions.assertEquals(QUERY_DEFAULT_MS, actual);
}

@Test
@DisplayName("determineTimeout returns default admin command timeout (no grace added at this layer)")
void defaultAdminTimeout() throws URISyntaxException {
ClientImpl client = newClient();
ClientRequestProperties crp = new ClientRequestProperties();
long actual = client.determineTimeout(crp, CommandType.ADMIN_COMMAND, "https://testcluster.kusto.windows.net");
Assertions.assertEquals(ADMIN_DEFAULT_MS, actual);
}

@Test
@DisplayName("determineTimeout returns default streaming ingest timeout (no grace added at this layer)")
void defaultStreamingIngestTimeout() throws URISyntaxException {
ClientImpl client = newClient();
ClientRequestProperties crp = new ClientRequestProperties();
long actual = client.determineTimeout(crp, CommandType.STREAMING_INGEST, "https://testcluster.kusto.windows.net");
Assertions.assertEquals(STREAMING_INGEST_DEFAULT_MS, actual);
}

@Test
@DisplayName("determineTimeout returns user-provided timeout verbatim (no grace added at this layer)")
void userProvidedTimeout() throws URISyntaxException {
ClientImpl client = newClient();
ClientRequestProperties crp = new ClientRequestProperties();
long userTimeout = TimeUnit.MINUTES.toMillis(7);
crp.setTimeoutInMilliSec(userTimeout);

long actual = client.determineTimeout(crp, CommandType.QUERY, "https://testcluster.kusto.windows.net");
Assertions.assertEquals(userTimeout, actual);
}

@Test
@DisplayName("determineTimeout returns Long.MAX_VALUE when OPTION_NO_REQUEST_TIMEOUT is true")
void noRequestTimeoutOption() throws URISyntaxException {
ClientImpl client = newClient();
ClientRequestProperties crp = new ClientRequestProperties();
crp.setOption(ClientRequestProperties.OPTION_NO_REQUEST_TIMEOUT, true);

long actual = client.determineTimeout(crp, CommandType.QUERY, "https://testcluster.kusto.windows.net");
Assertions.assertEquals(Long.MAX_VALUE, actual);
}

@Test
@DisplayName("determineTimeout writes the raw (no-grace) timeout back into the server-timeout option")
void serverTimeoutHeaderUnaffectedByGrace() throws URISyntaxException {
ClientImpl client = newClient();
ClientRequestProperties crp = new ClientRequestProperties();
client.determineTimeout(crp, CommandType.QUERY, "https://testcluster.kusto.windows.net");

// Server-timeout option must reflect the raw default (4 minutes), not a graced value.
Assertions.assertEquals("00:04:00", crp.getOption(ClientRequestProperties.OPTION_SERVER_TIMEOUT));
}
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</developers>

<properties>
<revision>8.0.0</revision> <!-- CHANGE THIS to adjust project version-->
<revision>8.0.1</revision> <!-- CHANGE THIS to adjust project version-->
<ingest-v2.revision>0.0.1-beta</ingest-v2.revision> <!-- CHANGE THIS to adjust ingest-v2 version --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<azure-bom-version>1.3.6</azure-bom-version>
Expand Down
2 changes: 1 addition & 1 deletion quickstart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</developers>

<properties>
<revision>8.0.0</revision> <!-- CHANGE THIS to match project version in the root (not technically parent) pom -->
<revision>8.0.1</revision> <!-- CHANGE THIS to match project version in the root (not technically parent) pom -->
<java.version>11</java.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
Expand Down
Loading