diff --git a/src/Momento.Sdk/CacheClient.cs b/src/Momento.Sdk/CacheClient.cs index 0e0bf4b1..0d718142 100644 --- a/src/Momento.Sdk/CacheClient.cs +++ b/src/Momento.Sdk/CacheClient.cs @@ -35,6 +35,25 @@ private ScsDataClient DataClient protected readonly IConfiguration config; /// protected readonly ILogger _logger; + + /// + /// Async factory function to construct a Momento CacheClient with an eager connection to the + /// Momento server. Calling the CacheClient constructor directly will not establish a connection + /// immediately, but instead establish it lazily when the first request is issued. This factory + /// function will ensure that the connection is established before the first request. + /// + /// Configuration to use for the transport, retries, middlewares. See for out-of-the-box configuration choices, eg + /// Momento auth provider. + /// Default time to live for the item in cache. + /// Maximum time to wait for eager connection. + /// is zero or negative. + /// The eager connection could not be established within the specified + public static async Task CreateAsync(IConfiguration config, ICredentialProvider authProvider, TimeSpan defaultTtl, TimeSpan eagerConnectionTimeout) + { + CacheClient cacheClient = new CacheClient(config, authProvider, defaultTtl); + await cacheClient.DataClient.EagerConnectAsync(eagerConnectionTimeout); + return cacheClient; + } /// diff --git a/src/Momento.Sdk/Config/Configurations.cs b/src/Momento.Sdk/Config/Configurations.cs index 2d7e8a08..dc1f640e 100644 --- a/src/Momento.Sdk/Config/Configurations.cs +++ b/src/Momento.Sdk/Config/Configurations.cs @@ -184,11 +184,7 @@ private Lambda(ILoggerFactory loggerFactory, IRetryStrategy retryStrategy, ITran /// public static IConfiguration Latest(ILoggerFactory? loggerFactory = null) { - var config = Default.V1(loggerFactory); - var transportStrategy = config.TransportStrategy.WithEagerConnectionTimeout( - TimeSpan.FromSeconds(30) - ); - return config.WithTransportStrategy(transportStrategy); + return Default.V1(loggerFactory); } } } diff --git a/src/Momento.Sdk/Config/Transport/ITransportStrategy.cs b/src/Momento.Sdk/Config/Transport/ITransportStrategy.cs index 0d894552..78849ec5 100644 --- a/src/Momento.Sdk/Config/Transport/ITransportStrategy.cs +++ b/src/Momento.Sdk/Config/Transport/ITransportStrategy.cs @@ -14,14 +14,6 @@ public interface ITransportStrategy /// public int MaxConcurrentRequests { get; } - /// - /// If null, the client will only attempt to connect to the server lazily when the first request is executed. - /// If provided, the client will attempt to connect to the server immediately upon construction; if the connection - /// cannot be established within the specified TimeSpan, it will abort the connection attempt, log a warning, - /// and proceed with execution so that the application doesn't hang. - /// - public TimeSpan? EagerConnectionTimeout { get; } - /// /// Configures the low-level gRPC settings for the Momento client's communication /// with the Momento server. @@ -50,15 +42,4 @@ public interface ITransportStrategy /// /// A new ITransportStrategy with the specified client timeout public ITransportStrategy WithClientTimeout(TimeSpan clientTimeout); - - /// - /// Copy constructor to enable eager connection to the server - /// - /// A timeout for attempting an eager connection to the server. When the client - /// is constructed, it will attempt to connect to the server immediately. If the connection - /// cannot be established within the specified TimeSpan, it will abort the connection attempt, log a warning, - /// and proceed with execution so that the application doesn't hang. - /// - /// A new ITransportStrategy configured to eagerly connect to the server upon construction - public ITransportStrategy WithEagerConnectionTimeout(TimeSpan connectionTimeout); } diff --git a/src/Momento.Sdk/Exceptions/ConnectionException.cs b/src/Momento.Sdk/Exceptions/ConnectionException.cs new file mode 100644 index 00000000..87ced02e --- /dev/null +++ b/src/Momento.Sdk/Exceptions/ConnectionException.cs @@ -0,0 +1,15 @@ +namespace Momento.Sdk.Exceptions; + +using System; + +/// +/// Unable to connect to the server. +/// +public class ConnectionException : SdkException +{ + /// + public ConnectionException(string message, MomentoErrorTransportDetails transportDetails, Exception? e = null) : base(MomentoErrorCode.CONNECTION_ERROR, message, transportDetails, e) + { + this.MessageWrapper = "Unable to connect to the server; consider retrying. If the error persists, please contact us at support@momentohq.com"; + } +} diff --git a/src/Momento.Sdk/Exceptions/SdkException.cs b/src/Momento.Sdk/Exceptions/SdkException.cs index cf3c5ab6..2822fca8 100644 --- a/src/Momento.Sdk/Exceptions/SdkException.cs +++ b/src/Momento.Sdk/Exceptions/SdkException.cs @@ -66,6 +66,10 @@ public enum MomentoErrorCode /// FAILED_PRECONDITION_ERROR, /// + /// Unable to connect to the server + /// + CONNECTION_ERROR, + /// /// Unknown error has occurred /// UNKNOWN_ERROR diff --git a/src/Momento.Sdk/Internal/DataGrpcManager.cs b/src/Momento.Sdk/Internal/DataGrpcManager.cs index b1210b1f..06d3eeff 100644 --- a/src/Momento.Sdk/Internal/DataGrpcManager.cs +++ b/src/Momento.Sdk/Internal/DataGrpcManager.cs @@ -15,6 +15,7 @@ using Momento.Sdk.Config; using Momento.Sdk.Config.Middleware; using Momento.Sdk.Config.Retry; +using Momento.Sdk.Exceptions; using Momento.Sdk.Internal.Middleware; using static System.Reflection.Assembly; @@ -299,24 +300,25 @@ internal DataGrpcManager(IConfiguration config, string authToken, string endpoin ).ToList(); var client = new Scs.ScsClient(invoker); - - if (config.TransportStrategy.EagerConnectionTimeout != null) + Client = new DataClientWithMiddleware(client, middlewares); + } + + internal async Task EagerConnectAsync(TimeSpan eagerConnectionTimeout) + { + _logger.LogDebug("Attempting eager connection to server"); + var pingClient = new Ping.PingClient(this.channel); + try { - TimeSpan eagerConnectionTimeout = config.TransportStrategy.EagerConnectionTimeout.Value; - _logger.LogDebug("TransportStrategy EagerConnection is enabled; attempting to connect to server"); - var pingClient = new Ping.PingClient(this.channel); - try - { - pingClient.Ping(new _PingRequest(), - new CallOptions(deadline: DateTime.UtcNow.Add(eagerConnectionTimeout))); - } - catch (RpcException ex) - { - _logger.LogWarning($"Failed to eagerly connect to the server; continuing with execution in case failure is recoverable later: {ex}"); - } + await pingClient.PingAsync(new _PingRequest(), + new CallOptions(deadline: DateTime.UtcNow.Add(eagerConnectionTimeout))); + } + catch (RpcException ex) + { + MomentoErrorTransportDetails transportDetails = new MomentoErrorTransportDetails( + new MomentoGrpcErrorDetails(ex.StatusCode, ex.Message, null) + ); + throw new ConnectionException("Eager connection to server failed", transportDetails, ex); } - - Client = new DataClientWithMiddleware(client, middlewares); } public void Dispose() diff --git a/src/Momento.Sdk/Internal/ScsDataClient.cs b/src/Momento.Sdk/Internal/ScsDataClient.cs index 3750f1ab..024b1271 100644 --- a/src/Momento.Sdk/Internal/ScsDataClient.cs +++ b/src/Momento.Sdk/Internal/ScsDataClient.cs @@ -33,6 +33,11 @@ public ScsDataClientBase(IConfiguration config, string authToken, string endpoin this._logger = config.LoggerFactory.CreateLogger(); this._exceptionMapper = new CacheExceptionMapper(config.LoggerFactory); } + + internal Task EagerConnectAsync(TimeSpan eagerConnectionTimeout) + { + return this.grpcManager.EagerConnectAsync(eagerConnectionTimeout); + } protected Metadata MetadataWithCache(string cacheName) {