2

I am getting the following error intermittently when trying to read a redis list via ServiceStack.Redis: "Unable to read data from the transport connection: An established connection was aborted by the software in your host machine". I am wondering if my entire concept of how to reliably connect and pool connections to Redis using ServiceStack is wrong. This is my code for connecting using a sealed class and singleton pattern:

public sealed class RedisClientBase
{
    public BasicRedisClientManager Redis;
    private static readonly RedisClientBase instance = new RedisClientBase();

    private RedisClientBase()
    {
        Redis = new BasicRedisClientManager("mypassword@localhost:6379");
    }

    public static RedisClientBase Instance
    {
        get
        {
            return instance;
        }
    }
}

Then I instantiate another class that uses the singleton:

public class RedisBarSetData
{
    private static RedisClient Redis;
    protected IRedisTypedClient<BarSet> redisBarSetClient;
    protected string instrument_key;

    public RedisBarSetData()
    {
        Redis = (RedisClient)RedisClientBase.Instance.Redis.GetClient();
        redisBarSetClient = Redis.As<BarSet>();
    }

    ~RedisBarSetData()
    {
        if (Redis != null)
            Redis.Dispose();
    }

    public List<BarSet> getData(BarSets data)
    {
        setKeys(data);  // instrument_key is set in here
        var redisBarSetClientList = redisBarSetClient.Lists[instrument_key];
        List<BarSet> barSetData;

        barSetData = redisBarSetClientList.GetAll();  // <-- exception here (sometimes)
        return(barSetData);
    }
}

This is in turn instantiated and called from a "Service" DTO callback:

public class JmaSetsService : Service
{
    public object Get(JmaSets request)
    {
            RedisBarSetData barSetData = new RedisBarSetData();
            BarSets barSets = new BarSets(request);
            barSetList = barSetData.getData(barSets);
            return barSetList;
    }
}

I then use "postman" to post to this route. Most clicks on "send" come back with the data. Some end with the exception. The exception is when trying to read from redis as indicated in the code with the comment "<-- exception here". Now one other point is that I recently configured my redis to use a password by setting the config file. I mention that because I don't recall having this problem before, but that could also not be related, don't know.

In terms of freeing of the redis connection, my thinking is that my destructor calls Redis.Dispose when the RedisBarSetData() is out of scope. Is this a solid way to handle it or is there a better way. I've seen people with "using" statements when getting the pooled client, but I then have a lot of "using" statements rather than a call in just one place in the class: "Redis = (RedisClient)RedisClientBase.Instance.Redis.GetClient();" If I have a bunch of methods for the class, then I've got to repeat code in every method?

  • When I say that "I don't recall having this problem before" I am using this pattern for dozens of working DTOs. Not sure why it is failing now?
tradetree
  • 354
  • 3
  • 20

1 Answers1

4

You shouldn't hold any singleton instances of RedisClient or IRedisTypedClient<BarSet> which both encapsulates a non thread-safe Redis TCP Connection. You can instead hold singleton instances of IRedisClientsManager - which is its purpose to provide a thread-safe Redis Client Factory (like a DB Connection Pool).

If you're also using ServiceStack Services, it's easier to register dependencies in ServiceStack's IOC so IRedisClientsManager can be injected like any other dependency, e.g in AppHost.Configure():

container.Register<IRedisClientsManager>(c =>
    new BasicRedisClientManager("mypassword@localhost:6379"));

This will allow you to use the base.Redis RedisClient property in your ServiceStack Services, e.g:

public class JmaSetsService : Service
{
    public object Get(JmaSets request)
    {
        var redisBarSets = base.Redis.As<BarSet>();
        return redisBarSets.Lists[instument_key].GetAll();
    }
}

If you use base.Redis you don't have to explicitly dispose of the RedisClient as it's already automatically disposed by the Service, i.e:

public class Service
{
    ...

    public virtual void Dispose()
    {
        if (redis != null)
            redis.Dispose();
        ...
    }
}

You can also inject IRedisClientsManager into your own classes like any other dependency using a public property or Constructor argument, e.g:

public class RedisBarSetData
{
    public virtual IRedisClientsManager RedisManager { get; set; }

    private IRedisClient redis;
    public virtual IRedisClient Redis
    {
        get { return redis ?? (redis = RedisManager.GetClient()); }
    }

    public override void Dispose()
    {
        if (redis != null)
            redis.Dispose();
    }

