309 lines · 13.6 KB · C#
RawDownload
//******************************************************************************************************//  InterprocessReaderWriterLock.cs - Gbtc////  Copyright © 2012, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/MIT////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  03/21/2011 - J. Ritchie Carroll//       Generated original version of source code.//  12/14/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Threading; namespace GSF.Threading{    /// <summary>    /// Represents an inter-process reader/writer lock using <see cref="Semaphore"/> and <see cref="Mutex"/> native locking mechanisms.    /// </summary>    public class InterprocessReaderWriterLock : IDisposable    {        #region [ Members ]         // Constants         /// <summary>        /// Default maximum concurrent locks allowed for <see cref="InterprocessReaderWriterLock"/>.        /// </summary>        public const int DefaultMaximumConcurrentLocks = 10;         // Fields        private Mutex m_semaphoreLock;                  // Mutex used to synchronize access to Semaphore        private Semaphore m_concurrencyLock;            // Semaphore used for reader/writer lock on consumer object        private readonly int m_maximumConcurrentLocks;  // Maximum number of concurrent locks before waiting        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="InterprocessReaderWriterLock"/> associated with the specified        /// <paramref name="name"/> that identifies a source object needing concurrency locking.        /// </summary>        /// <param name="name">Identifying name of source object needing concurrency locking (e.g., a path and file name).</param>        public InterprocessReaderWriterLock(string name)            : this(name, DefaultMaximumConcurrentLocks)        {        }         /// <summary>        /// Creates a new instance of the <see cref="InterprocessReaderWriterLock"/> associated with the specified        /// <paramref name="name"/> that identifies a source object needing concurrency locking.        /// </summary>        /// <param name="name">Identifying name of source object needing concurrency locking (e.g., a path and file name).</param>        /// <param name="maximumConcurrentLocks">Maximum concurrent reader locks to allow.</param>        /// <remarks>        /// If more reader locks are requested than the <paramref name="maximumConcurrentLocks"/>, excess reader locks will simply        /// wait until a lock is available (i.e., one of the existing reads completes).        /// </remarks>        public InterprocessReaderWriterLock(string name, int maximumConcurrentLocks)        {            m_maximumConcurrentLocks = maximumConcurrentLocks;            m_semaphoreLock = InterprocessLock.GetNamedMutex(name);            m_concurrencyLock = InterprocessLock.GetNamedSemaphore(name, m_maximumConcurrentLocks);        }         /// <summary>        /// Releases the unmanaged resources before the <see cref="InterprocessReaderWriterLock"/> object is reclaimed by <see cref="GC"/>.        /// </summary>        ~InterprocessReaderWriterLock() =>            Dispose(false);         #endregion         #region [ Properties ]         /// <summary>        /// Gets the maximum concurrent reader locks allowed.        /// </summary>        public int MaximumConcurrentLocks => m_maximumConcurrentLocks;         #endregion         #region [ Methods ]         /// <summary>        /// Releases all the resources used by the <see cref="InterprocessReaderWriterLock"/> object.        /// </summary>        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="InterprocessReaderWriterLock"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected virtual void Dispose(bool disposing)        {            if (m_disposed)                return;             try            {                if (!disposing)                    return;                 if (!(m_concurrencyLock is null))                {                    m_concurrencyLock.Close();                    m_concurrencyLock = null;                }                 if (!(m_semaphoreLock is null))                {                    m_semaphoreLock.Close();                    m_semaphoreLock = null;                }            }            finally            {                m_disposed = true;  // Prevent duplicate dispose.            }        }         /// <summary>        /// Tries to enter the lock in read mode.        /// </summary>        /// <remarks>        /// Upon successful acquisition of a read lock, use the <c>finally</c> block of a <c>try/finally</c> statement to call <see cref="ExitReadLock"/>.        /// One <see cref="ExitReadLock"/> should be called for each <see cref="EnterReadLock"/> or <see cref="TryEnterReadLock"/>.        /// </remarks>        public void EnterReadLock() =>            TryEnterReadLock(Timeout.Infinite);         /// <summary>        /// Tries to enter the lock in write mode.        /// </summary>        /// <remarks>        /// Upon successful acquisition of a write lock, use the <c>finally</c> block of a <c>try/finally</c> statement to call <see cref="ExitWriteLock"/>.        /// One <see cref="ExitWriteLock"/> should be called for each <see cref="EnterWriteLock"/> or <see cref="TryEnterWriteLock"/>.        /// </remarks>        public void EnterWriteLock() =>            TryEnterWriteLock(Timeout.Infinite);         /// <summary>        /// Exits read mode and returns the prior read lock count.        /// </summary>        /// <remarks>        /// Upon successful acquisition of a read lock, use the <c>finally</c> block of a <c>try/finally</c> statement to call <see cref="ExitReadLock"/>.        /// One <see cref="ExitReadLock"/> should be called for each <see cref="EnterReadLock"/> or <see cref="TryEnterReadLock"/>.        /// </remarks>        public int ExitReadLock() =>            m_concurrencyLock.Release();    // Release the semaphore lock and restore the slot         /// <summary>        /// Exits write mode.        /// </summary>        /// <remarks>        /// Upon successful acquisition of a write lock, use the <c>finally</c> block of a <c>try/finally</c> statement to call <see cref="ExitWriteLock"/>.        /// One <see cref="ExitWriteLock"/> should be called for each <see cref="EnterWriteLock"/> or <see cref="TryEnterWriteLock"/>.        /// </remarks>        public void ExitWriteLock() =>            m_semaphoreLock.ReleaseMutex(); // Release semaphore synchronization mutex lock         /// <summary>        /// Tries to enter the lock in read mode, with an optional time-out.        /// </summary>        /// <param name="millisecondsTimeout">The number of milliseconds to wait, or -1 (<see cref="Timeout.Infinite"/>) to wait indefinitely.</param>        /// <returns><c>true</c> if the calling thread entered read mode, otherwise, <c>false</c>.</returns>        /// <remarks>        /// <para>        /// Upon successful acquisition of a read lock, use the <c>finally</c> block of a <c>try/finally</c> statement to call <see cref="ExitReadLock"/>.        /// One <see cref="ExitReadLock"/> should be called for each <see cref="EnterReadLock"/> or <see cref="TryEnterReadLock"/>.        /// </para>        /// <para>        /// Note that this function may wait as long as 2 * <paramref name="millisecondsTimeout"/> since the function first waits for synchronous access        /// to the semaphore, then waits again on an available semaphore slot.        /// </para>        /// </remarks>        public bool TryEnterReadLock(int millisecondsTimeout)        {            bool success;             try            {                // Wait for system level mutex lock to synchronize access to semaphore                if (!m_semaphoreLock.WaitOne(millisecondsTimeout))                    return false;            }            catch (AbandonedMutexException)            {                // Abnormal application terminations can leave a mutex abandoned, in                // this case we now own the mutex so we just ignore the exception            }             try            {                // Wait for a semaphore slot to become available                success = m_concurrencyLock.WaitOne(millisecondsTimeout);            }            finally            {                // Release mutex so others can access the semaphore                m_semaphoreLock.ReleaseMutex();            }             return success;        }         /// <summary>        /// Tries to enter the lock in write mode, with an optional time-out.        /// </summary>        /// <param name="millisecondsTimeout">The number of milliseconds to wait, or -1 (<see cref="Timeout.Infinite"/>) to wait indefinitely.</param>        /// <returns><c>true</c> if the calling thread entered write mode, otherwise, <c>false</c>.</returns>        /// <remarks>        /// <para>        /// Upon successful acquisition of a write lock, use the <c>finally</c> block of a <c>try/finally</c> statement to call <see cref="ExitWriteLock"/>.        /// One <see cref="ExitWriteLock"/> should be called for each <see cref="EnterWriteLock"/> or <see cref="TryEnterWriteLock"/>.        /// </para>        /// <para>        /// Note that this function may wait as long as 2 * <paramref name="millisecondsTimeout"/> since the function first waits for synchronous access        /// to the semaphore, then waits again on an available semaphore slot.        /// </para>        /// </remarks>        public bool TryEnterWriteLock(int millisecondsTimeout)        {            bool success = false;             try            {                // Wait for system level mutex lock to synchronize access to semaphore                if (!m_semaphoreLock.WaitOne(millisecondsTimeout))                    return false;            }            catch (AbandonedMutexException)            {                // Abnormal application terminations can leave a mutex abandoned, in                // this case we now own the mutex so we just ignore the exception            }             try            {                // At this point no other threads can acquire read or write access since we own the mutex.                // Other threads may be busy reading, so we wait until all semaphore slots become available.                // The only way to get a semaphore slot count is to execute a successful wait and release.                long startTime = DateTime.UtcNow.Ticks;                 success = m_concurrencyLock.WaitOne(millisecondsTimeout);                 if (success)                {                    int count = m_concurrencyLock.Release();                    int adjustedTimeout = millisecondsTimeout;                     // After a successful wait and release the returned semaphore slot count will be -1 of the                    // actual count since we owned one slot after a successful wait.                    while (success && count != m_maximumConcurrentLocks - 1)                    {                        // Sleep to allow any remaining reads to complete                        Thread.Sleep(1);                         // Continue to adjust remaining time to accommodate user specified millisecond timeout                        if (millisecondsTimeout > 0)                            adjustedTimeout = millisecondsTimeout - (int)Ticks.ToMilliseconds(DateTime.UtcNow.Ticks - startTime);                         if (adjustedTimeout < 0)                            adjustedTimeout = 0;                         success = m_concurrencyLock.WaitOne(adjustedTimeout);                         if (success)                            count = m_concurrencyLock.Release();                    }                }            }            finally            {                // If lock failed, release mutex so others can access the semaphore                if (!success)                    m_semaphoreLock.ReleaseMutex();            }             // Successfully entering write lock leaves state of semaphore locking mutex "owned", it is critical that            // consumer call ExitWriteLock upon completion of write action to release mutex regardless of whether            // their code succeeds or fails, that is, consumer should use the finally clause of a "try/finally"            // expression to ExitWriteLock.            return success;        }         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t