diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cd3efc60..c09495fc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -8,13 +8,16 @@ jobs: build_csharp: strategy: matrix: + os: [ubuntu-latest, windows-latest] + target-framework: [net6.0] + grpc-web: [false, true] include: - - os: ubuntu-latest - target-framework: net6.0 - os: windows-latest - target-framework: net6.0 + target-framework: net462 + grpc-web: false - os: windows-latest target-framework: net462 + grpc-web: true runs-on: ${{ matrix.os }} env: TEST_AUTH_TOKEN: ${{ secrets.ALPHA_TEST_AUTH_TOKEN }} @@ -47,7 +50,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build - run: dotnet build + run: dotnet build ${{ matrix.grpc-web && '-p:DefineConstants=USE_GRPC_WEB' || '' }} - name: Unit Test run: dotnet test --logger "console;verbosity=detailed" -f ${{ matrix.target-framework }} tests/Unit/Momento.Sdk.Tests diff --git a/.github/workflows/on-push-to-main-branch.yaml b/.github/workflows/on-push-to-main-branch.yaml index 1ee763cd..bca2453b 100644 --- a/.github/workflows/on-push-to-main-branch.yaml +++ b/.github/workflows/on-push-to-main-branch.yaml @@ -8,11 +8,16 @@ jobs: build_csharp: strategy: matrix: + os: [ubuntu-latest, windows-latest] + target-framework: [net6.0] + grpc-web: [false, true] include: - - os: ubuntu-latest - target-framework: net6.0 - os: windows-latest target-framework: net462 + grpc-web: false + - os: windows-latest + target-framework: net462 + grpc-web: true runs-on: ${{ matrix.os }} env: TEST_AUTH_TOKEN: ${{ secrets.ALPHA_TEST_AUTH_TOKEN }} @@ -32,7 +37,7 @@ jobs: dotnet-version: "6.0.x" - name: Build - run: dotnet build + run: dotnet build ${{ matrix.grpc-web && '-p:DefineConstants=USE_GRPC_WEB' || '' }} - name: Unit Test run: dotnet test -f ${{ matrix.target-framework }} tests/Unit/Momento.Sdk.Tests diff --git a/README.md b/README.md index 66b76693..cc595da0 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ using (ICacheClient client = new CacheClient(Configurations.Laptop.V1(), authPro ``` -Note that the above code requires an environment variable named MOMENTO_AUTH_TOKEN which must -be set to a valid [Momento authentication token](https://docs.momentohq.com/docs/getting-started#obtain-an-auth-token). +Note that the above code requires an environment variable named MOMENTO_API_KEY which must +be set to a valid [Momento authentication token](https://docs.momentohq.com/cache/develop/authentication/api-keys). ## Getting Started and Documentation diff --git a/README.template.md b/README.template.md index a28eb5c5..1364d3ec 100644 --- a/README.template.md +++ b/README.template.md @@ -14,8 +14,8 @@ Here is a quickstart you can use in your own project: {% include "./examples/MomentoUsage/Program.cs" %} ``` -Note that the above code requires an environment variable named MOMENTO_AUTH_TOKEN which must -be set to a valid [Momento authentication token](https://docs.momentohq.com/docs/getting-started#obtain-an-auth-token). +Note that the above code requires an environment variable named MOMENTO_API_KEY which must +be set to a valid [Momento authentication token](https://docs.momentohq.com/cache/develop/authentication/api-keys). ## Getting Started and Documentation diff --git a/examples/DictionaryExample/README.md b/examples/DictionaryExample/README.md index ccab3a09..3fc72a71 100644 --- a/examples/DictionaryExample/README.md +++ b/examples/DictionaryExample/README.md @@ -4,12 +4,12 @@ This example program demonstrates usage of the dictionary data type. # Usage -The program assumes the auth token and cache names are available in environment variables. The auth token is assumed to be in the variable `TEST_AUTH_TOKEN` and the cache name in `TEST_CACHE_NAME`. If either of these is missing, you will be prompted to enter the values on the terminal. +The program assumes the auth token and cache names are available in environment variables. The auth token is assumed to be in the variable `MOMENTO_API_KEY` and the cache name in `MOMENTO_CACHE_NAME`. If either of these is missing, you will be prompted to enter the values on the terminal. To run the program, run either: ```bash -TEST_AUTH_TOKEN= TEST_CACHE_NAME= dotnet run +MOMENTO_API_KEY= MOMENTO_CACHE_NAME= dotnet run ``` or diff --git a/examples/DisposableTokens/README.md b/examples/DisposableTokens/README.md index 47620f0c..0358d26b 100644 --- a/examples/DisposableTokens/README.md +++ b/examples/DisposableTokens/README.md @@ -9,7 +9,7 @@ This example program demonstrates how to generate disposable Momento auth tokens The program assumes that your Momento auth token is available in the `MOMENTO_API_KEY` environment variable: ```bash -MOMENTO_API_KEY= dotnet run +MOMENTO_API_KEY= dotnet run ``` The example generates a disposable expiring auth token using the enumerated permissions and expiry defined in the program and prints its attributes to the console. diff --git a/examples/MomentoApplication/README.md b/examples/MomentoApplication/README.md index 8d2428d7..9116d6c3 100644 --- a/examples/MomentoApplication/README.md +++ b/examples/MomentoApplication/README.md @@ -13,20 +13,20 @@ functionality, including: ## Prerequisites * [`dotnet`](https://dotnet.microsoft.com/en-us/download) 6.0 or higher is required -* A Momento auth token is required. You can generate one using the [Momento CLI](https://github.com/momentohq/momento-cli). +* A Momento API key is required. You can generate one using the [Momento Console](https://console.gomomento.com/api-keys). ## Running the application example Run the following from within the `examples` directory: ```bash -MOMENTO_API_KEY= dotnet run --project MomentoApplication +MOMENTO_API_KEY= dotnet run --project MomentoApplication ``` Within the `MomentoAppication` directory you can run: ```bash -MOMENTO_API_KEY= dotnet run +MOMENTO_API_KEY= dotnet run ``` ## Error Handling diff --git a/examples/MomentoLoadGen/README.md b/examples/MomentoLoadGen/README.md index 042a58fc..e181a4d5 100644 --- a/examples/MomentoLoadGen/README.md +++ b/examples/MomentoLoadGen/README.md @@ -23,7 +23,7 @@ If you have questions or need help experimenting further, please reach out to us ## Prerequisites * [`dotnet`](https://dotnet.microsoft.com/en-us/download) 6.0 or higher is required -* A Momento auth token is required. You can generate one using the [Momento CLI](https://github.com/momentohq/momento-cli). +* A Momento API key is required. You can generate one using the [Momento Console](https://console.gomomento.com/api-keys). ## Running the load generator @@ -31,14 +31,14 @@ To run the load generator (from the `examples` directory): ```bash # Run example load generator -MOMENTO_API_KEY= dotnet run --project MomentoLoadGen +MOMENTO_API_KEY= dotnet run --project MomentoLoadGen ``` Within the `MomentoLoadGen` directory you can run: ```bash # Run example load generator -MOMENTO_API_KEY= dotnet run +MOMENTO_API_KEY= dotnet run ``` If you make modifications to the code, remember to do a clean otherwise @@ -46,5 +46,5 @@ the program might not run. ```bash dotnet clean -MOMENTO_API_KEY= dotnet run +MOMENTO_API_KEY= dotnet run ``` diff --git a/examples/MomentoUsage/README.md b/examples/MomentoUsage/README.md index 0ac84cfb..b1f64e6e 100644 --- a/examples/MomentoUsage/README.md +++ b/examples/MomentoUsage/README.md @@ -9,20 +9,20 @@ the client's full capabilities, take a look at the more advanced [MomentoApplic ## Prerequisites * [`dotnet`](https://dotnet.microsoft.com/en-us/download) 6.0 or higher is required -* A Momento auth token is required. You can generate one using the [Momento CLI](https://github.com/momentohq/momento-cli). +* A Momento API key is required. You can generate one using the [Momento Console](https://console.gomomento.com/api-keys). ## Running the application example Run the following from within the `examples` directory: ```bash -MOMENTO_API_KEY= dotnet run --project MomentoUsage +MOMENTO_API_KEY= dotnet run --project MomentoUsage ``` Within the `MomentoUsage` directory you can run: ```bash -MOMENTO_API_KEY= dotnet run +MOMENTO_API_KEY= dotnet run ``` ## Error Handling diff --git a/examples/README.md b/examples/README.md index 5263168b..671391a9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,7 +5,7 @@ ## Prerequisites * [`dotnet`](https://dotnet.microsoft.com/en-us/download) 6.0 or higher is required -* A Momento auth token is required. You can generate one using the [Momento CLI](https://github.com/momentohq/momento-cli). +* A Momento API key is required. You can generate one using the [Momento Console](https://console.gomomento.com/api-keys). ## Running the advanced example @@ -13,7 +13,7 @@ To run the advanced example code defined in [`MomentoApplication/Program.cs`](./ run the following from within the `examples` directory: ```bash -MOMENTO_AUTH_TOKEN= dotnet run --project MomentoApplication +MOMENTO_API_KEY= dotnet run --project MomentoApplication ``` ## Error Handling @@ -82,5 +82,5 @@ To run the load generator (from the `examples` directory): ```bash # Run example load generator -MOMENTO_AUTH_TOKEN= dotnet run --project MomentoLoadGen +MOMENTO_API_KEY= dotnet run --project MomentoLoadGen ``` diff --git a/examples/TopicExample/README.md b/examples/TopicExample/README.md index d6970d51..9f22f337 100644 --- a/examples/TopicExample/README.md +++ b/examples/TopicExample/README.md @@ -4,12 +4,12 @@ This example program demonstrates usage of Momento Topics. # Usage -The program assumes the auth token and cache names are available in environment variables. The auth token is assumed to be in the variable `TEST_AUTH_TOKEN` and the cache name in `TEST_CACHE_NAME`. If either of these is missing, you will be prompted to enter the values on the terminal. +The program assumes the auth token and cache names are available in environment variables. The auth token is assumed to be in the variable `MOMENTO_API_KEY` and the cache name in `MOMENTO_CACHE_NAME`. If either of these is missing, you will be prompted to enter the values on the terminal. To run the program, run either: ```bash -TEST_AUTH_TOKEN= TEST_CACHE_NAME= dotnet run +MOMENTO_API_KEY= MOMENTO_CACHE_NAME= dotnet run ``` or diff --git a/src/Momento.Sdk/Config/Configuration.cs b/src/Momento.Sdk/Config/Configuration.cs index 9f8973ce..407e0a37 100644 --- a/src/Momento.Sdk/Config/Configuration.cs +++ b/src/Momento.Sdk/Config/Configuration.cs @@ -61,12 +61,6 @@ public IConfiguration WithTransportStrategy(ITransportStrategy transportStrategy return new Configuration(LoggerFactory, RetryStrategy, Middlewares, transportStrategy); } - /// - public IConfiguration WithSocketsHttpHandlerOptions(SocketsHttpHandlerOptions options) - { - return new Configuration(LoggerFactory, RetryStrategy, Middlewares, TransportStrategy.WithSocketsHttpHandlerOptions(options)); - } - /// /// Add the specified middlewares to an existing instance of Configuration object in addition to already specified middlewares. /// diff --git a/src/Momento.Sdk/Config/Configurations.cs b/src/Momento.Sdk/Config/Configurations.cs index b426ce91..276de7a7 100644 --- a/src/Momento.Sdk/Config/Configurations.cs +++ b/src/Momento.Sdk/Config/Configurations.cs @@ -168,6 +168,11 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null) /// This config optimizes for lambda environments. In addition to the in region settings of /// , this configures the clients to eagerly connect to the Momento service /// to avoid the cold start penalty of establishing a connection on the first request. + /// 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 class Lambda : Configuration { @@ -184,8 +189,17 @@ private Lambda(ILoggerFactory loggerFactory, IRetryStrategy retryStrategy, ITran /// public static IConfiguration V1(ILoggerFactory? loggerFactory = null) { - return Default.V1(loggerFactory).WithSocketsHttpHandlerOptions( - SocketsHttpHandlerOptions.Of(pooledConnectionIdleTimeout: TimeSpan.FromMinutes(6))); + var config = Default.V1(loggerFactory); + var transportStrategy = config.TransportStrategy.WithSocketsHttpHandlerOptions( + 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/IConfiguration.cs b/src/Momento.Sdk/Config/IConfiguration.cs index 20f306d7..e1b7998e 100644 --- a/src/Momento.Sdk/Config/IConfiguration.cs +++ b/src/Momento.Sdk/Config/IConfiguration.cs @@ -43,13 +43,6 @@ public interface IConfiguration /// Configuration object with custom transport strategy provided public IConfiguration WithTransportStrategy(ITransportStrategy transportStrategy); - /// - /// Creates a new instance of the Configuration object, updated to use the specified SocketHttpHandler options. - /// - /// Customizations to the SocketsHttpHandler - /// - public IConfiguration WithSocketsHttpHandlerOptions(SocketsHttpHandlerOptions options); - /// /// Creates a new instance of the Configuration object, updated to use the specified client timeout. /// diff --git a/src/Momento.Sdk/Config/TopicConfigurations.cs b/src/Momento.Sdk/Config/TopicConfigurations.cs index bb33eaac..5c9822a0 100644 --- a/src/Momento.Sdk/Config/TopicConfigurations.cs +++ b/src/Momento.Sdk/Config/TopicConfigurations.cs @@ -102,7 +102,8 @@ 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))); + grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(1100)) + ); return new Default(finalLoggerFactory, transportStrategy); } } diff --git a/src/Momento.Sdk/Config/Transport/IVectorIndexTransportStrategy.cs b/src/Momento.Sdk/Config/Transport/IVectorIndexTransportStrategy.cs index 78935d4e..ebaa158a 100644 --- a/src/Momento.Sdk/Config/Transport/IVectorIndexTransportStrategy.cs +++ b/src/Momento.Sdk/Config/Transport/IVectorIndexTransportStrategy.cs @@ -26,4 +26,11 @@ public interface IVectorIndexTransportStrategy /// /// A new IVectorIndexTransportStrategy with the specified client timeout public IVectorIndexTransportStrategy WithClientTimeout(TimeSpan clientTimeout); + + /// + /// Copy constructor to update the SocketsHttpHandler's options + /// + /// + /// + public IVectorIndexTransportStrategy WithSocketsHttpHandlerOptions(SocketsHttpHandlerOptions options); } 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 97d90dc0..180f985b 100644 --- a/src/Momento.Sdk/Config/Transport/StaticTransportStrategy.cs +++ b/src/Momento.Sdk/Config/Transport/StaticTransportStrategy.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using Grpc.Net.Client; using Microsoft.Extensions.Logging; using Momento.Sdk.Internal; @@ -19,6 +20,12 @@ public class StaticGrpcConfiguration : IGrpcConfiguration public GrpcChannelOptions GrpcChannelOptions { get; } /// public SocketsHttpHandlerOptions SocketsHttpHandlerOptions { get; } + /// + public TimeSpan KeepAlivePingTimeout { get; } + /// + public TimeSpan KeepAlivePingDelay { get; } + /// + public bool KeepAlivePermitWithoutCalls { get; } /// /// @@ -27,15 +34,33 @@ public class StaticGrpcConfiguration : IGrpcConfiguration /// Customizations to low-level gRPC channel configuration /// minimum number of gRPC channels to open /// Customizations to the SocketsHttpHandler - public StaticGrpcConfiguration(TimeSpan deadline, GrpcChannelOptions? grpcChannelOptions = null, int minNumGrpcChannels = 1, SocketsHttpHandlerOptions? socketsHttpHandlerOptions = null) + public StaticGrpcConfiguration( + TimeSpan deadline, + GrpcChannelOptions? grpcChannelOptions = null, + int minNumGrpcChannels = 1, + SocketsHttpHandlerOptions? socketsHttpHandlerOptions = null + ) { Utils.ArgumentStrictlyPositive(deadline, nameof(deadline)); this.Deadline = deadline; this.MinNumGrpcChannels = minNumGrpcChannels; - this.GrpcChannelOptions = grpcChannelOptions ?? new GrpcChannelOptions(); + this.GrpcChannelOptions = grpcChannelOptions ?? DefaultGrpcChannelOptions(); this.SocketsHttpHandlerOptions = socketsHttpHandlerOptions ?? new SocketsHttpHandlerOptions(); } + /// + /// The grpc default value for max_send_message_length is 4mb. This function returns default grpc options that increase max message size to 5mb in order to support cases where users have requested a limit increase up to our maximum item size of 5mb. + /// + /// GrpcChannelOptions + public static GrpcChannelOptions DefaultGrpcChannelOptions() { + const int DEFAULT_MAX_MESSAGE_SIZE = 5_243_000; + return new GrpcChannelOptions + { + MaxReceiveMessageSize = DEFAULT_MAX_MESSAGE_SIZE, + MaxSendMessageSize = DEFAULT_MAX_MESSAGE_SIZE + }; + } + /// public IGrpcConfiguration WithDeadline(TimeSpan deadline) { diff --git a/src/Momento.Sdk/Config/Transport/StaticVectorIndexTransportStrategy.cs b/src/Momento.Sdk/Config/Transport/StaticVectorIndexTransportStrategy.cs index 90617168..46a708b3 100644 --- a/src/Momento.Sdk/Config/Transport/StaticVectorIndexTransportStrategy.cs +++ b/src/Momento.Sdk/Config/Transport/StaticVectorIndexTransportStrategy.cs @@ -37,6 +37,11 @@ public IVectorIndexTransportStrategy WithClientTimeout(TimeSpan clientTimeout) return new StaticVectorIndexTransportStrategy(_loggerFactory, GrpcConfig.WithDeadline(clientTimeout)); } + public IVectorIndexTransportStrategy WithSocketsHttpHandlerOptions(SocketsHttpHandlerOptions options) + { + return new StaticVectorIndexTransportStrategy(_loggerFactory, GrpcConfig.WithSocketsHttpHandlerOptions(options)); + } + /// /// Test equality by value. /// diff --git a/src/Momento.Sdk/Internal/AuthGrpcManager.cs b/src/Momento.Sdk/Internal/AuthGrpcManager.cs index 623a2983..0d578f17 100644 --- a/src/Momento.Sdk/Internal/AuthGrpcManager.cs +++ b/src/Momento.Sdk/Internal/AuthGrpcManager.cs @@ -45,65 +45,17 @@ public async Task<_GenerateDisposableTokenResponse> generateDisposableToken(_Gen } -public class AuthGrpcManager : IDisposable +public class AuthGrpcManager : GrpcManager { - private readonly GrpcChannel channel; public IAuthClient Client { get; } -#if USE_GRPC_WEB - private readonly static string moniker = "dotnet-web"; -#else - private readonly static string moniker = "dotnet"; -#endif - private readonly string version = $"{moniker}:{GetAssembly(typeof(Momento.Sdk.Responses.CacheGetResponse)).GetName().Version.ToString()}"; - // Some System.Environment.Version remarks to be aware of - // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks - private readonly string runtimeVersion = $"{moniker}:{System.Environment.Version}"; - - public AuthGrpcManager(IAuthConfiguration config, string authToken, string endpoint) + public AuthGrpcManager(IAuthConfiguration config, string authToken, string endpoint): base(config.TransportStrategy.GrpcConfig, config.LoggerFactory, authToken, endpoint, "AuthGrpcManager") { -#if USE_GRPC_WEB - // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server - endpoint = $"web.{endpoint}"; -#endif - var uri = $"https://{endpoint}"; - var channelOptions = config.TransportStrategy.GrpcConfig.GrpcChannelOptions; - if (channelOptions.LoggerFactory == null) - { - channelOptions.LoggerFactory = config.LoggerFactory; - } - - channelOptions.Credentials = ChannelCredentials.SecureSsl; - channelOptions.MaxReceiveMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - channelOptions.MaxSendMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - -#if USE_GRPC_WEB - channelOptions.HttpHandler = new GrpcWebHandler(new HttpClientHandler()); -#endif - - channel = GrpcChannel.ForAddress(uri, channelOptions); - - var headerTuples = new List> - { - new(Header.AuthorizationKey, authToken), - new(Header.AgentKey, version), - new(Header.RuntimeVersionKey, runtimeVersion) - }; - var headers = headerTuples.Select(tuple => new Header(name: tuple.Item1, value: tuple.Item2)).ToList(); - - CallInvoker invoker = this.channel.CreateCallInvoker(); - var middlewares = new List { - new HeaderMiddleware(config.LoggerFactory, headers) + new HeaderMiddleware(config.LoggerFactory, this.headers) }; - var client = new Token.TokenClient(invoker); - Client = new AuthClientWithMiddleware(client, middlewares, headerTuples); - } - - public void Dispose() - { - this.channel.Dispose(); - GC.SuppressFinalize(this); + var client = new Token.TokenClient(this.invoker); + Client = new AuthClientWithMiddleware(client, middlewares, this.headerTuples); } } diff --git a/src/Momento.Sdk/Internal/ControlGrpcManager.cs b/src/Momento.Sdk/Internal/ControlGrpcManager.cs index e357ba39..a0f1a43b 100644 --- a/src/Momento.Sdk/Internal/ControlGrpcManager.cs +++ b/src/Momento.Sdk/Internal/ControlGrpcManager.cs @@ -71,58 +71,18 @@ public async Task<_ListCachesResponse> ListCachesAsync(_ListCachesRequest reques } } -internal sealed class ControlGrpcManager : IDisposable +internal sealed class ControlGrpcManager : GrpcManager { - private readonly GrpcChannel channel; public IControlClient Client { get; } -#if USE_GRPC_WEB - private readonly static string moniker = "dotnet-web"; -#else - private readonly static string moniker = "dotnet"; -#endif - private readonly string version = $"{moniker}:{GetAssembly(typeof(Momento.Sdk.Responses.CacheGetResponse)).GetName().Version.ToString()}"; - // Some System.Environment.Version remarks to be aware of - // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks - private readonly string runtimeVersion = $"{moniker}:{System.Environment.Version}"; - private readonly ILogger _logger; - - public ControlGrpcManager(IConfiguration config, string authToken, string endpoint) + public ControlGrpcManager(IConfiguration config, string authToken, string endpoint): base(config.TransportStrategy.GrpcConfig, config.LoggerFactory, authToken, endpoint, "ControlGrpcManager") { - this._logger = config.LoggerFactory.CreateLogger(); -#if USE_GRPC_WEB - // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server - endpoint = $"web.{endpoint}"; -#endif - var uri = $"https://{endpoint}"; - this.channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions() + var middlewares = new List { - Credentials = ChannelCredentials.SecureSsl, - MaxReceiveMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE, - MaxSendMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE, -#if NET5_0_OR_GREATER - HttpHandler = new System.Net.Http.SocketsHttpHandler - { - EnableMultipleHttp2Connections = config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.EnableMultipleHttp2Connections, - PooledConnectionIdleTimeout = config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.PooledConnectionIdleTimeout - } -#elif USE_GRPC_WEB - HttpHandler = new GrpcWebHandler(new HttpClientHandler()) -#endif - }); - List
headers = new List
{ new Header(name: Header.AuthorizationKey, value: authToken), new Header(name: Header.AgentKey, value: version), new Header(name: Header.RuntimeVersionKey, value: runtimeVersion) }; - CallInvoker invoker = this.channel.CreateCallInvoker(); - - var middlewares = new List { - new HeaderMiddleware(config.LoggerFactory, headers) + new HeaderMiddleware(config.LoggerFactory, this.headers) }; - Client = new ControlClientWithMiddleware(new ScsControl.ScsControlClient(invoker), middlewares); - } - - public void Dispose() - { - this.channel.Dispose(); - GC.SuppressFinalize(this); + var client = new ScsControl.ScsControlClient(this.invoker); + Client = new ControlClientWithMiddleware(client, middlewares); } } diff --git a/src/Momento.Sdk/Internal/DataGrpcManager.cs b/src/Momento.Sdk/Internal/DataGrpcManager.cs index 09960dc0..eafc610a 100644 --- a/src/Momento.Sdk/Internal/DataGrpcManager.cs +++ b/src/Momento.Sdk/Internal/DataGrpcManager.cs @@ -250,64 +250,21 @@ public async Task<_ListConcatenateBackResponse> ListConcatenateBackAsync(_ListCo } } -public class DataGrpcManager : IDisposable +public class DataGrpcManager : GrpcManager { - private readonly GrpcChannel channel; - public readonly IDataClient Client; -#if USE_GRPC_WEB - private readonly static string moniker = "dotnet-web"; -#else - private readonly static string moniker = "dotnet"; -#endif - private readonly string version = $"{moniker}:{GetAssembly(typeof(Responses.CacheGetResponse)).GetName().Version.ToString()}"; - // Some System.Environment.Version remarks to be aware of - // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks - private readonly string runtimeVersion = $"{moniker}:{Environment.Version}"; - private readonly ILogger _logger; - - internal DataGrpcManager(IConfiguration config, string authToken, string endpoint) + internal DataGrpcManager(IConfiguration config, string authToken, string endpoint): base(config.TransportStrategy.GrpcConfig, config.LoggerFactory, authToken, endpoint, "DataGrpcManager") { - this._logger = config.LoggerFactory.CreateLogger(); -#if USE_GRPC_WEB - // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server - endpoint = $"web.{endpoint}"; -#endif - var uri = $"https://{endpoint}"; - var channelOptions = config.TransportStrategy.GrpcConfig.GrpcChannelOptions; - if (channelOptions.LoggerFactory == null) - { - channelOptions.LoggerFactory = config.LoggerFactory; - } - channelOptions.Credentials = ChannelCredentials.SecureSsl; - channelOptions.MaxReceiveMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - channelOptions.MaxSendMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - -#if NET5_0_OR_GREATER - channelOptions.HttpHandler = new SocketsHttpHandler - { - EnableMultipleHttp2Connections = config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.EnableMultipleHttp2Connections, - PooledConnectionIdleTimeout = config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.PooledConnectionIdleTimeout - }; -#elif USE_GRPC_WEB - channelOptions.HttpHandler = new GrpcWebHandler(new HttpClientHandler()); -#endif - - this.channel = GrpcChannel.ForAddress(uri, channelOptions); - List
headers = new List
{ new Header(name: Header.AuthorizationKey, value: authToken), new Header(name: Header.AgentKey, value: version), new Header(name: Header.RuntimeVersionKey, value: runtimeVersion) }; - - CallInvoker invoker = this.channel.CreateCallInvoker(); - var middlewares = config.Middlewares.Concat( new List { new RetryMiddleware(config.LoggerFactory, config.RetryStrategy), - new HeaderMiddleware(config.LoggerFactory, headers), + new HeaderMiddleware(config.LoggerFactory, this.headers), new MaxConcurrentRequestsMiddleware(config.LoggerFactory, config.TransportStrategy.MaxConcurrentRequests) } ).ToList(); - var client = new Scs.ScsClient(invoker); + var client = new Scs.ScsClient(this.invoker); Client = new DataClientWithMiddleware(client, middlewares); } @@ -328,10 +285,4 @@ await pingClient.PingAsync(new _PingRequest(), throw new ConnectionException("Eager connection to server failed", transportDetails, ex); } } - - public void Dispose() - { - this.channel.Dispose(); - GC.SuppressFinalize(this); - } } diff --git a/src/Momento.Sdk/Internal/GrpcManager.cs b/src/Momento.Sdk/Internal/GrpcManager.cs new file mode 100644 index 00000000..f129dd27 --- /dev/null +++ b/src/Momento.Sdk/Internal/GrpcManager.cs @@ -0,0 +1,99 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Linq; +using System.Net.Http; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Momento.Sdk.Internal.Middleware; +using Momento.Sdk.Config.Transport; +using Grpc.Core; +using Grpc.Net.Client; +#if USE_GRPC_WEB +using Grpc.Net.Client.Web; +#endif +using static System.Reflection.Assembly; + +namespace Momento.Sdk.Internal; + +/// +/// Base class for the various GrpcManager classes. +/// +public class GrpcManager : IDisposable +{ + protected GrpcChannel channel; + + protected ILogger _logger; + +#if USE_GRPC_WEB + protected readonly static string moniker = "dotnet-web"; +#else + protected readonly static string moniker = "dotnet"; +#endif + protected readonly string version = $"{moniker}:{GetAssembly(typeof(Momento.Sdk.Responses.CacheGetResponse)).GetName().Version.ToString()}"; + // Some System.Environment.Version remarks to be aware of + // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks + + protected readonly string runtimeVersion = $"{moniker}:{System.Environment.Version}"; + + internal List
headers; + + internal List> headerTuples; + + protected CallInvoker invoker; + + /// + /// Constructor for GrpcManager, establishes the GRPC connection and creates the logger and invoker. + /// + /// + /// + /// + /// + /// + internal GrpcManager(IGrpcConfiguration grpcConfig, ILoggerFactory loggerFactory, string authToken, string endpoint, string loggerName) + { + this._logger = loggerFactory.CreateLogger(loggerName); + + this.headerTuples = new List> + { + new(Header.AuthorizationKey, authToken), + new(Header.AgentKey, version), + new(Header.RuntimeVersionKey, runtimeVersion) + }; + this.headers = headerTuples.Select(tuple => new Header(name: tuple.Item1, value: tuple.Item2)).ToList(); + + // Set all channel opens and create the grpc connection + var channelOptions = grpcConfig.GrpcChannelOptions; + channelOptions.Credentials = ChannelCredentials.SecureSsl; + channelOptions.LoggerFactory ??= loggerFactory; +#if NET5_0_OR_GREATER + 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.SocketsHttpHandlerOptions.KeepAlivePingTimeout, + KeepAlivePingDelay = grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingDelay, + KeepAlivePingPolicy = grpcConfig.SocketsHttpHandlerOptions.KeepAlivePermitWithoutCalls ? System.Net.Http.HttpKeepAlivePingPolicy.Always : System.Net.Http.HttpKeepAlivePingPolicy.WithActiveRequests, + }; + } +#elif USE_GRPC_WEB + channelOptions.HttpHandler = new GrpcWebHandler(new HttpClientHandler()); + // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server + endpoint = $"web.{endpoint}"; +#endif + var uri = $"https://{endpoint}"; + this.channel = GrpcChannel.ForAddress(uri, channelOptions); + this.invoker = this.channel.CreateCallInvoker(); + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + this.channel.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/src/Momento.Sdk/Internal/ScsControlClient.cs b/src/Momento.Sdk/Internal/ScsControlClient.cs index 9ec7e952..e348ccc2 100644 --- a/src/Momento.Sdk/Internal/ScsControlClient.cs +++ b/src/Momento.Sdk/Internal/ScsControlClient.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Momento.Protos.ControlClient; using Momento.Sdk.Config; +using Momento.Sdk.Config.Transport; using Momento.Sdk.Exceptions; using Momento.Sdk.Responses; @@ -20,7 +21,17 @@ internal sealed class ScsControlClient : IDisposable public ScsControlClient(IConfiguration config, string authToken, string endpoint) { - this.grpcManager = new ControlGrpcManager(config, authToken, endpoint); + // Override the sockets http handler options to disable keepalive + var overrideKeepalive = SocketsHttpHandlerOptions.Of( + pooledConnectionIdleTimeout: config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.PooledConnectionIdleTimeout, + enableMultipleHttp2Connections: config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.EnableMultipleHttp2Connections, + keepAlivePingTimeout: System.Threading.Timeout.InfiniteTimeSpan, + keepAlivePingDelay: System.Threading.Timeout.InfiniteTimeSpan, + keepAlivePermitWithoutCalls: false + ); + var controlConfig = config.WithTransportStrategy(config.TransportStrategy.WithSocketsHttpHandlerOptions(overrideKeepalive)); + + this.grpcManager = new ControlGrpcManager(controlConfig, authToken, endpoint); this.authToken = authToken; this._logger = config.LoggerFactory.CreateLogger(); this._exceptionMapper = new CacheExceptionMapper(config.LoggerFactory); diff --git a/src/Momento.Sdk/Internal/TopicGrpcManager.cs b/src/Momento.Sdk/Internal/TopicGrpcManager.cs index c58f869c..c3e4f8ad 100644 --- a/src/Momento.Sdk/Internal/TopicGrpcManager.cs +++ b/src/Momento.Sdk/Internal/TopicGrpcManager.cs @@ -65,71 +65,18 @@ public AsyncServerStreamingCall<_SubscriptionItem> subscribe(_SubscriptionReques } } -public class TopicGrpcManager : IDisposable +public class TopicGrpcManager : GrpcManager { - private readonly GrpcChannel channel; - public readonly IPubsubClient Client; -#if USE_GRPC_WEB - private static readonly string Moniker = "dotnet-web"; -#else - private static readonly string Moniker = "dotnet"; -#endif - private readonly string version = - $"{Moniker}:{GetAssembly(typeof(Responses.CacheGetResponse)).GetName().Version.ToString()}"; - - // Some System.Environment.Version remarks to be aware of - // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks - private readonly string runtimeVersion = $"{Moniker}:{Environment.Version}"; - private readonly ILogger _logger; - - internal TopicGrpcManager(ITopicConfiguration config, string authToken, string endpoint) + internal TopicGrpcManager(ITopicConfiguration config, string authToken, string endpoint): base(config.TransportStrategy.GrpcConfig, config.LoggerFactory, authToken, endpoint, "TopicGrpcManager") { -#if USE_GRPC_WEB - // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server - endpoint = $"web.{endpoint}"; -#endif - var uri = $"https://{endpoint}"; - var channelOptions = config.TransportStrategy.GrpcConfig.GrpcChannelOptions; - if (channelOptions.LoggerFactory == null) - { - channelOptions.LoggerFactory = config.LoggerFactory; - } - - channelOptions.Credentials = ChannelCredentials.SecureSsl; - channelOptions.MaxReceiveMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - channelOptions.MaxSendMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - -#if USE_GRPC_WEB - channelOptions.HttpHandler = new GrpcWebHandler(new HttpClientHandler()); -#endif - - channel = GrpcChannel.ForAddress(uri, channelOptions); - var headerTuples = new List> - { - new(Header.AuthorizationKey, authToken), new(Header.AgentKey, version), - new(Header.RuntimeVersionKey, runtimeVersion) - }; - var headers = headerTuples.Select(tuple => new Header(name: tuple.Item1, value: tuple.Item2)).ToList(); - - _logger = config.LoggerFactory.CreateLogger(); - - var invoker = channel.CreateCallInvoker(); - var middlewares = new List { - new HeaderMiddleware(config.LoggerFactory, headers), + new HeaderMiddleware(config.LoggerFactory, this.headers), }; - var client = new Pubsub.PubsubClient(invoker); - - Client = new PubsubClientWithMiddleware(client, middlewares, headerTuples); - } - - public void Dispose() - { - channel.Dispose(); - GC.SuppressFinalize(this); + var client = new Pubsub.PubsubClient(this.invoker); + Client = new PubsubClientWithMiddleware(client, middlewares, this.headerTuples); } } diff --git a/src/Momento.Sdk/Internal/Utils.cs b/src/Momento.Sdk/Internal/Utils.cs index c40ad355..856b6565 100644 --- a/src/Momento.Sdk/Internal/Utils.cs +++ b/src/Momento.Sdk/Internal/Utils.cs @@ -3,7 +3,15 @@ using System.Collections.Generic; using System.Linq; using System.Text; - +using Microsoft.Extensions.Logging; +using Grpc.Core; +using Grpc.Net.Client; +using System.Net.Http; +#if USE_GRPC_WEB +using Grpc.Net.Client.Web; +#endif + +using Momento.Sdk.Config.Transport; using Momento.Sdk.Exceptions; namespace Momento.Sdk.Internal; @@ -13,11 +21,6 @@ namespace Momento.Sdk.Internal; ///
public static class Utils { - /// - /// The default value for max_send_message_length is 4mb. We need to increase this to 5mb in order to support cases where users have requested a limit increase up to our maximum item size of 5mb. - /// - public const int DEFAULT_MAX_MESSAGE_SIZE = 5_243_000; - /// /// Convert a UTF-8 encoded string to a byte array. /// diff --git a/src/Momento.Sdk/Internal/VectorIndexControlClient.cs b/src/Momento.Sdk/Internal/VectorIndexControlClient.cs index 7a6c7a9a..2f77553d 100644 --- a/src/Momento.Sdk/Internal/VectorIndexControlClient.cs +++ b/src/Momento.Sdk/Internal/VectorIndexControlClient.cs @@ -8,6 +8,8 @@ using Momento.Sdk.Exceptions; using Momento.Sdk.Requests.Vector; using Momento.Sdk.Responses.Vector; +using Momento.Sdk.Config; +using Momento.Sdk.Config.Transport; namespace Momento.Sdk.Internal; @@ -19,11 +21,21 @@ internal sealed class VectorIndexControlClient : IDisposable private readonly ILogger _logger; private readonly CacheExceptionMapper _exceptionMapper; - public VectorIndexControlClient(ILoggerFactory loggerFactory, string authToken, string endpoint) + public VectorIndexControlClient(IVectorIndexConfiguration config, string authToken, string endpoint) { - grpcManager = new VectorIndexControlGrpcManager(loggerFactory, authToken, endpoint); - _logger = loggerFactory.CreateLogger(); - _exceptionMapper = new CacheExceptionMapper(loggerFactory); + // Override the sockets http handler options to disable keepalive + var overrideKeepalive = SocketsHttpHandlerOptions.Of( + pooledConnectionIdleTimeout: config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.PooledConnectionIdleTimeout, + enableMultipleHttp2Connections: config.TransportStrategy.GrpcConfig.SocketsHttpHandlerOptions.EnableMultipleHttp2Connections, + keepAlivePingTimeout: System.Threading.Timeout.InfiniteTimeSpan, + keepAlivePingDelay: System.Threading.Timeout.InfiniteTimeSpan, + keepAlivePermitWithoutCalls: false + ); + var controlConfig = config.WithTransportStrategy(config.TransportStrategy.WithSocketsHttpHandlerOptions(overrideKeepalive)); + + grpcManager = new VectorIndexControlGrpcManager(controlConfig, authToken, endpoint); + _logger = config.LoggerFactory.CreateLogger(); + _exceptionMapper = new CacheExceptionMapper(config.LoggerFactory); } public async Task CreateIndexAsync(string indexName, long numDimensions, SimilarityMetric similarityMetric) diff --git a/src/Momento.Sdk/Internal/VectorIndexControlGrpcManager.cs b/src/Momento.Sdk/Internal/VectorIndexControlGrpcManager.cs index c7dbb7ae..ea581cd5 100644 --- a/src/Momento.Sdk/Internal/VectorIndexControlGrpcManager.cs +++ b/src/Momento.Sdk/Internal/VectorIndexControlGrpcManager.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using Momento.Protos.CachePing; using Momento.Protos.ControlClient; +using Momento.Sdk.Config; using Momento.Sdk.Config.Middleware; using Momento.Sdk.Config.Retry; using Momento.Sdk.Internal.Middleware; @@ -64,59 +65,17 @@ public async Task<_DeleteIndexResponse> DeleteIndexAsync(_DeleteIndexRequest req } } -public class VectorIndexControlGrpcManager : IDisposable +public class VectorIndexControlGrpcManager : GrpcManager { - private readonly GrpcChannel channel; - public readonly IVectorIndexControlClient Client; -#if USE_GRPC_WEB - private const string Moniker = "dotnet-web"; -#else - private const string Moniker = "dotnet"; -#endif - private readonly string version = $"{Moniker}:{GetAssembly(typeof(Responses.CacheGetResponse)).GetName().Version.ToString()}"; - // Some System.Environment.Version remarks to be aware of - // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks - private readonly string runtimeVersion = $"{Moniker}:{Environment.Version}"; - private readonly ILogger _logger; - - internal VectorIndexControlGrpcManager(ILoggerFactory loggerFactory, string authToken, string endpoint) + internal VectorIndexControlGrpcManager(IVectorIndexConfiguration config, string authToken, string endpoint): base(config.TransportStrategy.GrpcConfig, config.LoggerFactory, authToken, endpoint, "VectorIndexControlGrpcManager") { -#if USE_GRPC_WEB - // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server - endpoint = $"web.{endpoint}"; -#endif - var uri = $"https://{endpoint}"; - var channelOptions = new GrpcChannelOptions - { - LoggerFactory = loggerFactory, - Credentials = ChannelCredentials.SecureSsl, - MaxReceiveMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE, - MaxSendMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE, - }; -#if USE_GRPC_WEB - channelOptions.HttpHandler = new GrpcWebHandler(new HttpClientHandler()); -#endif - - channel = GrpcChannel.ForAddress(uri, channelOptions); - var headers = new List
{ new(name: Header.AuthorizationKey, value: authToken), new(name: Header.AgentKey, value: version), new(name: Header.RuntimeVersionKey, value: runtimeVersion) }; - - _logger = loggerFactory.CreateLogger(); - - var invoker = channel.CreateCallInvoker(); - var middlewares = new List { - new HeaderMiddleware(loggerFactory, headers) + new HeaderMiddleware(config.LoggerFactory, this.headers) }; - var client = new ScsControl.ScsControlClient(invoker); + var client = new ScsControl.ScsControlClient(this.invoker); Client = new VectorIndexControlClientWithMiddleware(client, middlewares); } - - public void Dispose() - { - channel.Dispose(); - GC.SuppressFinalize(this); - } } diff --git a/src/Momento.Sdk/Internal/VectorIndexDataGrpcManager.cs b/src/Momento.Sdk/Internal/VectorIndexDataGrpcManager.cs index 981ca690..d4627c07 100644 --- a/src/Momento.Sdk/Internal/VectorIndexDataGrpcManager.cs +++ b/src/Momento.Sdk/Internal/VectorIndexDataGrpcManager.cs @@ -91,61 +91,17 @@ public async Task<_DeleteItemBatchResponse> DeleteItemBatchAsync(_DeleteItemBatc } } -public class VectorIndexDataGrpcManager : IDisposable +public class VectorIndexDataGrpcManager : GrpcManager { - private readonly GrpcChannel channel; - public readonly IVectorIndexDataClient Client; -#if USE_GRPC_WEB - private const string Moniker = "dotnet-web"; -#else - private const string Moniker = "dotnet"; -#endif - private readonly string version = $"{Moniker}:{GetAssembly(typeof(Responses.CacheGetResponse)).GetName().Version}"; - // Some System.Environment.Version remarks to be aware of - // https://learn.microsoft.com/en-us/dotnet/api/system.environment.version?view=netstandard-2.0#remarks - private readonly string runtimeVersion = $"{Moniker}:{Environment.Version}"; - private readonly ILogger _logger; - - internal VectorIndexDataGrpcManager(IVectorIndexConfiguration config, string authToken, string endpoint) + internal VectorIndexDataGrpcManager(IVectorIndexConfiguration config, string authToken, string endpoint): base(config.TransportStrategy.GrpcConfig, config.LoggerFactory, authToken, endpoint, "VectorIndexDataGrpcManager") { -#if USE_GRPC_WEB - // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server - endpoint = $"web.{endpoint}"; -#endif - var uri = $"https://{endpoint}"; - var channelOptions = config.TransportStrategy.GrpcConfig.GrpcChannelOptions; - channelOptions.LoggerFactory ??= config.LoggerFactory; - channelOptions.Credentials = ChannelCredentials.SecureSsl; - channelOptions.MaxReceiveMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - channelOptions.MaxSendMessageSize = Internal.Utils.DEFAULT_MAX_MESSAGE_SIZE; - -#if USE_GRPC_WEB - channelOptions.HttpHandler = new GrpcWebHandler(new HttpClientHandler()); -#endif - - channel = GrpcChannel.ForAddress(uri, channelOptions); - var headers = new List
{ new(name: Header.AuthorizationKey, value: authToken), new(name: Header.AgentKey, value: version), new(name: Header.RuntimeVersionKey, value: runtimeVersion) }; - - _logger = config.LoggerFactory.CreateLogger(); - - var invoker = channel.CreateCallInvoker(); - var middlewares = new List { - new HeaderMiddleware(config.LoggerFactory, headers) + new HeaderMiddleware(config.LoggerFactory, this.headers) }; - var client = new VectorIndex.VectorIndexClient(invoker); - - - + var client = new VectorIndex.VectorIndexClient(this.invoker); Client = new VectorIndexDataClientWithMiddleware(client, middlewares); } - - public void Dispose() - { - channel.Dispose(); - GC.SuppressFinalize(this); - } } diff --git a/src/Momento.Sdk/Momento.Sdk.csproj b/src/Momento.Sdk/Momento.Sdk.csproj index 4aea7550..53553cb8 100644 --- a/src/Momento.Sdk/Momento.Sdk.csproj +++ b/src/Momento.Sdk/Momento.Sdk.csproj @@ -30,13 +30,6 @@ https://github.com/momentohq/client-sdk-dotnet - - - USE_GRPC_WEB - - @@ -66,7 +59,7 @@ - + diff --git a/src/Momento.Sdk/PreviewVectorIndexClient.cs b/src/Momento.Sdk/PreviewVectorIndexClient.cs index d0fd462b..62c84129 100644 --- a/src/Momento.Sdk/PreviewVectorIndexClient.cs +++ b/src/Momento.Sdk/PreviewVectorIndexClient.cs @@ -32,7 +32,7 @@ public PreviewVectorIndexClient(IVectorIndexConfiguration config, ICredentialPro { var loggerFactory = config.LoggerFactory; controlClient = - new VectorIndexControlClient(loggerFactory, authProvider.AuthToken, authProvider.ControlEndpoint); + new VectorIndexControlClient(config, authProvider.AuthToken, authProvider.ControlEndpoint); dataClient = new VectorIndexDataClient(config, authProvider.AuthToken, authProvider.CacheEndpoint); } diff --git a/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs b/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs index 78e49fda..1c783dc9 100644 --- a/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs +++ b/tests/Unit/Momento.Sdk.Tests/ConfigTest.cs @@ -19,4 +19,24 @@ public void V1VConfigs_EqualLatest_HappyPath() Assert.Equal(Configurations.InRegion.Default.Latest(), Configurations.InRegion.Default.V1()); Assert.Equal(Configurations.InRegion.LowLatency.Latest(), Configurations.InRegion.LowLatency.V1()); } + + [Fact] + public void LambdaConfigDisablesKeepAlive() + { + var config = Configurations.InRegion.Lambda.Latest(); + var grpcConfig = config.TransportStrategy.GrpcConfig; + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingTimeout); + Assert.Equal(System.Threading.Timeout.InfiniteTimeSpan, grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingDelay); + Assert.False(grpcConfig.SocketsHttpHandlerOptions.KeepAlivePermitWithoutCalls); + } + + [Fact] + public void LaptopConfigEnablesKeepAlive() + { + var config = Configurations.Laptop.Latest(); + var grpcConfig = config.TransportStrategy.GrpcConfig; + Assert.Equal(TimeSpan.FromMilliseconds(1000), grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingTimeout); + Assert.Equal(TimeSpan.FromMilliseconds(5000), grpcConfig.SocketsHttpHandlerOptions.KeepAlivePingDelay); + Assert.True(grpcConfig.SocketsHttpHandlerOptions.KeepAlivePermitWithoutCalls); + } }