mikaelsnavy
12/12/2017 - 8:59 PM

Lock Tracker with Expiration

// Each type is own static instance
    public static class Locker<T> where T: class
    {
        /// <summary>
        ///     Holds resource key, experiation date
        /// </summary>
        internal static volatile ConcurrentDictionary<string, DateTime> _lockTracker;

        private static volatile Object _lockObject = new Object();

        static Locker()
        {
            // Get info for initializing our dict
            int numProcs = Environment.ProcessorCount;
            int concurrencyLevel = numProcs * 2; // Max operations at once (practically)
            int initialCapacity = 13;

            _lockTracker = new ConcurrentDictionary<string, DateTime>(concurrencyLevel, initialCapacity);
        }

        /// <summary>
        ///     Given a key, report if resource is locked
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static bool IsLocked(string id)
        {
            DateTime lockExpiration;

            if (_lockTracker.TryGetValue(id, out lockExpiration))
                return lockExpiration > DateTime.UtcNow;

            return false;
        }

        /// <summary>
        ///     Given a key, attempt to lock resource for specified amount of time
        /// </summary>
        /// <param name="id"></param>
        /// <param name="secondsToKeep"></param>
        /// <returns></returns>
        public static bool Lock(string id, int secondsToKeep)
        {
            DateTime expiration;

            // Lock if possible
            lock (_lockObject)
            {
                if (_lockTracker.TryGetValue(id, out expiration) && expiration > DateTime.UtcNow)
                    return false;

                _lockTracker[id] = DateTime.UtcNow.AddSeconds(secondsToKeep);
            }

            // Cleanup
            var keysToRemove = _lockTracker.Keys.Where(key => _lockTracker[key] < DateTime.UtcNow);
            foreach (var s in keysToRemove)
            {
                _lockTracker.TryRemove(s, out expiration);
            }

            return true;
        }

        /// <summary>
        ///     Given a key, release the lock by removing it from tracking
        /// </summary>
        /// <param name="id"></param>
        public static void Release(string id)
        {
            DateTime expiration;

            _lockTracker.TryRemove(id, out expiration);
        }


        // I don't like a bunch of ToStrings
        public static bool IsLocked(int id) => IsLocked(id.ToString());
        public static bool Lock(int id, int secondsToKeep) => Lock(id.ToString(), secondsToKeep);
        public static void Release(int id) => Release(id.ToString());

    }