Skip to content
Open
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
59 changes: 57 additions & 2 deletions src/Ydb.Sdk/src/Ado/YdbConnectionStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private void InitDefaultValues()
_enableMultipleHttp2Connections = GrpcDefaultSettings.EnableMultipleHttp2Connections;
_maxSendMessageSize = GrpcDefaultSettings.MaxSendMessageSize;
_maxReceiveMessageSize = GrpcDefaultSettings.MaxReceiveMessageSize;
_pooledConnectionIdleTimeout = GrpcDefaultSettings.PooledConnectionIdleTimeoutSeconds;
_pooledConnectionLifetime = GrpcDefaultSettings.PooledConnectionLifetimeSeconds;
_disableDiscovery = GrpcDefaultSettings.DisableDiscovery;
_disableServerBalancer = false;
_enableImplicitSession = false;
Expand Down Expand Up @@ -425,6 +427,46 @@ public int MaxReceiveMessageSize

private int _maxReceiveMessageSize;

/// <summary>
/// Gets or sets the idle timeout for pooled HTTP/2 connections in seconds.
/// </summary>
/// <remarks>
/// Specifies how long a pooled connection can remain idle before being closed.
/// This helps prevent HTTP/2 protocol errors from long-lived connections.
/// <para>Default value: 60 seconds.</para>
/// </remarks>
public int PooledConnectionIdleTimeout
{
get => _pooledConnectionIdleTimeout;
set
{
_pooledConnectionIdleTimeout = value;
SaveValue(nameof(PooledConnectionIdleTimeout), value);
}
}

private int _pooledConnectionIdleTimeout;

/// <summary>
/// Gets or sets the lifetime for pooled HTTP/2 connections in seconds.
/// </summary>
/// <remarks>
/// Specifies the maximum lifetime for a pooled connection before being closed.
/// This helps prevent HTTP/2 protocol errors from long-lived connections.
/// <para>Default value: 300 seconds (5 minutes).</para>
/// </remarks>
public int PooledConnectionLifetime
{
get => _pooledConnectionLifetime;
set
{
_pooledConnectionLifetime = value;
SaveValue(nameof(PooledConnectionLifetime), value);
}
}

private int _pooledConnectionLifetime;

/// <summary>
/// Gets or sets a value indicating whether to disable server load balancing.
/// </summary>
Expand Down Expand Up @@ -585,7 +627,8 @@ public override object this[string keyword]
$"UseTls={UseTls};Host={Host};Port={Port};Database={Database};User={User};Password={Password};" +
$"ConnectTimeout={ConnectTimeout};KeepAlivePingDelay={KeepAlivePingDelay};KeepAlivePingTimeout={KeepAlivePingTimeout};" +
$"EnableMultipleHttp2Connections={EnableMultipleHttp2Connections};MaxSendMessageSize={MaxSendMessageSize};" +
$"MaxReceiveMessageSize={MaxReceiveMessageSize};DisableDiscovery={DisableDiscovery}";
$"MaxReceiveMessageSize={MaxReceiveMessageSize};PooledConnectionIdleTimeout={PooledConnectionIdleTimeout};" +
$"PooledConnectionLifetime={PooledConnectionLifetime};DisableDiscovery={DisableDiscovery}";

internal async Task<IDriver> BuildDriver()
{
Expand All @@ -611,7 +654,13 @@ internal async Task<IDriver> BuildDriver()
Password = Password,
EnableMultipleHttp2Connections = EnableMultipleHttp2Connections,
MaxSendMessageSize = MaxSendMessageSize,
MaxReceiveMessageSize = MaxReceiveMessageSize
MaxReceiveMessageSize = MaxReceiveMessageSize,
PooledConnectionIdleTimeout = PooledConnectionIdleTimeout == 0
? Timeout.InfiniteTimeSpan
: TimeSpan.FromSeconds(PooledConnectionIdleTimeout),
PooledConnectionLifetime = PooledConnectionLifetime == 0
? Timeout.InfiniteTimeSpan
: TimeSpan.FromSeconds(PooledConnectionLifetime)
};

