0

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!

Eyal Perry
  • 2,255
  • 18
  • 26
  • Just looking through this is not going to work: sleeping under lock usually is superslow – Lol4t0 Oct 24 '15 at 20:08
  • 2
    one issue here ... `DateTime.Now` is updated within an interval of some ms only, as this is no high precision implementation like `TimeSpan` is. –  Oct 24 '15 at 20:12
  • @Lol4t0 : what would you reccomend? – Eyal Perry Oct 24 '15 at 20:17
  • @AndreasNiedermair I will consider that! thanks! – Eyal Perry Oct 24 '15 at 20:17
  • It depends on the task actually. May be just an integer initialized with 0 and incrementing on 1 on every call will be ok (so it will work like _logical_ timestamp). But I have to understand what you are going to achieve first (and most probably I could not provide best answer in C# land anyway) – Lol4t0 Oct 24 '15 at 20:34
  • @Lol4t0 I am syncing db tables with client devices.. since each client stores the time stamp issued by the server and receives info according to that time stamp on the next sync session, I can't risk the timestamp resetting. logical timestamps do hold that risk in the grand scheme of things. anyway - the answer below suffices and even does so without locking. Nonetheless, if you have any ideas, I am always willing to learn. – Eyal Perry Oct 25 '15 at 05:51

1 Answers1

2

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;
       }
   }
}
Community
  • 1
  • 1
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • I have to lock since I am using the time stamp in a synchronization protocol, per some key which has to block threads trying to work with the resource.. – Eyal Perry Oct 24 '15 at 20:12
  • 1
    @EyalPerry please read the documentation on `Interlocked`. only one thread can compare and exchange the value, hence it is lock-free through enforced CPU instruction ordering. –  Oct 24 '15 at 20:15
  • 3
    Just as a aide-note: `DateTime.Now` is not high-precision timed, as its internal value is only updated every 35ms or so. –  Oct 24 '15 at 20:18
  • @AndreasNiedermair noted, sir ! – Eyal Perry Oct 24 '15 at 20:36