0

My application creates IDisposable objects that should be reused, so I create a factory that encapsulates the creation and reuse of those objects, code like this:

public class ServiceClientFactory
{
    private static readonly object SyncRoot = new object();
    private static readonly Dictionary<string, ServiceClient> Clients = new Dictionary<string, ServiceClient>(StringComparer.OrdinalIgnoreCase);

    public static ServiceClient CreateServiceClient(string host)
    {
        lock(SyncRoot)
        {
            if (Clients.ContainsKey(host) == false)
            {
                Clients[host] = new ServiceClient(host);
            }

            return Clients[host];
        }
    }  
}

public class QueryExecutor
{
    private readonly ServiceClient serviceClient;

    public QueryExecutor(string host)
    {
        this.serviceClient = ServiceClientFactory.CreateServiceClient(host);
    }

    public IDataReader ExecuteQuery(string query)
    {
        this.serviceClient.Query(query, ...);
    }
}

What makes me scratch my head is, ServiceClient is IDisposable, I should dispose them explicitly sometime.

One way is implementing IDisposable in QueryExecutor and dispose ServiceClient when QueryExecutor is disposed, but in this way, (1) when disposing ServiceClient, also needs to notify ServiceClientFactory, (2) cannot reuse ServiceClient instance.

So I think it would be much easier to let ServiceClientFactory manage lifetime of all ServiceClient instances, if I go this way, what is the best practice here to dispose all IDisposable objects created by factory? Hook the AppDomain exit event and manually call Dispose() on every ServiceClient instance?

