diff --git a/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs b/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
index ef82aca225..717b68597a 100644
--- a/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
+++ b/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
@@ -447,6 +447,14 @@ public TimeSpan? IdleTcpConnectionTimeout
///
///
/// When the time elapses, the attempt is cancelled and an error is returned. Longer timeouts will delay retries and failures.
+ /// The supplied is preserved unchanged on this property. At the transport boundary,
+ /// values in [, 1 second) are treated as 0 (use )
+ /// and values greater than or equal to 1 second are rounded up to the nearest whole second
+ /// (for example, 2.3 seconds becomes 3 seconds).
+ /// Negative values are not recommended and will emit a warning trace. They are preserved
+ /// on this property for backward compatibility; at the transport boundary they are truncated
+ /// to whole seconds and the TransportClient.Options.OpenTimeout getter returns
+ /// for any stored value that is not greater than .
///
public TimeSpan? OpenTcpConnectionTimeout
{
diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
index 38c8997dc0..c25001cc54 100644
--- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
+++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
@@ -12,7 +12,8 @@ namespace Microsoft.Azure.Cosmos
using System.Net;
using System.Net.Http;
using System.Net.Security;
- using System.Security.Cryptography.X509Certificates;
+ using System.Security.Cryptography.X509Certificates;
+ using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Cosmos.FaultInjection;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Azure.Documents;
@@ -605,12 +606,40 @@ public TimeSpan? IdleTcpConnectionTimeout
///
///
/// When the time elapses, the attempt is cancelled and an error is returned. Longer timeouts will delay retries and failures.
+ ///
+ /// The supplied is preserved unchanged on this property. At the
+ /// transport boundary the value is converted to whole seconds:
+ ///
+ ///
+ /// -
+ /// Values in [, 1 second) are treated as 0, causing the
+ /// configured to be used as the open-connection timeout.
+ ///
+ /// -
+ /// Values greater than or equal to 1 second are rounded up to the nearest whole second
+ /// (for example, 2.3 seconds becomes 3 seconds).
+ ///
+ ///
+ /// Negative values are not recommended and will emit a warning trace. They are preserved
+ /// on this property for backward compatibility. At the transport boundary they are converted
+ /// to whole seconds via truncation (e.g. −5.7 s → −5) and ultimately reach the
+ /// TransportClient.Options.OpenTimeout getter, which returns
+ /// for any value that is not greater than
+ /// .
///
public TimeSpan? OpenTcpConnectionTimeout
{
get => this.openTcpConnectionTimeout;
set
{
+ if (value.HasValue && value.Value < TimeSpan.Zero)
+ {
+ DefaultTrace.TraceWarning(
+ "OpenTcpConnectionTimeout value {0} is negative. Negative values are not recommended; "
+ + "the TransportClient will fall back to the configured RequestTimeout.",
+ value.Value);
+ }
+
this.openTcpConnectionTimeout = value;
this.ValidateDirectTCPSettings();
}
diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs
index c70b0b8668..4f439eec2a 100644
--- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs
+++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs
@@ -927,7 +927,24 @@ internal virtual void Initialize(Uri serviceEndpoint,
if (connectionPolicy.OpenTcpConnectionTimeout.HasValue)
{
- this.openConnectionTimeoutInSeconds = (int)connectionPolicy.OpenTcpConnectionTimeout.Value.TotalSeconds;
+ // Values in [TimeSpan.Zero, 1 second) become 0 (use RequestTimeout).
+ // Values >= 1 second round up to the nearest whole second, clamped to int.MaxValue.
+ // Negative values are truncated via (int)TotalSeconds, preserving pre-PR behavior.
+ TimeSpan openTcpConnectionTimeout = connectionPolicy.OpenTcpConnectionTimeout.Value;
+
+ if (openTcpConnectionTimeout < TimeSpan.Zero)
+ {
+ this.openConnectionTimeoutInSeconds = (int)openTcpConnectionTimeout.TotalSeconds;
+ }
+ else if (openTcpConnectionTimeout < TimeSpan.FromSeconds(1))
+ {
+ this.openConnectionTimeoutInSeconds = 0;
+ }
+ else
+ {
+ double ceilingSeconds = Math.Ceiling(openTcpConnectionTimeout.TotalSeconds);
+ this.openConnectionTimeoutInSeconds = ceilingSeconds > int.MaxValue ? int.MaxValue : (int)ceilingSeconds;
+ }
}
if (connectionPolicy.MaxRequestsPerTcpConnection.HasValue)
diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
index d7a9dac0c4..a3a93d5b14 100644
--- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
+++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
@@ -435,6 +435,12 @@ public CosmosClientBuilder WithConnectionModeDirect()
/// Controls the amount of time allowed for trying to establish a connection.
/// The default timeout is 5 seconds. Recommended values are greater than or equal to 5 seconds.
/// When the time elapses, the attempt is cancelled and an error is returned. Longer timeouts will delay retries and failures.
+ /// At the transport boundary, values in [, 1 second) are treated as 0
+ /// (use the configured request timeout). Values greater than or equal to 1 second are rounded up to
+ /// the nearest whole second (for example, 2.3 seconds becomes 3 seconds).
+ /// Negative values are not recommended and will emit a warning trace. They are preserved for
+ /// backward compatibility; at the transport boundary they are truncated to whole seconds and the
+ /// TransportClient returns the configured request timeout for any stored value not greater than zero.
///
///
/// Controls the number of requests allowed simultaneously over a single TCP connection. When more requests are in flight simultaneously, the direct/TCP client will open additional connections.
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs
index 9b3797ba11..27e7989653 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs
@@ -921,6 +921,191 @@ public void VerifyGetConnectionPolicyThrowIfDirectTcpSettingAreUsedInGatewayMode
Assert.ThrowsException(() => cosmosClientOptions.MaxTcpConnectionsPerEndpoint = maxTcpConnectionsPerEndpoint);
}
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutNegativeTimeSpanPassesThroughWithWarning()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ };
+
+ // Negative values are passed through unchanged (warning is logged but value is preserved).
+ options.OpenTcpConnectionTimeout = TimeSpan.FromMilliseconds(-1);
+ Assert.AreEqual(TimeSpan.FromMilliseconds(-1), options.OpenTcpConnectionTimeout,
+ "Negative value should be preserved unchanged on the property.");
+
+ options.OpenTcpConnectionTimeout = TimeSpan.FromSeconds(-30);
+ Assert.AreEqual(TimeSpan.FromSeconds(-30), options.OpenTcpConnectionTimeout,
+ "Negative value should be preserved unchanged on the property.");
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutZeroIsAllowedAndRoundTripsThroughConnectionPolicy()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = TimeSpan.Zero,
+ };
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ Assert.AreEqual(TimeSpan.Zero, policy.OpenTcpConnectionTimeout);
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutSubSecondNormalizesToZeroInRntbdConfig()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = TimeSpan.FromMilliseconds(500),
+ };
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ CosmosClientBuilder builder = new CosmosClientBuilder(
+ accountEndpoint: AccountEndpoint,
+ authKeyOrResourceToken: MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey);
+ CosmosClient cosmosClient = builder.Build(new MockDocumentClient(connectionPolicy: policy));
+
+ Microsoft.Azure.Cosmos.Tracing.TraceData.RntbdConnectionConfig tcpConfig =
+ cosmosClient.ClientConfigurationTraceDatum.RntbdConnectionConfig;
+
+ Assert.AreEqual(
+ 0,
+ tcpConfig.ConnectionTimeout,
+ "Sub-second OpenTcpConnectionTimeout must surface as 0 seconds (fall back to request timeout).");
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutExactlyOneSecondPreservedInRntbdConfig()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = TimeSpan.FromSeconds(1),
+ };
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ CosmosClientBuilder builder = new CosmosClientBuilder(
+ accountEndpoint: AccountEndpoint,
+ authKeyOrResourceToken: MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey);
+ CosmosClient cosmosClient = builder.Build(new MockDocumentClient(connectionPolicy: policy));
+
+ Microsoft.Azure.Cosmos.Tracing.TraceData.RntbdConnectionConfig tcpConfig =
+ cosmosClient.ClientConfigurationTraceDatum.RntbdConnectionConfig;
+
+ Assert.AreEqual(1, tcpConfig.ConnectionTimeout);
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutWholeSecondsPreservedInRntbdConfig()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = TimeSpan.FromSeconds(7),
+ };
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ CosmosClientBuilder builder = new CosmosClientBuilder(
+ accountEndpoint: AccountEndpoint,
+ authKeyOrResourceToken: MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey);
+ CosmosClient cosmosClient = builder.Build(new MockDocumentClient(connectionPolicy: policy));
+
+ Microsoft.Azure.Cosmos.Tracing.TraceData.RntbdConnectionConfig tcpConfig =
+ cosmosClient.ClientConfigurationTraceDatum.RntbdConnectionConfig;
+
+ Assert.AreEqual(7, tcpConfig.ConnectionTimeout);
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutFractionalRoundsUpInRntbdConfig()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = TimeSpan.FromSeconds(2.5),
+ };
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ CosmosClientBuilder builder = new CosmosClientBuilder(
+ accountEndpoint: AccountEndpoint,
+ authKeyOrResourceToken: MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey);
+ CosmosClient cosmosClient = builder.Build(new MockDocumentClient(connectionPolicy: policy));
+
+ Microsoft.Azure.Cosmos.Tracing.TraceData.RntbdConnectionConfig tcpConfig =
+ cosmosClient.ClientConfigurationTraceDatum.RntbdConnectionConfig;
+
+ Assert.AreEqual(
+ 3,
+ tcpConfig.ConnectionTimeout,
+ "Fractional OpenTcpConnectionTimeout (>= 1s) rounds up to the nearest whole second at the transport boundary.");
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutJustOverOneSecondRoundsUpInRntbdConfig()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = TimeSpan.FromMilliseconds(1001),
+ };
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ CosmosClientBuilder builder = new CosmosClientBuilder(
+ accountEndpoint: AccountEndpoint,
+ authKeyOrResourceToken: MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey);
+ CosmosClient cosmosClient = builder.Build(new MockDocumentClient(connectionPolicy: policy));
+
+ Microsoft.Azure.Cosmos.Tracing.TraceData.RntbdConnectionConfig tcpConfig =
+ cosmosClient.ClientConfigurationTraceDatum.RntbdConnectionConfig;
+
+ Assert.AreEqual(
+ 2,
+ tcpConfig.ConnectionTimeout,
+ "1.001s rounds up to 2s at the transport boundary.");
+ }
+
+ [TestMethod]
+ public void OpenTcpConnectionTimeoutFractionalPreservedOnConnectionPolicyTimeSpan()
+ {
+ TimeSpan customerSupplied = TimeSpan.FromSeconds(2.5);
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ OpenTcpConnectionTimeout = customerSupplied,
+ };
+
+ Assert.AreEqual(
+ customerSupplied,
+ options.OpenTcpConnectionTimeout,
+ "CosmosClientOptions preserves the supplied TimeSpan unchanged.");
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ Assert.AreEqual(
+ customerSupplied,
+ policy.OpenTcpConnectionTimeout,
+ "ConnectionPolicy preserves the supplied TimeSpan unchanged.");
+ }
+
+ [TestMethod]
+ public void WithConnectionModeDirectNegativeOpenTcpTimeoutPassesThroughUnchanged()
+ {
+ CosmosClientOptions options = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Direct,
+ };
+
+ // Negative values are preserved on the property and round-trip through ConnectionPolicy.
+ options.OpenTcpConnectionTimeout = TimeSpan.FromSeconds(-1);
+ Assert.AreEqual(TimeSpan.FromSeconds(-1), options.OpenTcpConnectionTimeout,
+ "Negative openTcpConnectionTimeout should be preserved unchanged.");
+
+ ConnectionPolicy policy = options.GetConnectionPolicy(clientId: 0);
+ Assert.AreEqual(TimeSpan.FromSeconds(-1), policy.OpenTcpConnectionTimeout,
+ "Negative value should round-trip through ConnectionPolicy unchanged.");
+ }
+
[TestMethod]
public void VerifyHttpClientFactoryBlockedWithConnectionLimit()
{
diff --git a/changelog.md b/changelog.md
index db34364e9c..d75ee5165a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Bugs Fixed
+- [5873](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/5873) Direct: Fixes silent truncation of `OpenTcpConnectionTimeout`. Sub-second values in [0, 1s) are now explicitly normalized to 0 and fractional values ≥ 1 second are rounded up to the nearest whole second (for example, 2.3s becomes 3s). Negative values emit a warning trace but are left unchanged for backward compatibility.
- [5783](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/5783) Container: Fixes SemanticRerankAsync TypeLoadException in derived classes
### [3.60.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.60.0) - 2026-5-18