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); } }
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); } } }
/// <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; } } }
/// <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); }
/// <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); }