codewarrior
  • 723
  • 7
  • 22
  • 4
    Odds are whatever you're using to create the connection is *already* using a connection pooling strategy, so there's no reason for you to construct your own connection pool. Constructing your own connection pool properly is a rather large endeavor. – Servy Oct 24 '17 at 18:10
  • 1
    Note that currently your code allows multiple threads to interact with the same connection at the same time. Is your connection object designed to be able to be used by multiple threads at the same time? – Servy Oct 24 '17 at 18:11
  • The client code calls the `Dispose` when it feels it doesn't need the connection anymore. Who and when would then dispose *all objects created by the factory*? The pool looks static, which suggests it should be garbage collected only when the process ends. – Wiktor Zychla Oct 24 '17 at 18:12
  • @Servy my object is not an connection actually, just image an IDisposable class. – codewarrior Oct 24 '17 at 18:13
  • As a rule, the creator of a disposable object is responsible for disposing of it. That's the scope of the object, if you will. This suggests that your pool is responsible for disposing of these objects -- that is, if it's a good idea to write it at all. – 15ee8f99-57ff-4f92-890c-b56153 Oct 24 '17 at 18:14
  • @WiktorZychla If a single consumer disposes of the connection when it's done then all of the other people using it, or who will be given it later, will break as they'd be using an already disposed object. You need to ensure that they *don't* dispose of the connection, so that it can be reused. As for holding them until process ends, that will depend on the particulars of the pool. Some will dispose of resources that have been unused for a while, to avoid holding onto way more resources than is needed (obviously this one isn't doing that at the moment). – Servy Oct 24 '17 at 18:14
  • @codewarrior Well then what *is* it? The specifics of what it is and how it needs to be used, what resource it represents, how difficult it is to construct, when it can be and when it needs to be disposed, etc. all radically affect how your pooling of it needs to operate (and even whether it needs to exist in the first place, as mentioned earlier). – Servy Oct 24 '17 at 18:16
  • @Servy: *If a single consumer disposes of the connection when it's done then all of the other people using it, or who will be given it later, will break as they'd be using an already disposed object.* That sounds false, `SqlConnection` is a working example. One client disposes it and sees it as disposed forever from then, other clients acquire it through the pool and can use it. It's also easy to implement this in a custom pool. I believe then, there's some misunderstanding here between us. – Wiktor Zychla Oct 24 '17 at 18:22
  • You could either keep track of them via the factory and dispose via the factory, or you could just `using(factory.CreateServiceClient(someHokeyString))` and let the language construct dispose for you, or you could do basically `int.MaxSize` number of things, and every one of them could be a legitimate way to handle your challenge. Looking for a cookie-cutter approach to your challenge can lead to trouble, to be honest. – code4life Oct 24 '17 at 18:24
  • @WiktorZychla The typical behavior for a disposed resource is to throw an `ObjectDisposedException` when someone tries to use it after it has been disposed. You are correct that, technically, implementations are free to ignore that and function anyway when disposed, but this is however rare. You are correct that the way a well designed connection pool works is that the pool doesn't give out the "real" object, it gives out a wrapper that, when disposed, merely returns the pooled resource to the pool, rather than actually disposing of it. The OP doesn't (yet) have such a wrapper. – Servy Oct 24 '17 at 18:26
  • `IDisposable` and `"Should be reused"` are mutually exclusive. Don't put those two things together. – Joel Coehoorn Oct 24 '17 at 18:39
  • 1
    Note that if you're really considering "hook[ing] the AppDomain exit event and manually call Dispose() on every ServiceClient instance", then you might also consider doing *nothing*. All OS-level resources (sockets, memory, file handles) will be released by the OS when the process ends. Of course, if your disposables have custom logic (like sending a "bye" somewhere or flushing a file) it's another matter. At the core, Dispose is intended to release resources deterministically and as fast as possible. If you're holding on to things forever, it basically loses its purpose. – Jeroen Mostert Oct 24 '17 at 18:43
  • @JoelCoehoorn I am consuming an external library, library creator highly recommended to keep one instance for one URL in whole process. – codewarrior Oct 24 '17 at 18:44
  • @JeroenMostert I don't know if there are any magic inside ServiceClient.Dispose(), because it is an external library, so I think I should dispose it explicitly. And by the way please note that the dictionary is static, but ServiceClient instances are not static. – codewarrior Oct 24 '17 at 18:53
  • If `ServiceClient` is a plain old WCF/SOAP client communicating with an HTTP endpoint, there's no actual magic in it. My point is: if you're encouraged to hold on to the thing for as long as you can to minimize the cost of creating it, to the point of wanting to hold on to it for the entire lifetime of the application, then you basically have no need of ever disposing it, except to prevent leaks in some external server (but I doubt that's the case here). If you just want to hold on to it "for quite a long time", consider leveraging `MemoryCache`. – Jeroen Mostert Oct 24 '17 at 18:58
  • @JeroenMostert Basically I don't know, have to decompile the library. If there are no magic inside Dispose(), why would author implement IDisposable? If Dispose() is not obliged to call here. – codewarrior Oct 24 '17 at 19:01
  • 9 times out of 10, because the object itself has references to disposable objects, like (for example) `Socket`. `Dispose` is there to give you the *option* of deterministic disposal of things that would otherwise hang around and unacceptably consume precious and limited OS-level resources when you're already done with them. Process exit is the ultimate disposal of all those resources, though. (It has to be -- a process could, after all, always *crash* without anyone calling dispose or finalizers on anything.) – Jeroen Mostert Oct 24 '17 at 19:05
  • @JoelCoehoorn Reusing disposable objects isn't a mutually exclusive idea at all. Things that are disposable have a tendency to be expensive, and things that are expensive are things that are often better off reused rather than being re-created, when that's feasible. The two most common examples are threads and network connections, both of which are expensive resources that need to be cleaned up when finished, but that can, and almost always are, re-used by a resource pool to avoid expensive allocation/deallocation costs. Whether the OP's resource is amenable to pooling remains to be seen. – Servy Oct 24 '17 at 19:09

2 Answers2

1

A couple of things to think about here...

Firstly, what you appear to be describing is some variation of the Flyweight pattern. You have an expensive object, ServiceClient, which you want to reuse, but you want to allow consumer objects to create and destroy at will without breaking the expensive object. Flyweight traditionally does this with reference-counting, which might be a bit old hat.

You'll need to make sure that consumers cannot dispose the ServiceClient directly, so you'll also need a lightweight Facade which intercepts the calls to ServiceClient.Dispose and chooses whether to dispose the real object or not. You should hide the real ServiceClient from consumers.

If all that is feasible, you could rewrite your approach as something like:

// this is the facade that you will work from, instead of ServiceClient
public interface IMyServiceClient : IDisposable
{
    void Query(string query);
}

// This is your factory, reworked to provide flyweight instances
// of IMyServiceClient, instead of the real ServiceClient
public class ServiceClientFactory : IDisposable
{
    // This is the concrete implementation of IMyServiceClient 
    // that the factory will create and you can pass around; it 
    // provides both the reference count and facade implementation
    // and is nested inside the factory to indicate that consumers 
    // should not alter these (and cannot without reflecting on 
    // non-publics)
    private class CachedServiceClient : IMyServiceClient
    {
        internal ServiceClient _realServiceClient;
        internal int _referenceCount;

        #region Facade wrapper methods around real ServiceClient ones
        void IMyServiceClient.Query(string query)
        {
            _realServiceClient.Query(query);
        }
        #endregion

        #region IDisposable for the client facade
        private bool _isClientDisposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!_isClientDisposed)
            {
                if (Interlocked.Decrement(ref _referenceCount) == 0)
                {
                    // if there are no more references, we really
                    // dispose the real object
                    using (_realServiceClient) { /*NOOP*/ }
                }
                _isClientDisposed = true;
            }
        }

        ~CachedServiceClient() { Dispose(false); }

        void IDisposable.Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

    // The object cache; note that it is not static
    private readonly ConcurrentDictionary<string, CachedServiceClient> _cache
        = new ConcurrentDictionary<string, CachedServiceClient>();

    // The method which allows consumers to create the client; note
    // that it returns the facade interface, rather than the concrete
    // class, so as to hide the implementation details
    public IMyServiceClient CreateServiceClient(string host)
    {
        var cached = _cache.GetOrAdd(
            host,
            k => new CachedServiceClient()
        );
        if (Interlocked.Increment(ref cached._referenceCount) == 1)
        {
            cached._realServiceClient = new ServiceClient(host);
        }
        return cached;
    }

    #region IDisposable for the factory (will forcibly clean up all cached items)
    private bool _isFactoryDisposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_isFactoryDisposed)
        {
            Debug.WriteLine($"ServiceClientFactory #{GetHashCode()} disposing cache");
            if (disposing)
            {
                foreach (var element in _cache)
                {
                    element.Value._referenceCount = 0;
                    using (element.Value._realServiceClient) { }
                }
            }
            _cache.Clear();
            _isFactoryDisposed = true;
        }
    }

    ~ServiceClientFactory() { Dispose(false); }

    void IDisposable.Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion
}

