Deterministic Finalization and IDisposable Part 3: Useful IDisposable Class 1: TimedLock

This is part 3 of my Deterministic Finalization and IDisposable post series.

For the first example of a useful custom class which implements IDisposable, I will simply link to and reproduce Ian Griffith’s TimedLock — an enhancement of the C# lock statement which allows the specification of a timeout period instead of blocking forever while trying to obtain the lock.

The code for TimedLock is reproduced below:

using System;
using System.Threading;

// Thanks to Eric Gunnerson for recommending this be a struct rather
// than a class - avoids a heap allocation.
// Thanks to Change Gillespie and Jocelyn Coulmance for pointing out
// the bugs that then crept in when I changed it to use struct...
// Thanks to John Sands for providing the necessary incentive to make
// me invent a way of using a struct in both release and debug builds
// without losing the debug leak tracking.
public struct TimedLock : IDisposable
{
    public static TimedLock Lock (object o)
    {
        return Lock (o, TimeSpan.FromSeconds (10));
    }

    public static TimedLock Lock (object o, TimeSpan timeout)
    {
        TimedLock tl = new TimedLock (o);
        if (!Monitor.TryEnter (o, timeout))
        {
#if DEBUG
            System.GC.SuppressFinalize(tl.leakDetector);
#endif
            throw new LockTimeoutException ();
        }

        return tl;
    }

    private TimedLock (object o)
    {
        target = o;
#if DEBUG
        leakDetector = new Sentinel();
#endif
    }

    private object target;

    public void Dispose ()
    {
        Monitor.Exit (target);

        // It's a bad error if someone forgets to call Dispose,
        // so in Debug builds, we put a finalizer in to detect
        // the error. If Dispose is called, we suppress the
        // finalizer.
#if DEBUG
        GC.SuppressFinalize(leakDetector);
#endif
    }

#if DEBUG
    // (In Debug mode, we make it a class so that we can add a finalizer
    // in order to detect when the object is not freed.)
    private class Sentinel
    {
        ~Sentinel()
        {
            // If this finalizer runs, someone somewhere failed to
            // call Dispose, which means we've failed to leave
            // a monitor!
            System.Diagnostics.Debug.Fail("Undisposed lock");
        }
    }
    private Sentinel leakDetector;
#endif
}

public class LockTimeoutException : ApplicationException
{
    public LockTimeoutException () : base("Timeout waiting for lock")
    {
    }
}

It is trivial to use TimedLock instead of lock in your applications. Simply change statements from:

lock (objectToLock)
{
    ... Do work while holding lock
}

… to:

using (TimedLock.Lock(objectToLock))
{
    ... Do work while holding lock
}

Others have enhanced TimedLock even futher, such as by having it keep track of the stack trace of the thread which is holding the lock.

About Steven Engelhardt, CFA, AIF
Adjunct Professor of Software Engineering at DePaul University • Software Engineering, Data & Analytics in FinTech • Lives in Chicago, IL

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s