Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,14 @@ public TimeSpan? IdleTcpConnectionTimeout
/// </value>
/// <remarks>
/// When the time elapses, the attempt is cancelled and an error is returned. Longer timeouts will delay retries and failures.
/// The supplied <see cref="TimeSpan"/> is preserved unchanged on this property. At the transport boundary,
/// values in [<see cref="TimeSpan.Zero"/>, 1 second) are treated as 0 (use <see cref="RequestTimeout"/>)
/// 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 <c>TransportClient.Options.OpenTimeout</c> getter returns
/// <see cref="RequestTimeout"/> for any stored value that is not greater than <see cref="TimeSpan.Zero"/>.
/// </remarks>
public TimeSpan? OpenTcpConnectionTimeout
{
Expand Down
31 changes: 30 additions & 1 deletion Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -593,12 +594,40 @@ public TimeSpan? IdleTcpConnectionTimeout
/// </value>
/// <remarks>
/// When the time elapses, the attempt is cancelled and an error is returned. Longer timeouts will delay retries and failures.
/// <para>
/// The supplied <see cref="TimeSpan"/> is preserved unchanged on this property. At the
/// transport boundary the value is converted to whole seconds:
/// </para>
/// <list type="bullet">
/// <item>
/// Values in [<see cref="TimeSpan.Zero"/>, 1 second) are treated as 0, causing the
/// configured <see cref="RequestTimeout"/> to be used as the open-connection timeout.
/// </item>
/// <item>
/// Values greater than or equal to 1 second are rounded up to the nearest whole second
/// (for example, 2.3 seconds becomes 3 seconds).
/// </item>
/// </list>
/// 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
/// <c>TransportClient.Options.OpenTimeout</c> getter, which returns
/// <see cref="RequestTimeout"/> for any value that is not greater than
/// <see cref="TimeSpan.Zero"/>.
/// </remarks>
public TimeSpan? OpenTcpConnectionTimeout
{
get => this.openTcpConnectionTimeout;
set
{
if (value.HasValue && value.Value < TimeSpan.Zero)
{
DefaultTrace.TraceWarning(
Comment thread
yumnahussain marked this conversation as resolved.
"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();
}
Expand Down
19 changes: 18 additions & 1 deletion Microsoft.Azure.Cosmos/src/DocumentClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 [<see cref="TimeSpan.Zero"/>, 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.
/// </param>
/// <param name="maxRequestsPerTcpConnection">
/// 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,191 @@ public void VerifyGetConnectionPolicyThrowIfDirectTcpSettingAreUsedInGatewayMode
Assert.ThrowsException<ArgumentException>(() => 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()
{
Expand Down
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ 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.
- [5870](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/5870) CrossRegionHedgingAvailabilityStrategy: Fixes StackOverflow in CrossRegionHedgingAvailabilityStrategy Observed in .NET Framework 4.7.2.
Comment thread
yumnahussain marked this conversation as resolved.
Outdated
- [5783](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/5783) Container: Fixes SemanticRerankAsync TypeLoadException in derived classes

### <a name="3.60.0"/> [3.60.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.60.0) - 2026-5-18
Expand Down
Loading