Is ServiceStack.Redis threadsafe?

We are using a components across a BFF API where we inject a Singleton RedisClient, and using cachekey of format: “servicename_compontentname_identifier.” The frontend is a elm based SPA which does concurrent invocations towards the BFF.

We get the following error when we use RedisClient.Get<>(key)

_cacheClient.Get(cacheKey)

Gyldendal.Goldfish.Error.ServiceException: [07:06:31.234] Unable to Connect: sPort: 59325, Error: The ReadAsync method cannot be called when another read operation is pending.
at System.Net.Security.SslStreamInternal.ReadAsyncInternal[TReadAdapter](TReadAdapter adapter, Memory1 buffer) at System.Net.Security.SslStreamInternal.Read(Byte[] buffer, Int32 offset, Int32 count) at System.Net.Security.SslStream.Read(Byte[] buffer, Int32 offset, Int32 count) at System.IO.Stream.ReadByte() at ServiceStack.Redis.RedisNativeClient.ReadLine() in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 245 at ServiceStack.Redis.RedisNativeClient.ReadData() in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 924 at ServiceStack.Redis.RedisNativeClient.SendReceive[T](Byte[][] cmdWithBinaryArgs, Func1 fn, Action1 completePipelineFn, Boolean sendWithoutRead) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 607 ---> ServiceStack.Redis.RedisException: [07:06:31.234] Unable to Connect: sPort: 59325, Error: The ReadAsync method cannot be called when another read operation is pending. at System.Net.Security.SslStreamInternal.ReadAsyncInternal[TReadAdapter](TReadAdapter adapter, Memory1 buffer)
at System.Net.Security.SslStreamInternal.Read(Byte buffer, Int32 offset, Int32 count)
at System.Net.Security.SslStream.Read(Byte buffer, Int32 offset, Int32 count)
at System.IO.Stream.ReadByte()
at ServiceStack.Redis.RedisNativeClient.ReadLine() in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 245
at ServiceStack.Redis.RedisNativeClient.ReadData() in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 924
at ServiceStack.Redis.RedisNativeClient.SendReceive[T](Byte cmdWithBinaryArgs, Func1 fn, Action1 completePipelineFn, Boolean sendWithoutRead) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 607 —> System.NotSupportedException: The ReadAsync method cannot be called when another read operation is pending.
at System.Net.Security.SslStreamInternal.ReadAsyncInternal[TReadAdapter](TReadAdapter adapter, Memory1 buffer) at System.Net.Security.SslStreamInternal.Read(Byte[] buffer, Int32 offset, Int32 count) at System.Net.Security.SslStream.Read(Byte[] buffer, Int32 offset, Int32 count) at System.IO.Stream.ReadByte() at ServiceStack.Redis.RedisNativeClient.ReadLine() in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 245 at ServiceStack.Redis.RedisNativeClient.ReadData() in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 924 at ServiceStack.Redis.RedisNativeClient.SendReceive[T](Byte[][] cmdWithBinaryArgs, Func1 fn, Action1 completePipelineFn, Boolean sendWithoutRead) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 607 --- End of inner exception stack trace --- at ServiceStack.Redis.RedisNativeClient.CreateConnectionError(Exception originalEx) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 366 at ServiceStack.Redis.RedisNativeClient.SendReceive[T](Byte[][] cmdWithBinaryArgs, Func1 fn, Action1 completePipelineFn, Boolean sendWithoutRead) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 634 at ServiceStack.Redis.RedisNativeClient.SendExpectData(Byte[][] cmdWithBinaryArgs) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 709 at ServiceStack.Redis.RedisClient.<>c__DisplayClass131_01.b__0(RedisClient r) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisClient.ICacheClient.cs:line 49
at ServiceStack.Redis.RedisClient.Exec[T](Func`2 action) in C:\BuildAgent\work\b2a0bfe2b1c9a118\src\ServiceStack.Redis\RedisClient.ICacheClient.cs:line 29
at Bff.Logic.Tibet.TibetProxy.Get(TibetPublicationAccessRequest request) in C:\src\contessa\bff\Bff.Logic\Tibet\Proxy\TibetProxy.cs:line 85
at Bff.Logic.Tibet.Access.TibetAccessLogic.Get(TibetPublicationAccessRequest request) in C:\src\contessa\bff\Bff.Logic\Tibet\Access\TibetAccessLogic.cs:line 53
— End of inner exception stack trace —

Should the redis client be injected in request scope, and is this considered best practice?

If we need to create the client for each invocation, it would differ from the memorybased cache client (which we consider that want to fall back onto in case of runtime disabling of the redis based on a feature flag (in case of redis issues))

As per the ServiceStack.Redis docs:

The recommended way to access RedisClient instances is to use one of the available Thread-Safe Client Managers below. Client Managers are connection factories which is ideally registered as a Singleton either in your IOC or static classes.

Accessing the Redis Client

Once registered, accessing the RedisClient is the same in all Client Managers, e.g:

var clientsManager = container.Resolve<IRedisClientsManager>();
using (IRedisClient redis = clientsManager.GetClient())
{
  //...
}

i.e. only the Redis Client Managers are ThreadSafe and should be registered in your IOC as a singleton which all Redis Client instances should be resolved from, don’t register RedisClient instances in the IOC. Resolve Redis Clients from IRedisClientsManager within a using statement so the Redis client connections are returned to the pool immediately after use.

Thank you so much. Next time I will read the docs more thoroughly :no_mouth: