Skip to content

Commit

Permalink
Merge pull request #535 from momentohq/make-eager-connection-factory-fn
Browse files Browse the repository at this point in the history
feat: async factory function for eager connections
  • Loading branch information
cprice404 authored Feb 23, 2024
2 parents 2836a65 + 5916f73 commit 2324880
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 60 deletions.
19 changes: 19 additions & 0 deletions src/Momento.Sdk/CacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ private ScsDataClient DataClient
protected readonly IConfiguration config;
/// <inheritdoc cref="Microsoft.Extensions.Logging.ILogger" />
protected readonly ILogger _logger;

/// <summary>
/// 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.
/// </summary>
/// <param name="config">Configuration to use for the transport, retries, middlewares. See <see cref="Configurations"/> for out-of-the-box configuration choices, eg <see cref="Configurations.Laptop.Latest"/></param>
/// <param name="authProvider">Momento auth provider.</param>
/// <param name="defaultTtl">Default time to live for the item in cache.</param>
/// <param name="eagerConnectionTimeout">Maximum time to wait for eager connection.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="defaultTtl"/> is zero or negative.</exception>
/// <exception cref="ConnectionException">The eager connection could not be established within the specified <paramref name="eagerConnectionTimeout"/></exception>
public static async Task<ICacheClient> CreateAsync(IConfiguration config, ICredentialProvider authProvider, TimeSpan defaultTtl, TimeSpan eagerConnectionTimeout)
{
CacheClient cacheClient = new CacheClient(config, authProvider, defaultTtl);
await cacheClient.DataClient.EagerConnectAsync(eagerConnectionTimeout);
return cacheClient;
}


/// <summary>
Expand Down
6 changes: 1 addition & 5 deletions src/Momento.Sdk/Config/Configurations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,7 @@ private Lambda(ILoggerFactory loggerFactory, IRetryStrategy retryStrategy, ITran
/// <returns></returns>
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);
}
}
}
Expand Down
19 changes: 0 additions & 19 deletions src/Momento.Sdk/Config/Transport/ITransportStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ public interface ITransportStrategy
/// </summary>
public int MaxConcurrentRequests { get; }

/// <summary>
/// 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.
/// </summary>
public TimeSpan? EagerConnectionTimeout { get; }

/// <summary>
/// Configures the low-level gRPC settings for the Momento client's communication
/// with the Momento server.
Expand Down Expand Up @@ -50,15 +42,4 @@ public interface ITransportStrategy
/// <param name="clientTimeout"></param>
/// <returns>A new ITransportStrategy with the specified client timeout</returns>
public ITransportStrategy WithClientTimeout(TimeSpan clientTimeout);

/// <summary>
/// Copy constructor to enable eager connection to the server
/// </summary>
/// <param name="connectionTimeout">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.
/// </param>
/// <returns>A new ITransportStrategy configured to eagerly connect to the server upon construction</returns>
public ITransportStrategy WithEagerConnectionTimeout(TimeSpan connectionTimeout);
}
15 changes: 15 additions & 0 deletions src/Momento.Sdk/Exceptions/ConnectionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Momento.Sdk.Exceptions;

using System;

/// <summary>
/// Unable to connect to the server.
/// </summary>
public class ConnectionException : SdkException
{
/// <include file="../docs.xml" path='docs/class[@name="SdkException"]/constructor/*' />
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 [email protected]";
}
}
4 changes: 4 additions & 0 deletions src/Momento.Sdk/Exceptions/SdkException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ public enum MomentoErrorCode
/// </summary>
FAILED_PRECONDITION_ERROR,
/// <summary>
/// Unable to connect to the server
/// </summary>
CONNECTION_ERROR,
/// <summary>
/// Unknown error has occurred
/// </summary>
UNKNOWN_ERROR
Expand Down
34 changes: 18 additions & 16 deletions src/Momento.Sdk/Internal/DataGrpcManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/Momento.Sdk/Internal/ScsDataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public ScsDataClientBase(IConfiguration config, string authToken, string endpoin
this._logger = config.LoggerFactory.CreateLogger<ScsDataClient>();
this._exceptionMapper = new CacheExceptionMapper(config.LoggerFactory);
}

internal Task EagerConnectAsync(TimeSpan eagerConnectionTimeout)
{
return this.grpcManager.EagerConnectAsync(eagerConnectionTimeout);
}

protected Metadata MetadataWithCache(string cacheName)
{
Expand Down
16 changes: 3 additions & 13 deletions tests/Integration/Momento.Sdk.Tests/CacheEagerConnectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,6 @@ public void CacheClientConstructor_LazyConnection()
var client = new CacheClient(config, authProvider, defaultTtl);
}

[Fact]
public void CacheClientConstructor_EagerConnection_Success()
{
var config = Configurations.Laptop.Latest(loggerFactory);
config = config.WithTransportStrategy(config.TransportStrategy.WithEagerConnectionTimeout(TimeSpan.FromSeconds(5)));
// just validating that we can construct the client when the eager connection is successful
var client = new CacheClient(config, authProvider, defaultTtl);
}

[Fact]
public void CacheClientConstructor_WithChannelsAndMaxConn_Success()
{
Expand All @@ -56,13 +47,12 @@ public void CacheClientConstructor_WithChannelsAndMaxConn_Success()
}

[Fact]
public void CacheClientConstructor_EagerConnection_BadEndpoint()
public async void CacheClientCreate_EagerConnection_BadEndpoint()
{
var config = Configurations.Laptop.Latest(loggerFactory);
config = config.WithTransportStrategy(config.TransportStrategy.WithEagerConnectionTimeout(TimeSpan.FromSeconds(2)));
var authProviderWithBadCacheEndpoint = authProvider.WithCacheEndpoint("cache.cell-external-beta-1.prod.a.momentohq.com:65000");
Console.WriteLine($"Hello developer! We are about to run a test that verifies that the cache client is still operational even if our eager connection (ping) fails. So you will see the test log a warning message about that. It's expected, don't worry!");
// validating that the constructor doesn't fail when the eager connection fails
var client = new CacheClient(config, authProviderWithBadCacheEndpoint, defaultTtl);

await Assert.ThrowsAsync<ConnectionException>(async () => await CacheClient.CreateAsync(config, authProviderWithBadCacheEndpoint, defaultTtl, TimeSpan.FromSeconds(2)));
}
}
7 changes: 0 additions & 7 deletions tests/Unit/Momento.Sdk.Tests/ConfigTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,4 @@ 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 LambdaLatest_HasEagerConnectionTimeout_HappyPath()
{
var config = Configurations.InRegion.Lambda.Latest();
Assert.Equal(TimeSpan.FromSeconds(30), config.TransportStrategy.EagerConnectionTimeout);
}
}

0 comments on commit 2324880

Please sign in to comment.