diff --git a/src/Momento.Sdk/Config/AuthConfigurations.cs b/src/Momento.Sdk/Config/AuthConfigurations.cs index 6bfee95b..e560a0a9 100644 --- a/src/Momento.Sdk/Config/AuthConfigurations.cs +++ b/src/Momento.Sdk/Config/AuthConfigurations.cs @@ -34,12 +34,7 @@ public static IAuthConfiguration V1(ILoggerFactory? loggerFactory = null) var finalLoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; IAuthTransportStrategy transportStrategy = new StaticAuthTransportStrategy( loggerFactory: finalLoggerFactory, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(15000), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(15000)) ); return new Default(finalLoggerFactory, transportStrategy); } diff --git a/src/Momento.Sdk/Config/Configurations.cs b/src/Momento.Sdk/Config/Configurations.cs index 3467f998..370d9ea4 100644 --- a/src/Momento.Sdk/Config/Configurations.cs +++ b/src/Momento.Sdk/Config/Configurations.cs @@ -55,12 +55,7 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null) ITransportStrategy transportStrategy = new StaticTransportStrategy( loggerFactory: finalLoggerFactory, maxConcurrentRequests: 200, // max of 2 connections https://github.com/momentohq/client-sdk-dotnet/issues/460 - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(15000), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(15000)) ); return new Laptop(finalLoggerFactory, retryStrategy, transportStrategy); } @@ -113,12 +108,7 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null) ITransportStrategy transportStrategy = new StaticTransportStrategy( loggerFactory: finalLoggerFactory, maxConcurrentRequests: 400, // max of 4 connections https://github.com/momentohq/client-sdk-dotnet/issues/460 - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(1100), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(1100)) ); return new Default(finalLoggerFactory, retryStrategy, transportStrategy); } @@ -169,12 +159,7 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null) ITransportStrategy transportStrategy = new StaticTransportStrategy( loggerFactory: finalLoggerFactory, maxConcurrentRequests: 20, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(500), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(500)) ); return new LowLatency(finalLoggerFactory, retryStrategy, transportStrategy); } @@ -207,7 +192,14 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null) { var config = Default.V1(loggerFactory); var transportStrategy = config.TransportStrategy.WithSocketsHttpHandlerOptions( - SocketsHttpHandlerOptions.Of(pooledConnectionIdleTimeout: TimeSpan.FromMinutes(6))); + SocketsHttpHandlerOptions.Of( + pooledConnectionIdleTimeout: TimeSpan.FromMinutes(6), + enableMultipleHttp2Connections: true, + keepAlivePingTimeout: System.Threading.Timeout.InfiniteTimeSpan, + keepAlivePingDelay: System.Threading.Timeout.InfiniteTimeSpan, + keepAlivePermitWithoutCalls: false + ) + ); return config.WithTransportStrategy(transportStrategy); } diff --git a/src/Momento.Sdk/Config/TopicConfigurations.cs b/src/Momento.Sdk/Config/TopicConfigurations.cs index ab866e11..5c9822a0 100644 --- a/src/Momento.Sdk/Config/TopicConfigurations.cs +++ b/src/Momento.Sdk/Config/TopicConfigurations.cs @@ -35,12 +35,7 @@ public static ITopicConfiguration latest(ILoggerFactory? loggerFactory = null) var finalLoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; ITopicTransportStrategy transportStrategy = new StaticTopicTransportStrategy( loggerFactory: finalLoggerFactory, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(15000), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(15000)) ); return new Laptop(finalLoggerFactory, transportStrategy); } @@ -70,12 +65,7 @@ public static ITopicConfiguration latest(ILoggerFactory? loggerFactory = null) var finalLoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; ITopicTransportStrategy transportStrategy = new StaticTopicTransportStrategy( loggerFactory: finalLoggerFactory, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(15000), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(15000)) ); return new Mobile(finalLoggerFactory, transportStrategy); } @@ -112,12 +102,7 @@ public static ITopicConfiguration Latest(ILoggerFactory? loggerFactory = null) var finalLoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; ITopicTransportStrategy transportStrategy = new StaticTopicTransportStrategy( loggerFactory: finalLoggerFactory, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(1100), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(1100)) ); return new Default(finalLoggerFactory, transportStrategy); } @@ -151,12 +136,7 @@ public static ITopicConfiguration Latest(ILoggerFactory? loggerFactory = null) var finalLoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; ITopicTransportStrategy transportStrategy = new StaticTopicTransportStrategy( loggerFactory: finalLoggerFactory, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(500), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(500)) ); return new LowLatency(finalLoggerFactory, transportStrategy); } diff --git a/src/Momento.Sdk/Config/Transport/IGrpcConfiguration.cs b/src/Momento.Sdk/Config/Transport/IGrpcConfiguration.cs index b825cf89..31e4bf93 100644 --- a/src/Momento.Sdk/Config/Transport/IGrpcConfiguration.cs +++ b/src/Momento.Sdk/Config/Transport/IGrpcConfiguration.cs @@ -43,36 +43,6 @@ public interface IGrpcConfiguration /// public SocketsHttpHandlerOptions SocketsHttpHandlerOptions { get; } - /// - /// Override the time to wait for a response from a keepalive or ping. - /// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time - /// when the connection is idle. However, they are very problematic for lambda environments where the lambda - /// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received - /// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy. - /// Therefore, keep-alives should be disabled in lambda and similar environments. - /// - public TimeSpan KeepAlivePingTimeout { get; } - - /// - /// After a duration of this time the client/server pings its peer to see if the transport is still alive. - /// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time - /// when the connection is idle. However, they are very problematic for lambda environments where the lambda - /// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received - /// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy. - /// Therefore, keep-alives should be disabled in lambda and similar environments. - /// - public TimeSpan KeepAlivePingDelay { get; } - - /// - /// Indicates if it permissible to send keepalive pings from the client without any outstanding streams. - /// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time - /// when the connection is idle. However, they are very problematic for lambda environments where the lambda - /// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received - /// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy. - /// Therefore, keep-alives should be disabled in lambda and similar environments. - /// - public bool KeepAlivePermitWithoutCalls { get; } - /// /// Copy constructor to override the Deadline /// diff --git a/src/Momento.Sdk/Config/Transport/SocketsHttpHandlerOptions.cs b/src/Momento.Sdk/Config/Transport/SocketsHttpHandlerOptions.cs index 4393bcf6..52d8f586 100644 --- a/src/Momento.Sdk/Config/Transport/SocketsHttpHandlerOptions.cs +++ b/src/Momento.Sdk/Config/Transport/SocketsHttpHandlerOptions.cs @@ -1,5 +1,6 @@ #pragma warning disable 1591 using System; +using System.Net.Http; using Momento.Sdk.Internal; namespace Momento.Sdk.Config.Transport; @@ -9,6 +10,36 @@ public class SocketsHttpHandlerOptions public TimeSpan PooledConnectionIdleTimeout { get; } = DefaultPooledConnectionIdleTimeout; public bool EnableMultipleHttp2Connections { get; } = true; + /// + /// Override the time to wait for a response from a keepalive or ping. + /// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time + /// when the connection is idle. However, they are very problematic for lambda environments where the lambda + /// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received + /// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy. + /// Therefore, keep-alives should be disabled in lambda and similar environments. + /// + public TimeSpan KeepAlivePingTimeout { get; } = TimeSpan.FromMilliseconds(1000); + + /// + /// After a duration of this time the client/server pings its peer to see if the transport is still alive. + /// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time + /// when the connection is idle. However, they are very problematic for lambda environments where the lambda + /// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received + /// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy. + /// Therefore, keep-alives should be disabled in lambda and similar environments. + /// + public TimeSpan KeepAlivePingDelay { get; } = TimeSpan.FromMilliseconds(5000); + + /// + /// Indicates if it permissible to send keepalive pings from the client without any outstanding streams. + /// NOTE: keep-alives are very important for long-lived server environments where there may be periods of time + /// when the connection is idle. However, they are very problematic for lambda environments where the lambda + /// runtime is continuously frozen and unfrozen, because the lambda may be frozen before the "ACK" is received + /// from the server. This can cause the keep-alive to timeout even though the connection is completely healthy. + /// Therefore, keep-alives should be disabled in lambda and similar environments. + /// + public bool KeepAlivePermitWithoutCalls { get; } = true; + public SocketsHttpHandlerOptions() { } public SocketsHttpHandlerOptions(TimeSpan pooledConnectionIdleTimeout) : this(pooledConnectionIdleTimeout, true) { } public SocketsHttpHandlerOptions(bool enableMultipleHttp2Connections) : this(DefaultPooledConnectionIdleTimeout, enableMultipleHttp2Connections) { } @@ -19,6 +50,21 @@ public SocketsHttpHandlerOptions(TimeSpan pooledConnectionIdleTimeout, bool enab PooledConnectionIdleTimeout = pooledConnectionIdleTimeout; EnableMultipleHttp2Connections = enableMultipleHttp2Connections; } + public SocketsHttpHandlerOptions( + TimeSpan pooledConnectionIdleTimeout, + bool enableMultipleHttp2Connections, + TimeSpan keepAlivePingTimeout, + TimeSpan keepAlivePingDelay, + bool keepAlivePermitWithoutCalls + ) + { + Utils.ArgumentStrictlyPositive(pooledConnectionIdleTimeout, nameof(pooledConnectionIdleTimeout)); + PooledConnectionIdleTimeout = pooledConnectionIdleTimeout; + EnableMultipleHttp2Connections = enableMultipleHttp2Connections; + KeepAlivePingTimeout = keepAlivePingTimeout; + KeepAlivePingDelay = keepAlivePingDelay; + KeepAlivePermitWithoutCalls = keepAlivePermitWithoutCalls; + } public SocketsHttpHandlerOptions WithPooledConnectionIdleTimeout(TimeSpan pooledConnectionIdleTimeout) { @@ -45,6 +91,23 @@ public static SocketsHttpHandlerOptions Of(TimeSpan pooledConnectionIdleTimeout, return new SocketsHttpHandlerOptions(pooledConnectionIdleTimeout, enableMultipleHttp2Connections); } + public static SocketsHttpHandlerOptions Of( + TimeSpan pooledConnectionIdleTimeout, + bool enableMultipleHttp2Connections, + TimeSpan keepAlivePingTimeout, + TimeSpan keepAlivePingDelay, + bool keepAlivePermitWithoutCalls + ) + { + return new SocketsHttpHandlerOptions( + pooledConnectionIdleTimeout, + enableMultipleHttp2Connections, + keepAlivePingTimeout, + keepAlivePingDelay, + keepAlivePermitWithoutCalls + ); + } + public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) diff --git a/src/Momento.Sdk/Config/Transport/StaticTransportStrategy.cs b/src/Momento.Sdk/Config/Transport/StaticTransportStrategy.cs index e83790aa..f6cf23e4 100644 --- a/src/Momento.Sdk/Config/Transport/StaticTransportStrategy.cs +++ b/src/Momento.Sdk/Config/Transport/StaticTransportStrategy.cs @@ -34,17 +34,11 @@ public class StaticGrpcConfiguration : IGrpcConfiguration /// Customizations to low-level gRPC channel configuration /// minimum number of gRPC channels to open /// Customizations to the SocketsHttpHandler - /// Override the time to wait for a response from a keepalive ping - /// Override how often the client sends keepalive pings the server - /// Sets option to send keepalive pings from the client without any outstanding RPCs public StaticGrpcConfiguration( TimeSpan deadline, GrpcChannelOptions? grpcChannelOptions = null, int minNumGrpcChannels = 1, - SocketsHttpHandlerOptions? socketsHttpHandlerOptions = null, - TimeSpan? keepAlivePingTimeout = null, - TimeSpan? keepAlivePingDelay = null, - bool? keepAlivePermitWithoutCalls = null + SocketsHttpHandlerOptions? socketsHttpHandlerOptions = null ) { Utils.ArgumentStrictlyPositive(deadline, nameof(deadline)); @@ -52,9 +46,6 @@ public StaticGrpcConfiguration( this.MinNumGrpcChannels = minNumGrpcChannels; this.GrpcChannelOptions = grpcChannelOptions; this.SocketsHttpHandlerOptions = socketsHttpHandlerOptions ?? new SocketsHttpHandlerOptions(); - this.KeepAlivePingTimeout = keepAlivePingTimeout ?? System.Threading.Timeout.InfiniteTimeSpan; - this.KeepAlivePingDelay = keepAlivePingDelay ?? System.Threading.Timeout.InfiniteTimeSpan; - this.KeepAlivePermitWithoutCalls = keepAlivePermitWithoutCalls ?? false; } /// diff --git a/src/Momento.Sdk/Config/VectorIndexConfigurations.cs b/src/Momento.Sdk/Config/VectorIndexConfigurations.cs index cb9b3b4d..9a3c19d7 100644 --- a/src/Momento.Sdk/Config/VectorIndexConfigurations.cs +++ b/src/Momento.Sdk/Config/VectorIndexConfigurations.cs @@ -50,12 +50,7 @@ public static IVectorIndexConfiguration V1(ILoggerFactory? loggerFactory = null) var finalLoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; IVectorIndexTransportStrategy transportStrategy = new StaticVectorIndexTransportStrategy( loggerFactory: finalLoggerFactory, - grpcConfig: new StaticGrpcConfiguration( - deadline: TimeSpan.FromMilliseconds(15000), - keepAlivePermitWithoutCalls: true, - keepAlivePingDelay: TimeSpan.FromMilliseconds(5000), - keepAlivePingTimeout: TimeSpan.FromMilliseconds(1000) - ) + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(15000)) ); return new Laptop(finalLoggerFactory, transportStrategy); } diff --git a/src/Momento.Sdk/Internal/GrpcManager.cs b/src/Momento.Sdk/Internal/GrpcManager.cs index 82e48266..b646c15a 100644 --- a/src/Momento.Sdk/Internal/GrpcManager.cs +++ b/src/Momento.Sdk/Internal/GrpcManager.cs @@ -89,21 +89,15 @@ public GrpcChannelOptions GrpcChannelOptionsFromGrpcConfig(IGrpcConfiguration gr channelOptions.MaxReceiveMessageSize = grpcConfig.GrpcChannelOptions?.MaxReceiveMessageSize ?? DEFAULT_MAX_MESSAGE_SIZE; channelOptions.MaxSendMessageSize = grpcConfig.GrpcChannelOptions?.MaxSendMessageSize ?? DEFAULT_MAX_MESSAGE_SIZE; #if NET5_0_OR_GREATER - var keepAliveWithoutCalls = System.Net.Http.HttpKeepAlivePingPolicy.WithActiveRequests; - if (grpcConfig.KeepAlivePermitWithoutCalls == true) - { - keepAliveWithoutCalls = System.Net.Http.HttpKeepAlivePingPolicy.Always; - } - if (SocketsHttpHandler.IsSupported) // see: https://github.com/grpc/grpc-dotnet/blob/098dca892a3949ade411c3f2f66003f7b330dfd2/src/Shared/HttpHandlerFactory.cs#L28-L30 { channelOptions.HttpHandler = new SocketsHttpHandler { EnableMultipleHttp2Connections = grpcConfig.SocketsHttpHandlerOptions.EnableMultipleHttp2Connections, PooledConnectionIdleTimeout = grpcConfig.SocketsHttpHandlerOptions.PooledConnectionIdleTimeout, - KeepAlivePingTimeout = grpcConfig.KeepAlivePingTimeout, - KeepAlivePingDelay = grpcConfig.KeepAlivePingDelay, - KeepAlivePingPolicy = keepAliveWithoutCalls + KeepAlivePingTimeout = grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingTimeout, + KeepAlivePingDelay = grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingDelay, + KeepAlivePingPolicy = grpcConfig.SocketsHttpHandlerOptions.KeepAlivePermitWithoutCalls == true ? System.Net.Http.HttpKeepAlivePingPolicy.Always : System.Net.Http.HttpKeepAlivePingPolicy.WithActiveRequests, }; } #elif USE_GRPC_WEB diff --git a/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs b/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs index 0dd1c1df..1c783dc9 100644 --- a/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs +++ b/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs @@ -25,9 +25,9 @@ public void LambdaConfigDisablesKeepAlive() { var config = Configurations.InRegion.Lambda.Latest(); var grpcConfig = config.TransportStrategy.GrpcConfig; - Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, grpcConfig.KeepAlivePingTimeout); - Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, grpcConfig.KeepAlivePingDelay); - Assert.False(grpcConfig.KeepAlivePermitWithoutCalls); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingTimeout); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingDelay); + Assert.False(grpcConfig.SocketsHttpHandlerOptions.KeepAlivePermitWithoutCalls); } [Fact] @@ -35,8 +35,8 @@ public void LaptopConfigEnablesKeepAlive() { var config = Configurations.Laptop.Latest(); var grpcConfig = config.TransportStrategy.GrpcConfig; - Assert.Equal(TimeSpan.FromMilliseconds(1000), grpcConfig.KeepAlivePingTimeout); - Assert.Equal(TimeSpan.FromMilliseconds(5000), grpcConfig.KeepAlivePingDelay); - Assert.True(grpcConfig.KeepAlivePermitWithoutCalls); + Assert.Equal(TimeSpan.FromMilliseconds(1000), grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingTimeout); + Assert.Equal(TimeSpan.FromMilliseconds(5000), grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingDelay); + Assert.True(grpcConfig.SocketsHttpHandlerOptions.KeepAlivePermitWithoutCalls); } }