return DisableDiscovery
Expand Down Expand Up @@ -707,6 +756,12 @@ static YdbConnectionOption()
AddOption(new YdbConnectionOption<int>(IntExtractor, (builder, maxReceiveMessageSize) =>
builder.MaxReceiveMessageSize = maxReceiveMessageSize),
"MaxReceiveMessageSize", "Max Receive Message Size");
AddOption(new YdbConnectionOption<int>(IntExtractor, (builder, pooledConnectionIdleTimeout) =>
builder.PooledConnectionIdleTimeout = pooledConnectionIdleTimeout),
"PooledConnectionIdleTimeout", "Pooled Connection Idle Timeout");
AddOption(new YdbConnectionOption<int>(IntExtractor, (builder, pooledConnectionLifetime) =>
builder.PooledConnectionLifetime = pooledConnectionLifetime),
"PooledConnectionLifetime", "Pooled Connection Lifetime");
AddOption(new YdbConnectionOption<bool>(BoolExtractor, (builder, disableDiscovery) =>
builder.DisableDiscovery = disableDiscovery), "DisableDiscovery", "Disable Discovery");
AddOption(new YdbConnectionOption<int>(IntExtractor,
Expand Down
14 changes: 14 additions & 0 deletions src/Ydb.Sdk/src/DriverConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ public class DriverConfig
/// </summary>
public int MaxReceiveMessageSize { get; init; } = GrpcDefaultSettings.MaxReceiveMessageSize;

/// <summary>
/// Gets or sets the idle timeout for pooled HTTP/2 connections.
/// This helps prevent connection corruption from long-lived connections.
/// </summary>
public TimeSpan PooledConnectionIdleTimeout { get; init; } =
TimeSpan.FromSeconds(GrpcDefaultSettings.PooledConnectionIdleTimeoutSeconds);

/// <summary>
/// Gets or sets the lifetime for pooled HTTP/2 connections.
/// This helps prevent connection corruption from long-lived connections.
/// </summary>
public TimeSpan PooledConnectionLifetime { get; init; } =
TimeSpan.FromSeconds(GrpcDefaultSettings.PooledConnectionLifetimeSeconds);

internal X509Certificate2Collection CustomServerCertificates { get; } = new();
internal TimeSpan EndpointDiscoveryInterval = TimeSpan.FromMinutes(1);
internal TimeSpan EndpointDiscoveryTimeout = TimeSpan.FromSeconds(10);
Expand Down
12 changes: 12 additions & 0 deletions src/Ydb.Sdk/src/GrpcDefaultSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,16 @@ internal static class GrpcDefaultSettings
internal const bool EnableMultipleHttp2Connections = false;

internal const bool DisableDiscovery = false;

/// <summary>
/// Default idle timeout (in seconds) for pooled HTTP/2 connections.
/// Set to prevent connection corruption from long-lived connections.
/// </summary>
internal const int PooledConnectionIdleTimeoutSeconds = 60;

/// <summary>
/// Default lifetime (in seconds) for pooled HTTP/2 connections.
/// Set to prevent connection corruption from long-lived connections.
/// </summary>
internal const int PooledConnectionLifetimeSeconds = 300; // 5 minutes
}
6 changes: 5 additions & 1 deletion src/Ydb.Sdk/src/Pool/ChannelPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ public GrpcChannel CreateChannel(string endpoint)
KeepAlivePingDelay = _config.KeepAlivePingDelay,
KeepAlivePingTimeout = _config.KeepAlivePingTimeout,
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always,
EnableMultipleHttp2Connections = _config.EnableMultipleHttp2Connections
EnableMultipleHttp2Connections = _config.EnableMultipleHttp2Connections,
// https://github.com/grpc/grpc-dotnet/issues/2641
// Set connection pool timeouts to prevent HTTP/2 PROTOCOL_ERROR from long-lived connections
PooledConnectionIdleTimeout = _config.PooledConnectionIdleTimeout,
PooledConnectionLifetime = _config.PooledConnectionLifetime
};

