In part 1 of this series, we looked at how using an IOC container helped us separate the concerns of the construction of a ZookeeperClient from the concerns of using it in service handlers. In this one, we look at how the IOC container can transparently help us manage a singleton which leaks memory.
Our goal is to implement a lifestyle which is like the Singleton lifestyle — returning the same object over and over — but retiring the old singleton every so often and making a new one. We’ll call this the RecycledSingletonLifestyle. Castle Windsorasks us to implement the following abstract class to make our own custom lifestyle.
public abstract class AbstractLifestyleManager : ILifestyleManager, IDisposable{ public virtual void Init(IComponentActivator componentActivator, IKernel kernel, ComponentModel model); public virtual bool Release(object instance); public virtual object Resolve(CreationContext context); public abstract void Dispose();}
Castle Windsor will call Init and Dispose when the app starts up and shuts down, but the real action is in Resolve and Release. Each time a handler asks for anIZookeeperClient, the container will call Resolve, and every time the handler is disposed, it will call Release. A simple singleton lifestyle will just make one instance the first time Resolve is called, and ‘really release’ it when the whole app is disposed, all the while just ignoring calls to Release.
Our strategy will be to implement a reference counting singleton, so that even if a certain time has passed and we have moved on and made a new singleton, we know when people are done using the previous one we handed out. Let’s look at that first. (It’s not safe for access from multiple threads, which might be surprising in an IOC plugin, but we’ll see why this is okay later.)
You construct this class with the instance you intend to be the singleton, and a method to call to release or dispose that singleton. All we do is keep track of it, increment a reference count when someone resolves it, decrement the reference count when they release it, and call the release method when the ref count drops to zero, or we are explicitly disposed.
It’s roughly what the base Singleton lifestyle is likely to do. Our real implementation had some nice logging and debug support which we’ve omitted here for clarity.
private class Singleton : IDisposable{ public Singleton(object instance, Action<object width="300" height="150"> release) { _instance = instance; _release = release; _refCount = 0; _isDisposed = false; _isExpired = false; } public object Instance { get { return _instance; } } public object Resolve() { if (_isDisposed) { throw new ObjectDisposedException(); } ++_refCount; return _instance; } public bool Release(object instance) { if (_isDisposed) { return true; } if (_refCount < 0 || !object.ReferenceEquals(instance, _instance)) { throw new InvalidOperationException(); } --_refCount; if (_isExpired && _refCount == 0) { Dispose(); return true; } else { return false; } } public void Dispose() { if (_isDisposed) { return; } _release(_instance); _isDisposed = true; _refCount = 0; }}Nothing too exciting. But what if it’s time to expire a singleton, and yet people are still using it? We want to keep track of this event. So <code>Singleton</code> also implements<pre>public bool Expire(){ _isExpired = true; if (!_isDisposed && _refCount > 0) { return false; // still in use } else { // not in use: we can actually dispose Dipose(); return true; }}
Now we combine this simple reference counting singleton with the behavior of expiring one and creating a new one.Keeping to the single-responsibility principle, this class won’t itself run a timer. It’ll just be constructed with an IObservable<Unit> which will be triggered every time we are to stop using the current singleton and use a new one instead. You could supply a timer, or you could attach to a monitor looking at resources, or keyboard presses.
public abstract class RecycledSingletonLifestyle : AbstractLifestyleManager, IDisposable, IObserver<unit> { public RecycledSingletonLifestyle(IObservable<unit> recycle) { _recycleSubscription = recycle.Subscribe(this); _syncLock = new object(); _singletons = new List<singleton>(4); _isDisposed = false; _currentSingleton = null; }}</singleton></unit></unit>
Recall that we will have a current preferred singleton we hand out (_currentSingleton) until we receive notice that we should retire it (when the recycle observable’s OnNext is called).Because this lifestyle can be called from multiple threads, we’ll execute everything in a lock. This keeps the logic simple, and we don’t imagine this being an extremely heavily used function. (And this is why we didn’t bother writing our own Singletonclass to be thread-safe.) First, what happens when the container wants to resolve an instance?
public override object Resolve(Castle.MicroKernel.Context.CreationContext context){ lock (_syncLock) { // lazily create the singleton if we haven’t done so if (_currentSingleton == null) { // the base class does the real work of making the object object resolved = base.Resolve(context); // we keep track of it in our Singleton class, // referencing a method declaring how to // really release the singleton when the refcount // goes to zero _currentSingleton = new Singleton(resolved, base.Release); _singletons.Add(_currentSingleton); } // resolve the current singleton // (which might have just been created) // this increments its reference count return _currentSingleton.Resolve(); }}
Why do we have a list of Singletons? Because we may expire the current singleton, i.e. start handing out references to a new one, before everyone is done using the old one. By keeping a list of all the ones we’ve handed out, we can safely dispose of all them when the application shuts down.What happens when a user releases the instance they got? In a Singleton, we ignore this request. But here we must take care to let our Singleton know it’s being released, so it can decrement its reference count.
public override bool Release(object instance) { if (instance == null) { return true; } lock (_syncLock) { var singleton = _singletons.FirstOrDefault( s => object.ReferenceEquals( s.Instance, instance)); if (singleton == null) { throw new InvalidOperationException(); } return singleton.Release(instance); }}
And when our lifecycle is disposed — when the application shuts down — we want to dispose all the singletons we may be hanging on to.
protected virtual void Dispose(bool isDispose) { if (!isDisposing) { return; } lock (_syncLock) { if (_isDisposed) { return; } _isDisposed = true; if (_recycleSubscription != null) { _recycleSubscription.Dispose(); } // expires all the singletons OnNext(Unit.Default); foreach(var s in _singletons) { s.Dispose(); } _currentSingleton = null;}
All that remains is the mechanics of responding to the notice we should expire the current singleton:
public void OnNext(Unit value){ lock (_syncLock) { // all the ones in use now are no longer to be used foreach(var s in _singletons) { s.Expire(); } // and the one we’ve been handing out shouldn’t be // a new one will be created next time we ask _currentSingleton = null; }}
So Singleton refcounts the object in question — in our case an IZookeeperClient— and RecycledSingletonLifestyle keeps track of the current singleton and a pool of former singletons. It adds up to a hundred or so lines of code, but the state manipulation is fairly straightforward. (By the time you add logging and unit tests, we’re at about 400 lines, but that’s life when you want code that actually goes into production!)And all we have to do is register the lifestyle itself.
container.Register( Component.For<recycledsingletonlifestyle> ImplementedBy<recycledsingletonlifestyle> .DependsOn(new { recycle = Observable.Timer(TimeSpan.FromMinutes(10)) }) .Lifestyle.Singleton);</recycledsingletonlifestyle></recycledsingletonlifestyle>
The nice thing about all this pooling and expiration logic is that it’s collected in one place, and it works in synchrony with the IOC container, managing startup, shutdown, creation and disposal cleanly. Other parts of the program don’t have to manage, or even know about, how this works.Now mind you, it would have been even nicer if the Zookeeper client didn’t have a resource leak, but for a day’s extra work, we were able to hide the problem so it never came out and bit us in production, or even troubled any of our developers.That’s the sort of thing IOC containers are good at.
Tell us what you need and one of our experts will get back to you.