As with any multithreaded program, deadlocks are a huge pain in the butt, and when they happen it costs time, money, and stress.
In my code base I’ve introduced something called an ExtendedLock, which basically has something like this inside:
1: public class ExtendedLock : IExtendedLock {
2: public IDisposable Lock() {
3: while (!Monitor.TryEnter(this, 5000)) {
4: IncrementLockTime();
5: }
6: return new Disposer(() => Release());
7: }
8: public event Deadlock;
9: }
Pretty simple. IncrementLockTime, as the name implies keeps track of how long the current thread has been attempting to acquire the lock. It returns a Disposer which takes an Action, which releases the lock. This allows us to take advantage of the using syntax, and avoid boiler plate try/finally (oh, and it avoids typos in Monitor.Exit). After some configurable amount of time, if the lock cannot be acquired within, say, 2 minutes, it’s probably a good probability your application is blocked somewhere.
Now, using this class basically means replacing lock(_syncRoot) type code with _elock.Lock(). Also, I believe it’s a good candidate for “mixing” into any other component. Mixins are sort of like multiple-inheritance, but not. I like to think of mixins as a “can do” rather than “is a.”
Now, we know that C# doesn’t let you do multiple inheritance, but with libraries like Castle’s DynamicProxy2, it lets you do something very similar, and is extremely powerful. In a sense, it will automatically generate the following code for you:
1: public class SomeService : ISomeService, IExtendedLock {
2: IExtendedLock _lock = new ExtendedLock();
3: public void DoSomething() { }
4: IDisposable IExtendedLock.Lock() { return _lock.Lock(); }
5: }
_lock is a private instance variable, SomeService implements IExtendedLock, and simply redirects all the interface methods to _lock. This seems pretty simple and straightforward, but becomes tedious when the type you want to mix in has many methods (as my actual IExtendedLock is).
With Windsor/DynamicProxy, you can do this automatically with minimal amount of code. For example, first you define something like this:
public interface ILockableDictionary : IDictionary, IExtendedLock { }
Then, you register it in the container:
1: var container = new WindsorContainer();
2: container.Register(Component.For(typeof(ILockableHashtable))
3: .LifeStyle.Transient
4: .Activator<LockableHashtableActivator>());
Now, whenever you need an instance of a lockable hashtable you can simply do something like this:
var hash = container.Resolve<ILockableHashtable>(); using (hash.Lock()) { hash["1"] = 1; }
You might be wondering why it’s worth all this trouble, and what’s wrong with regular locks and Monitor. For our system it’s pretty critical that it stays running 24/7, and every minute it’s down is money lost, so it is in our best interest to detect any problematic condition.
Last but not least, here’s the important code that actually generates the proxy:
1: internal class LockableHashtableActivator : DefaultComponentActivator
2: {
3: public LockableHashtableActivator(ComponentModel model, IKernel kernel, ComponentInstanceDelegate onCreation, ComponentInstanceDelegate onDestruction)
4: : base(model, kernel, onCreation, onDestruction)
5: {
6: }
7:
8: public override object Create(CreationContext context)
9: {
10: IExtendedLock lockMixin = Kernel.Resolve<IExtendedLock>();
11:
12: // an additional object we want to "mix" with the implementation to provide combined functionality
13: ProxyGenerationOptions options = new ProxyGenerationOptions();
14: options.AddMixinInstance(lockMixin);
15:
16: return Kernel.Resolve<ProxyGenerator>().CreateInterfaceProxyWithTarget(
17: typeof(IDictionary), // the interface of the implementation
18: new[] { typeof(ILockableHashtable) }, // additional interfaces to use
19: Activator.CreateInstance<Hashtable>(), // concrete implementation to mix into
20: options);
21: }
22: }
For those who are familiar with Windsor and wondering why I didn’t use the fluent Proxy.Mixins method, it’s because those mixins are created once per registration. In this case, it is very important that each mixin (which is an extended lock), is transient, otherwise every lockable hashtable ends up with the same extended lock, which is just asking for trouble.
1 comment:
actually, with the trunk version the limitation "one mixin per registration" was lifted off. Now Mixins behave pretty much like interceptors, which means they can have any lifestyle, including being transient.
Post a Comment