// This is just an example `ServiceClient` which uses the default
// implementation of GetHashCode to "prove" that new objects aren't
// created unnecessarily; note it does not implement `IMyServiceClient`
public class ServiceClient : IDisposable
{
    private readonly string _host;

    public ServiceClient(string host)
    {
        _host = host;
        Debug.WriteLine($"ServiceClient #{GetHashCode()} was created for {_host}");
    }

    public void Query(string query)
    {
        Debug.WriteLine($"ServiceClient #{GetHashCode()} asked '{query}' to {_host}");
    }

    public void Dispose()
    {
        Debug.WriteLine($"ServiceClient #{GetHashCode()} for {_host} was disposed");
        GC.SuppressFinalize(this);
    }
}

Secondly, in general, I'd remove the static clauses from the factory and make ServiceClientFactory : IDisposable. You'll see I've done that in my example above.

It may seem like you're just pushing the problem up the chain, but doing so allows you to make this decision on a case-by-case basis, per-something (application, session, request, unit test run -- whatever makes sense) and have the object that represents your something be responsible for the disposal.

If your application would benefit from a single-instance cache then expose a singleton instance as part of an AppContext class (for example) and call AppContext.DefaultServiceClientFactory.Dispose() in your clean-shutdown routine.

The emphasis here is on a clean shutdown. As others have stated, there is no guarantee that your Dispose method will ever actually be called (think power-cycling the machine, mid-run). As such, ideally, ServiceClient.Dispose would not have any tangible side-effects (i.e. beyond freeing resources that would be freed naturally if the process terminates or the machine power-cycles).

