Search code examples
c#multithreadingsynchronizationcritical-sectiontrusted-timestamp

Locking a Resource and generating Time Stamps according to lock time


Suppose that I would like to implement a synchronization primitive which generates a time stamp that is going to be used in a synchronization protocol. The time stamp would be such, that for a given key used to lock a resource, no two threads would be able to obtain the same time stamp value.

A possible implementation of the latter specification would be:

namespace InfinityLabs.PowersInfinity.BCL.Synchronization
{

public static class TimeStampMonitor
{
    private static readonly IDictionary<object, long> TimeStamps;

    static TimeStampMonitor()
    {
        TimeStamps = new Dictionary<object, long>();
    }

    #region API

    public static long Enter(object key)
    {
        var lockTaken = false;

        Monitor.Enter(key, ref lockTaken);

        ThrowIfLockNotAcquired(key, lockTaken);

        var timeStamp = GetCurrentTimeStamp();

        Thread.Sleep(1);

        TimeStamps.Add(key, timeStamp);

        return timeStamp;
    }

    public static void Exit(object key)
    {
        var lockTaken = false;

        Monitor.Enter(key, ref lockTaken);

        try
        {
            ThrowIfInvalidKey(key);
            TimeStamps.Remove(key);
        }
        finally
        {
            if (lockTaken)
                Monitor.Exit(key);
        }
    }

    public static long GetTimeStampOrThrow(object key)
    {
        TryEnterOrThrow(key);

        var timeStamp = GetTimeStamp(key);
        return timeStamp;
    }

    public static void TryEnterOrThrow(object key)
    {
        var lockTaken = false;

        try
        {
            Monitor.Enter(key, ref lockTaken);

            ThrowIfLockNotAcquired(key, lockTaken);
            ThrowIfInvalidKey(key);
        }
        catch (SynchronizationException)
        {
            throw;
        }
        catch (Exception)
        {
            if(lockTaken)
                Monitor.Exit(key);

            throw;
        }
    }

    #endregion

    #region Time Stamping

    private static long GetCurrentTimeStamp()
    {
        var timeStamp = DateTime.Now.ToUnixTime();
        return timeStamp;
    }

    private static long GetTimeStamp(object key)
    {
        var timeStamp = TimeStamps[key];
        return timeStamp;
    }

    #endregion

    #region Validation

    private static void ThrowIfInvalidKey(object key, [CallerMemberName] string methodName = null)
    {
        if (!TimeStamps.ContainsKey(key))
            throw new InvalidOperationException($"Must invoke '{nameof(Enter)}' prior to invoking '{methodName}'. Key: '{key}'");
    }

    private static void ThrowIfLockNotAcquired(object key, bool lockTaken)
    {
        if (!lockTaken)
            throw new SynchronizationException($"Unable to acquire lock for key '{key}'");
    }
    #endregion
}
}

Mind that the two API methods TryEnterOrThrow and GetTimeStampOrThrow are intended to be used by consuming classes as guard methods which disallow poorly written code to break the critical section's atomicity. The latter method also returns a previously acquired time stamp value for a given key. The time stamp is maintained so long that its owner did not exit the critical section.

I have been running all possible scenarios through my mind, and I cant seem to break it- not only atomically, but by trying to misuse it. I guess my question is, since this is one of my very few attempts at writing synchronization primitives- is this code fool proof and does it provide atomicity?

Help would be much appreciated!


Solution

  • Take a look at https://stackoverflow.com/a/14369695/224370

    You can create a unique timestamp without locking. Code below.

    Your question differs slightly in that you want a unique timestamp per key, but if you have a globally unique timestamp then you automatically have a unique timestamp per key without any extra effort. So I don't think you really need a dictionary for this:

    public class HiResDateTime
    {
       private static long lastTimeStamp = DateTime.UtcNow.Ticks;
       public static long UtcNowTicks
       {
           get
           {
               long original, newValue;
               do
               {
                   original = lastTimeStamp;
                   long now = DateTime.UtcNow.Ticks;
                   newValue = Math.Max(now, original + 1);
               } while (Interlocked.CompareExchange
                            (ref lastTimeStamp, newValue, original) != original);
    
               return newValue;
           }
       }
    }