    public List<BarSet> getData(BarSets data)
    {
        setKeys(data);  // instrument_key is set in here
        return Redis.As<BarSet>().Lists[instrument_key].GetAll();
    }
}

Which you can then register and autowire in ServiceStack's IOC with:

container.RegisterAutoWired<RedisBarSetData>();

Which will then let you use it as a dependency in your Services:

public class JmaSetsService : Service
{
    public RedisBarSetData RedisBarSetData { get; set; }

    public object Get(JmaSets request)
    {
        return RedisBarSetData.getData(new BarSets(request));
    }
}

An alternative to creating your own base class is to inherit from the pre-existing LogicBase base class, which already has IRedisClientsManager property and above boilerplate.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thanks for the insights mythz! I'll be reviewing and integrating an approach that you mention into the code. It will take hours or longer due to the size of my system that needs to be converted over to a new approach. I'm surprise how far I've gotten with it this way, but I've been doing functional development at slow single-task rates so it did not show up before. I'll respond back with my approach or any other complications when I know more. – tradetree Jan 02 '15 at 15:06
  • I converted over to using the "container.RegisterAutoWired();" registration and Service dependency and that is working great. No more exceptions. However, I have a complex system and I'm still unsure how to access Redis using ServiceStack for my special cases. I need to spin up tasks that are long running. So I use a route / service to start the task, but then it needs to run for days while accessing Redis. If I knew at system startup how many of these tasks I needed, that would be easier, as I could instantiate a BasicRedisClientManager for each one. What are my options? – tradetree Jan 03 '15 at 20:12
  • @tradetree `BasicRedisClientManager` is a connection factory that's meant to be used as a singleton (i.e. you should re-use the same instance), i.e. it doesn't hold any connections open so you can hold onto to it for as long as needed. – mythz Jan 03 '15 at 20:19
  • So if I do: **new BasicRedisClientManager** and then **GetClient** in my service, I can use that client to access Redis for long term and pass it into a sub-thread? What confused me was your saying "it doesn't hold any connections open"? – tradetree Jan 03 '15 at 20:34
  • @tradetree Please read the documentation on the different [Redis Client Managers](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/README.md#redis-client-managers) available. Client Managers are Connection Factories, `RedisManagerPool` and `PooledRedisClientManager` are pooled connection managers whilst `BasicRedisClientManager` returns a new `RedisClient` instance each time so doesn't maintain any open connections itself. – mythz Jan 03 '15 at 22:00
  • I have read that documentation before, but I think it is the terminology that I find confusing. When you say, "doesn't maintain any open connections" what I think you mean (and this may be my limitation on terms) is that connections are not reused? So there is a new connection created with each call. I get that and I think that is what I'm looking for. – tradetree Jan 03 '15 at 22:23
  • @tradetree only "Connection Pools" hold connections open, i.e. when you dispose of a connection it gets released back into the pool. But `BasicRedisClientManager` is not a pooled client manager which means when you resolve a RedisClient a new TCP connection with the redis-server is opened and when its disposed so is the TCP connection is closed, so the `BasicRedisClientManager` doesn't actually hold any connections itself. This is just an impl detail tho and shouldn't change how you use any of the managers which should be treated like singletons. – mythz Jan 03 '15 at 22:30
  • I had this working using LogicBase derived class for RedisBarSetData and now its failing. I have too many changes along the way to know what changed, but here is what I get from the REST client: {"responseStatus":{"errorCode":"Exception","message":"Error trying to resolve Service 'CloudTrader.BarReplayService' or one of its autowired dependencies (see inner exception for details).","stackTrace":" at Funq.Container.ResolveImpl[TService](String name, Boolean ... If I comment out the public RedisBarSetData RedisBarSetData { get; set; } it hits my breakpoint at the start of the service. – tradetree Jan 05 '15 at 05:52
  • @tradetree the error message is saying it's unable to resolve the Service's required deps. Ensure that `RedisBarSetData` is registered in the IOC and has an empty constructor or if you're using constructor injection ensure all constructor argument types are also registered in the IOC. – mythz Jan 05 '15 at 06:18
  • Ok, turns out I added a constructor to RedisBarSetData. I thought if I still had the non-parameter constructor that all would be well. I needed to get rid of the second constructor, not sure why, but it is working again. Thanks for the help. – tradetree Jan 05 '15 at 15:09