Beispiel #1
0
        private int DequeueNextRequest()
        {
            lock (m_QueueLock)
            {
                int dequeueCount = 0;

                // Make sure we don't thread-abort in the middle of this logic.
                try
                {
                }
                finally
                {
                    while (m_CurrentLockTurn == null && m_WaitQueue.Count > 0)
                    {
                        m_CurrentLockTurn = m_WaitQueue.Dequeue();
                        dequeueCount++;

                        if (m_CurrentLockTurn.IsExpired)
                        {
                            m_CurrentLockTurn.Dispose(); // There's no one waiting on that request, so just discard it.
                            m_CurrentLockTurn = null;    // Get the next one (if any) on next loop.
                        }
                        else
                        {
                            m_CurrentLockTurn.Disposed += Lock_Disposed; // Subscribe to their Disposed event.  Now we care.
                        }
                    }
                }

                return(dequeueCount);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Check the thread with the current turn for the lock and grant a secondary lock if applicable.
        /// </summary>
        /// <param name="candidateLock">An unexpired lock request on the current thread, or null to just check the turn thread.</param>
        /// <returns>The Thread with the current turn for the lock, or null if there are none holding or waiting.</returns>
        internal Thread CheckCurrentTurnThread(InterprocessLock candidateLock)
        {
            if (candidateLock != null && candidateLock.OwningThread != Thread.CurrentThread)
            {
                throw new InvalidOperationException("A lock request may only be waited on by the thread which created it.");
            }

            lock (m_QueueLock)
            {
                if (m_CurrentLockTurn != null)
                {
                    Thread currentOwningThread = m_CurrentLockTurn.OwningThread;
                    if (candidateLock != null && Thread.CurrentThread == currentOwningThread)
                    {
                        candidateLock.GrantTheLock(m_CurrentLockTurn);     // Set it as a secondary lock on that holder (same thread).
                        if (candidateLock.ActualLock == m_CurrentLockTurn) // Sanity-check that it was successful.
                        {
                            candidateLock.OurLockProxy = this;             // So its dispose-on-close setting pass-through can function.
                        }
                    }

                    return(currentOwningThread); // Whether it's a match or some other thread.
                }

                return(null); // No thread owns the lock.
            }
        }
 internal InterprocessLock(object requester, string indexPath, string lockName, int timeoutSeconds)
 {
     m_OwningObject         = requester;
     m_OwningThread         = Thread.CurrentThread;
     m_IndexPath            = indexPath;
     m_LockName             = lockName;
     m_ActualLock           = null;
     m_MyTurn               = false;
     m_WaitForLock          = (timeoutSeconds > 0);
     m_WaitTimeout          = m_WaitForLock ? DateTimeOffset.Now.AddSeconds(timeoutSeconds) : DateTimeOffset.Now;
     m_LockFullFileNamePath = GetLockFileName(indexPath, lockName);
 }
Beispiel #4
0
        private void Lock_Disposed(object sender, EventArgs e)
        {
            InterprocessLock disposingLock = (InterprocessLock)sender;

            disposingLock.Disposed -= Lock_Disposed; // Unsubscribe.

            //we need to remove this object from the lock collection
            lock (m_QueueLock)
            {
                // Only remove the lock if the one we're disposing is the original top-level lock for that key.
                if (m_CurrentLockTurn == null || ReferenceEquals(m_CurrentLockTurn, disposingLock) == false)
                {
                    return;               // Wasn't our current holder, so we don't care about it.
                }
                m_CurrentLockTurn = null; // It's disposed, no longer current owner.

                if (m_Disposed == false)
                {
                    // We're releasing the lock for this thread.  We need to check if any other process has a request pending.
                    // And if so, we need to force this process to wait a minimum delay, even if we don't have one waiting now.
                    if (m_FileLock != null && CheckLockRequest(m_LockFullFileNamePath))
                    {
                        m_MinTimeNextTurn = DateTimeOffset.Now.AddMilliseconds(BackOffDelay); // Back off for a bit.
                        m_FileLock.Dispose();                                                 // We have to give up the OS lock because other processes need a chance.
                        m_FileLock = null;
                    }

                    StartNextTurn(null); // Find and signal the next turn to go ahead (also handles all-done).
                }
                else
                {
                    // We're already disposed, so we'd better release the lock and request now if we still have them!
                    if (m_LockRequest != null)
                    {
                        m_LockRequest.Dispose();
                        m_LockRequest = null;
                    }

                    if (m_FileLock != null)
                    {
                        m_FileLock.Dispose();
                        m_FileLock = null;
                    }
                }
            }
        }
Beispiel #5
0
        /// <summary>
        /// Queue a lock request (RepositoryLock instance).  Must be followed by a call to AwaitOurTurnOrTimeout (which can block).
        /// </summary>
        /// <param name="lockRequest"></param>
        internal void QueueRequest(InterprocessLock lockRequest)
        {
            if (string.Equals(lockRequest.FullName, m_LockFullFileNamePath, StringComparison.OrdinalIgnoreCase) == false)
            {
                throw new InvalidOperationException("A lock request may not be queued to a proxy for a different full name.");
            }

            if (lockRequest.OwningThread != Thread.CurrentThread)
            {
                throw new InvalidOperationException("A lock request may only be queued by the thread which created it.");
            }

            lock (m_QueueLock)
            {
                m_WaitQueue.Enqueue(lockRequest);
            }
        }
Beispiel #6
0
        /// <summary>
        /// Query whether a particular lock is available without holding on to it.
        /// </summary>
        /// <param name="requester">The object that is querying the lock (useful for debugging purposes)</param>
        /// <param name="indexPath">The fully qualified path to the directory containing the index file of the repository</param>
        /// <param name="lockName">The name of the lock to query (locks are a combination of index and this name)</param>
        /// <returns>True if the lock could have been obtained.  False if the lock could not be obtained without waiting.</returns>
        public static bool QueryLockAvailable(object requester, string indexPath, string lockName)
        {
            string fileName = InterprocessLockProxy.GetLockFileName(indexPath, lockName);

            if (System.IO.File.Exists(fileName) == false)
            {
                return(true); // Lock file didn't exist, so we could have obtained it.
            }
            bool lockAvailable;

            using (InterprocessLock attemptedLock = Lock(requester, indexPath, lockName, 0, true))
            {
                lockAvailable = (attemptedLock != null);
            }

            return(lockAvailable);
        }
Beispiel #7
0
        /// <summary>
        /// Try to get the actual file lock on behalf of the current request.
        /// </summary>
        /// <param name="currentRequest"></param>
        /// <returns></returns>
        private bool TryGetLock(InterprocessLock currentRequest)
        {
            bool           waitForLock = currentRequest.WaitForLock;
            DateTimeOffset lockTimeout = currentRequest.WaitTimeout;
            bool           validLock   = false;

            while (waitForLock == false || DateTimeOffset.Now < lockTimeout)
            {
                if (DateTimeOffset.Now >= m_MinTimeNextTurn)          // Make sure we aren't in a back-off delay.
                {
                    m_FileLock = GetFileLock(m_LockFullFileNamePath); // TODO: DeleteOnClose no longer supported in our file opens.
                    if (m_FileLock != null)
                    {
                        // We have the lock!  Close our lock request if we have one so later we can detect if anyone else does.
                        if (m_LockRequest != null)
                        {
                            m_LockRequest.Dispose();
                            m_LockRequest = null;
                        }

                        validLock = true; // Report that we have the lock now.
                    }
                }
                // Otherwise, just pretend we couldn't get the lock in this attempt.

                if (validLock == false && waitForLock)
                {
                    // We didn't get the lock and we want to wait for it, so try to open a lock request.
                    if (m_LockRequest == null)
                    {
                        m_LockRequest = GetLockRequest(m_LockFullFileNamePath); // Tell the other process we'd like a turn.
                    }
                    // Then we should allow some real time to pass before trying again because file opens aren't very fast.
                    Thread.Sleep(LockPollingDelay);
                }
                else
                {
                    // We either got the lock or the user doesn't want to keep retrying, so exit the loop.
                    break;
                }
            }

            return(validLock);
        }
        internal void GrantTheLock(InterprocessLock actualLock)
        {
            if (actualLock != null && actualLock.IsDisposed == false && actualLock.OwningThread == m_OwningThread &&
                string.Equals(actualLock.FullName, m_LockFullFileNamePath, StringComparison.OrdinalIgnoreCase))
            {
                // We don't need to lock around this because we're bypassing the proxy's queue and staying only on our own thread.
                m_ActualLock  = actualLock;
                m_WaitTimeout = DateTimeOffset.MaxValue; // We have a lock (sort of), so reset our timeout to forever.
            }
            else
            {
                // It's an invalid call, so make sure our setting is cleared out.
                m_ActualLock = null;
                // Note: Should this case always throw an exception?
#if DEBUG
                throw new InvalidOperationException("Can't set a secondary lock from an invalid actual lock.");
#endif
            }
        }
Beispiel #9
0
        /// <summary>
        /// Find the next request still waiting and signal it to go.  Or return true if the current caller may proceed.
        /// </summary>
        /// <param name="currentRequest">The request the caller is waiting on, or null for none.</param>
        /// <returns>True if the caller's supplied request is the next turn, false otherwise.</returns>
        private bool StartNextTurn(InterprocessLock currentRequest)
        {
            lock (m_QueueLock)
            {
                int dequeueCount = DequeueNextRequest(); // Find the next turn if there isn't one already underway.
                if (m_CurrentLockTurn != null)
                {
                    // If we popped a new turn off the queue make sure it gets started.
                    if (dequeueCount > 0)
                    {
                        m_CurrentLockTurn.SignalMyTurn();                   // Signal the thread waiting on that request to proceed.
                    }
                    if (ReferenceEquals(m_CurrentLockTurn, currentRequest)) // Is the current request the next turn?
                    {
                        return(true);                                       // Yes, so skip waiting and just tell our caller they can go ahead (and wait for the lock).
                    }
                }
                else
                {
                    // Otherwise, nothing else is waiting on the lock!  Time to shut it down.

                    if (m_LockRequest != null)
                    {
                        m_LockRequest.Dispose(); // Release the lock request (an open read) since we're no longer waiting on it.
                        m_LockRequest = null;
                    }

                    if (m_FileLock != null)
                    {
                        m_FileLock.Dispose(); // Release the OS file lock.
                        m_FileLock = null;
                    }

                    if (m_DisposeOnClose)
                    {
                        Dispose();
                    }
                }

                return(false);
            }
        }
        private async void RepositoryPublishMain()
        {
            //before we get going, lets stall for a few seconds.  We aren't a critical operation, and I don't
            //want to get in the way of the application starting up.
            if (BackgroundStartupDelay > 0)
            {
                if (!Log.SilentMode)
                {
                    Log.Write(LogMessageSeverity.Information, LogCategory,
                              "Waiting for startup delay to start publisher",
                              "To avoid competing against other startup activities we're going to delay for {0} before we start.",
                              BackgroundStartupDelay);
                }

                await Task.Delay(BackgroundStartupDelay).ConfigureAwait(false);
            }

            InterprocessLock backgroundLock = null; //we can't do our normal using trick in this situation.

            try
            {
                //we have two totally different modes:  Either WE'RE the background processor or someone else is.
                //if we are then we move on to start publishing.  If someone else is then we just poll
                //the lock to see if whoever owned it has exited.
                backgroundLock = GetLock(0);
                while ((backgroundLock == null) && (m_StopRequested == false))
                {
                    //we didn't get the lock - so someone else is currently the main background thread.
                    SleepUntilNextCheck(m_MultiprocessLockCheckInterval);
                    backgroundLock = GetLock(0);
                }

                //if we got the lock then we want to go ahead and perform background processing.
                if (backgroundLock != null)
                {
                    //here is where we want to keep looping - it will return every time the subscription changes.
                    await RepositoryPublishLoop().ConfigureAwait(false);

                    //release the lock; we'll get it on the next round.
                    backgroundLock.Dispose();
                    backgroundLock = null;
                }
            }
            catch (Exception ex)
            {
                GC.KeepAlive(ex);
            }
            finally
            {
                if (backgroundLock != null)
                {
                    backgroundLock.Dispose();
                }

                lock (m_SessionPublishThreadLock)
                {
                    //clear the dispatch thread variable since we're about to exit.
                    m_SessionPublishThread = null;

                    //we want to write out that we had a problem and mark that we're failed so we'll get restarted.
                    m_SessionPublishThreadFailed = true;

                    System.Threading.Monitor.PulseAll(m_SessionPublishThreadLock);
                }

                if (!Log.SilentMode)
                {
                    Log.Write(LogMessageSeverity.Information, LogCategory, "Background session publisher thread has stopped", null);
                }
            }
        }
Beispiel #11
0
        /// <summary>
        /// Performs the actual releasing of managed and unmanaged resources.
        /// Most usage should instead call Dispose(), which will call Dispose(true) for you
        /// and will suppress redundant finalization.
        /// </summary>
        /// <param name="releaseManaged">Indicates whether to release managed resources.
        /// This should only be called with true, except from the finalizer which should call Dispose(false).</param>
        private void Dispose(bool releaseManaged)
        {
            if (releaseManaged)
            {
                // Free managed resources here (normal Dispose() stuff, which should itself call Dispose(true))
                // Other objects may be referenced in this case

                lock (m_QueueLock)
                {
                    if (!m_Disposed)
                    {
                        m_Disposed = true; // Make sure we don't do it more than once.

                        // Empty our queue (although it should already be empty!).
                        while (m_WaitQueue.Count > 0)
                        {
                            InterprocessLock lockInstance = m_WaitQueue.Dequeue();
                            //lockInstance.Disposed -= Lock_Disposed; // Suppress the events, don't start new turns!
                            lockInstance.Dispose(); // Tell any threads still waiting that their request has expired.
                        }

                        if (m_CurrentLockTurn == null)
                        {
                            // No thread is currently prepared to do this, so clear them here.
                            if (m_LockRequest != null)
                            {
                                m_LockRequest.Dispose();
                                m_LockRequest = null;
                            }

                            if (m_FileLock != null)
                            {
                                m_FileLock.Dispose();
                                m_FileLock = null;
                            }
                        }

                        // We're not fully disposed until the current lock owner gets disposed so we can release the lock.
                        // But fire the event to tell the RepositoryLockManager that we are no longer a valid proxy.
                        OnDispose();
                    }
                }
            }
            else
            {
                // Free native resources here (alloc's, etc)
                // May be called from within the finalizer, so don't reference other objects here

                // But we need to make sure the file opens get cleaned up, so we will anyway if we have to.... ???
                if (m_LockRequest != null)
                {
                    m_LockRequest.Dispose();
                    m_LockRequest = null;
                }

                if (m_FileLock != null)
                {
                    m_FileLock.Dispose();
                    m_FileLock = null;
                }
            }
        }
Beispiel #12
0
        /// <summary>
        /// Wait for our turn to have the lock (and wait for the lock) up to our time limit
        /// </summary>
        /// <param name="lockRequest"></param>
        /// <returns></returns>
        internal bool AwaitOurTurnOrTimeout(InterprocessLock lockRequest)
        {
            if (lockRequest.IsExpired)
            {
                throw new InvalidOperationException("Can't wait on an expired lock request.");
            }

            if (string.Equals(lockRequest.FullName, m_LockFullFileNamePath, StringComparison.OrdinalIgnoreCase) == false)
            {
                throw new InvalidOperationException("A lock request may not be queued to a proxy for a different full name.");
            }

            if (lockRequest.OwningThread != Thread.CurrentThread)
            {
                throw new InvalidOperationException("A lock request may only be waited on by the thread which created it.");
            }

            lockRequest.OurLockProxy = this; // Mark the request as pending with us.

            // Do NOT clear out current lock owner, this will allow DequeueNextRequest to find one already there, if any.
            bool ourTurn = StartNextTurn(lockRequest); // Gets its own queue lock.

            if (ourTurn == false)
            {
                // It's not our turn yet, we need to wait our turn.  Are we willing to wait?
                if (lockRequest.WaitForLock && lockRequest.WaitTimeout > DateTimeOffset.Now)
                {
                    ourTurn = lockRequest.AwaitTurnOrTimeout();
                }

                // Still not our turn?
                if (ourTurn == false)
                {
                    if (!CommonCentralLogic.SilentMode)
                    {
                        // Who actually has the lock right now?
                        if (m_CurrentLockTurn != null)
                        {
                            Thread currentOwningThread     = m_CurrentLockTurn.OwningThread;
                            int    currentOwningThreadId   = -1;
                            string currentOwningThreadName = "null";
                            if (currentOwningThread != null) // To make sure we can't get a null-ref exception from logging this...
                            {
                                currentOwningThreadId   = currentOwningThread.ManagedThreadId;
                                currentOwningThreadName = currentOwningThread.Name ?? string.Empty;
                            }

                            m_Logger.LogDebug("{0}\r\nA lock request gave up because it is still being held by another thread.\r\n" +
                                              "Lock file: {1}\r\nCurrent holding thread: {2} ({3})",
                                              lockRequest.WaitForLock ? "Lock request timed out" : "Lock request couldn't wait",
                                              m_LockFullFileNamePath, currentOwningThreadId, currentOwningThreadName);
                        }
                        else
                        {
                            m_Logger.LogError("Lock request turn error\r\nA lock request failed to get its turn but the current lock turn is null.  " +
                                              "This probably should not happen.\r\nLock file: {0}\r\n", m_LockFullFileNamePath);
                        }
                    }

                    lockRequest.Dispose(); // Expire the request.
                    return(false);         // Failed to get the lock.  Time to give up.
                }
            }

            // Yay, now it's our turn!  Do we already hold the lock?

            bool validLock;

            if (m_FileLock != null)
            {
                validLock = true; // It's our request's turn and this proxy already holds the lock!
            }
            else
            {
                validLock = TryGetLock(lockRequest); // Can we get the lock?
            }
            // Do we actually have the lock now?
            if (validLock)
            {
                lockRequest.GrantTheLock(lockRequest); // It owns the actual lock itself now.
            }
            else
            {
                if (!CommonCentralLogic.SilentMode)
                {
                    m_Logger.LogTrace("{0}\r\nA lock request gave up because it could not obtain the file lock.  " +
                                      "It is most likely still held by another process.\r\nLock file: {1}",
                                      lockRequest.WaitForLock ? "Lock request timed out" : "Lock request couldn't wait",
                                      m_LockFullFileNamePath);
                }

                lockRequest.Dispose(); // Failed to get the lock.  Expire the request and give up.
            }

            return(validLock);
        }
Beispiel #13
0
        /// <summary>
        /// Attempt to lock the repository with the provided index path.
        /// </summary>
        /// <param name="requester">The object that is requesting the lock (useful for debugging purposes)</param>
        /// <param name="indexPath">The fully qualified path to the directory containing the index file of the repository</param>
        /// <param name="lockName">The name of the lock to get (locks are a combination of index and this name)</param>
        /// <param name="timeoutSeconds">The maximum number of seconds to wait on the lock before giving up.</param>
        /// <param name="deleteOnClose">Whether the lock file should be deleted on close or left around for reuse.</param>
        /// <returns>A Repository Lock object if the lock could be obtained or Null if the lock timed out.</returns>
        public static InterprocessLock Lock(object requester, string indexPath, string lockName, int timeoutSeconds, bool deleteOnClose)
        {
            if (requester == null)
            {
                throw new ArgumentNullException(nameof(requester));
            }

            if (indexPath == null)
            {
                throw new ArgumentNullException(nameof(indexPath));
            }

            if (lockName == null)
            {
                throw new ArgumentNullException(nameof(lockName));
            }

            InterprocessLock candidateLock = new InterprocessLock(requester, indexPath, lockName, timeoutSeconds);

            // Lookup or create the proxy for the requested lock.
            InterprocessLockProxy lockProxy;

            lock (g_Lock)
            {
                if (g_Proxies.TryGetValue(candidateLock.FullName, out lockProxy) == false)
                {
                    // Didn't exist, need to make one.
                    lockProxy = new InterprocessLockProxy(indexPath, lockName, deleteOnClose);

#if DEBUG
                    if (string.Equals(lockProxy.FullName, candidateLock.FullName, StringComparison.OrdinalIgnoreCase) == false)
                    {
                        throw new InvalidOperationException("Proxy generated a different full name than the candidate lock.");
                    }
#endif

                    lockProxy.Disposed += LockProxy_Disposed;
                    g_Proxies.Add(lockProxy.FullName, lockProxy);
                }

                // Does the current thread already hold the lock?  (If it was still waiting on it, we couldn't get here.)
                Thread currentTurnThread = lockProxy.CheckCurrentTurnThread(candidateLock);
                if (Thread.CurrentThread == currentTurnThread && candidateLock.ActualLock != null)
                {
                    return(candidateLock); // It's a secondary lock, so we don't need to queue it or wait.
                }
                // Or is the lock currently held by another thread that we don't want to wait for?
                if (currentTurnThread != null && candidateLock.WaitForLock == false)
                {
                    candidateLock.Dispose(); // We don't want to wait for it, so don't bother queuing an expired request.
                    return(null);            // Just fail out.
                }

                lockProxy.QueueRequest(candidateLock); // Otherwise, queue the request inside the lock to keep the proxy around.
            }

            // Now we have the proxy and our request is queued.  Make sure some thread is trying to get the file lock.
            bool ourTurn = false; // Assume false.
            try
            {
                ourTurn = lockProxy.AwaitOurTurnOrTimeout(candidateLock);
            }
            finally
            {
                if (ourTurn == false)
                {
                    // We have to make sure this gets disposed if we didn't get the lock, even if a ThreadAbortException occurs.
                    candidateLock.Dispose(); // Bummer, we didn't get it.  Probably already disposed, but safe to do again.
                    candidateLock = null;    // Clear it out to report the failure.
                }
            }
            // Otherwise... yay, we got it!

            return(candidateLock);
        }