// https://github.com/grpc/grpc-dotnet/issues/2312#issuecomment-1790661801
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@ public void InitDefaultValues_WhenEmptyConstructorInvoke_ReturnDefaultConnection
Assert.False(ydbConnectionStringBuilder.EnableMultipleHttp2Connections);
Assert.Equal(MessageSize, ydbConnectionStringBuilder.MaxSendMessageSize);
Assert.Equal(MessageSize, ydbConnectionStringBuilder.MaxReceiveMessageSize);
Assert.Equal(60, ydbConnectionStringBuilder.PooledConnectionIdleTimeout);
Assert.Equal(300, ydbConnectionStringBuilder.PooledConnectionLifetime);
Assert.False(ydbConnectionStringBuilder.DisableDiscovery);
Assert.False(ydbConnectionStringBuilder.DisableServerBalancer);
Assert.False(ydbConnectionStringBuilder.UseTls);
Assert.False(ydbConnectionStringBuilder.EnableImplicitSession);

Assert.Equal("UseTls=False;Host=localhost;Port=2136;Database=/local;User=;Password=;ConnectTimeout=5;" +
"KeepAlivePingDelay=10;KeepAlivePingTimeout=10;EnableMultipleHttp2Connections=False;" +
$"MaxSendMessageSize={MessageSize};MaxReceiveMessageSize={MessageSize};DisableDiscovery=False",
$"MaxSendMessageSize={MessageSize};MaxReceiveMessageSize={MessageSize};PooledConnectionIdleTimeout=60;" +
"PooledConnectionLifetime=300;DisableDiscovery=False",
ydbConnectionStringBuilder.GrpcConnectionString);
}

Expand Down Expand Up @@ -86,7 +89,8 @@ public void InitConnectionStringBuilder_WhenExpectedKeys_ReturnUpdatedConnection
Assert.True(ydbConnectionStringBuilder.EnableImplicitSession);
Assert.Equal("UseTls=True;Host=server;Port=2135;Database=/my/path;User=Kirill;Password=;ConnectTimeout=30;" +
"KeepAlivePingDelay=30;KeepAlivePingTimeout=60;EnableMultipleHttp2Connections=True;" +
"MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;DisableDiscovery=True",
"MaxSendMessageSize=1000000;MaxReceiveMessageSize=1000000;PooledConnectionIdleTimeout=60;" +
"PooledConnectionLifetime=300;DisableDiscovery=True",
ydbConnectionStringBuilder.GrpcConnectionString);
}

Expand All @@ -98,15 +102,17 @@ public void Host_WhenSetInProperty_ReturnUpdatedConnectionString()
Assert.Equal(
"UseTls=False;Host=server;Port=2135;Database=/my/path;User=Kirill;Password=;ConnectTimeout=5;" +
"KeepAlivePingDelay=10;KeepAlivePingTimeout=10;EnableMultipleHttp2Connections=False;" +
$"MaxSendMessageSize={MessageSize};MaxReceiveMessageSize={MessageSize};DisableDiscovery=False",
$"MaxSendMessageSize={MessageSize};MaxReceiveMessageSize={MessageSize};PooledConnectionIdleTimeout=60;" +
"PooledConnectionLifetime=300;DisableDiscovery=False",
ydbConnectionStringBuilder.GrpcConnectionString);
Assert.Equal("server", ydbConnectionStringBuilder.Host);
ydbConnectionStringBuilder.Host = "new_server";
Assert.Equal("new_server", ydbConnectionStringBuilder.Host);
Assert.Equal(
"UseTls=False;Host=new_server;Port=2135;Database=/my/path;User=Kirill;Password=;ConnectTimeout=5;" +
"KeepAlivePingDelay=10;KeepAlivePingTimeout=10;EnableMultipleHttp2Connections=False;" +
$"MaxSendMessageSize={MessageSize};MaxReceiveMessageSize={MessageSize};DisableDiscovery=False",
$"MaxSendMessageSize={MessageSize};MaxReceiveMessageSize={MessageSize};PooledConnectionIdleTimeout=60;" +
"PooledConnectionLifetime=300;DisableDiscovery=False",
ydbConnectionStringBuilder.GrpcConnectionString);
Assert.Equal("Host=new_server;Port=2135;Database=/my/path;User=Kirill",
ydbConnectionStringBuilder.ConnectionString);
Expand Down
Loading