If ServiceClient.Dispose does have tangible side-effects, then you have identified a risk here, and you should clearly document how to recover your system from an "unclean" shutdown, in an accompanying user manual.

Thirdly, if both ServiceClient and QueryExecutor objects are intended to be reusable, then let the consumer be responsible for both the creation and disposal.

QueryExecutor only really has to be IDisposable in your sample because it can own a ServiceClient (which is also IDisposable). If QueryExecutor didn't actually create the ServiceClient, it wouldn't be responsible for destroying it.

Instead, have the constructor take a ServiceClient parameter (or, using my rewrite, an IMyServiceClient parameter) instead of a string, so the immediate consumer can be responsible for the lifetime of all objects:

using (var client = AppContext.DefaultServiceClientFactory.CreateServiceClient("localhost"))
{
    var query = new QueryExecutor(client);
    using (var reader = query.ExecuteReader("SELECT * FROM foo"))
    {
        //...
    }
}

PS: Is there any need for consumers to actually directly access ServiceClient or are there any other objects which need a reference to it? If not, perhaps reduce the chain a little and move this stuff directly to QueryExecutor, i.e. using a QueryExecutorFactory to create flyweight QueryExector objects around cached ServiceClient instances, instead.

jimbobmcgee
  • 1,561
  • 11
  • 34
-1

Static classes are tricky that way. The constructor for a static class is invoked when an instance member is first invoked. The class is destroyed if and when the application domain is destroyed. If the application terminates abruptly (aborts), there's no guarantee a constructor/finalizer would be called.

Your best bet here is to redesign the class to use a singleton pattern. There are ways to work around this issue, but they require strange, dark magic forces that may cost you your soul.

EDIT: As servy points out, a Singleton won't help here. A destructor is a finalizer, and you won't have any guarantee that it will be called (for a wide variety of reasons). If it were me, I'd simply refactor it to an instantiable class that implements IDisposable, and let the caller deal with calling Dispose. Not ideal, I know, but it's the only way to be sure.

Mike Hofer
  • 16,477
  • 11
  • 74
  • 110
  • What would it help for this object to be a singleton? – Servy Oct 24 '17 at 18:22
  • A singleton is a non-static class that is instantiated somewhere else. (It can even be instantiated as the property of a static class, and kept around, if you like.) The benefit of it, however, is that it can have a destructor, whereas a static class cannot. – Mike Hofer Oct 24 '17 at 18:26
  • 1
    It can *have* a finalizer, but it would never run, as there would always be a rooted reference to it, so it wouldn't *accomplish* anything, other than being misleading. – Servy Oct 24 '17 at 18:27
  • @Servy I stand corrected. Earlier documentation (circa 2010) drew a distinction between destructors and finalizers. More recent documentation makes it a bit clearer that they are one and the same. – Mike Hofer Oct 24 '17 at 18:35
  • @MikeHofer Please note that the dictionary is static, but ServiceClient instances are not. What causes the trouble here is ServiceClient, not the static Dictionary. – codewarrior Oct 24 '17 at 19:05
  • @codewarrior objects are never static or non-static. *Variables* are static or non-static variables. Your code has a static variable that holds a dictionary, which itself is holding a bunch of instances of your disposable object, along with whatever other places those object may be held by as a result of being returned from the method. – Servy Oct 24 '17 at 19:06
  • @Servy static will change the lifetime of object. e.g. static members are created when type is initialized or it is referenced, static members will not be GCed. But non-static variables' lifetime is different. The problem we are discussing here actually is about lifetime management of IDisposable objects. – codewarrior Oct 24 '17 at 19:12
  • @codewarrior Static changes the lifetime of *the variable*, which has some indirect effects on the lifetime of the objects it contains. The point is that it's not the dictionary, or the `ServiceClient`, that is static, it's the variable named `Clients` that's static. The dictionary simply happens to be what's in it, and the clients happen to be what is in that dictionary. Because of that, the clients are all accessible, indirectly, through that static variable. – Servy Oct 24 '17 at 19:15