using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Umbraco.Core { // http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx // // notes: // - this is NOT a reader/writer lock // - this is NOT a recursive lock // // using a named Semaphore here and not a Mutex because mutexes have thread // affinity which does not work with async situations // // it is important that managed code properly release the Semaphore before // going down else it will maintain the lock - however note that when the // whole process (w3wp.exe) goes down and all handles to the Semaphore have // been closed, the Semaphore system object is destroyed - so in any case // an iisreset should clean up everything // internal class AsyncLock { private readonly SemaphoreSlim _semaphore; private readonly Semaphore _semaphore2; private readonly IDisposable _releaser; private readonly Task _releaserTask; public AsyncLock() : this (null) { } public AsyncLock(string name) { // WaitOne() waits until count > 0 then decrements count // Release() increments count // initial count: the initial count value // maximum count: the max value of count, and then Release() throws if (string.IsNullOrWhiteSpace(name)) { // anonymous semaphore // use one unique releaser, that will not release the semaphore when finalized // because the semaphore is destroyed anyway if the app goes down _semaphore = new SemaphoreSlim(1, 1); // create a local (to the app domain) semaphore _releaser = new SemaphoreSlimReleaser(_semaphore); _releaserTask = Task.FromResult(_releaser); } else { // named semaphore // use dedicated releasers, that will release the semaphore when finalized // because the semaphore is system-wide and we cannot leak counts _semaphore2 = new Semaphore(1, 1, name); // create a system-wide named semaphore } } private IDisposable CreateReleaser() { // for anonymous semaphore, use the unique releaser, else create a new one return _semaphore != null ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) : new NamedSemaphoreReleaser(_semaphore2); } public Task LockAsync() { var wait = _semaphore != null ? _semaphore.WaitAsync() : _semaphore2.WaitOneAsync(); return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public Task LockAsync(int millisecondsTimeout) { var wait = _semaphore != null ? _semaphore.WaitAsync(millisecondsTimeout) : _semaphore2.WaitOneAsync(millisecondsTimeout); return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public IDisposable Lock() { if (_semaphore != null) _semaphore.Wait(); else _semaphore2.WaitOne(); return _releaser ?? CreateReleaser(); // anonymous vs named } public IDisposable Lock(int millisecondsTimeout) { var entered = _semaphore != null ? _semaphore.Wait(millisecondsTimeout) : _semaphore2.WaitOne(millisecondsTimeout); if (entered == false) throw new TimeoutException("Failed to enter the lock within timeout."); return _releaser ?? CreateReleaser(); // anonymous vs named } // note - before making those classes some structs, read // about "impure methods" and mutating readonly structs... private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore _semaphore; private GCHandle _handle; internal NamedSemaphoreReleaser(Semaphore semaphore) { _semaphore = semaphore; _handle = GCHandle.Alloc(_semaphore); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // finalize will not run } private void Dispose(bool disposing) { // critical _handle.Free(); _semaphore.Release(); _semaphore.Dispose(); } // we WANT to release the semaphore because it's a system object, ie a critical // non-managed resource - and if it is not released then noone else can acquire // the lock - so we inherit from CriticalFinalizerObject which means that the // finalizer "should" run in all situations - there is always a chance that it // does not run and the semaphore remains "acquired" but then chances are the // whole process (w3wp.exe...) is going down, at which point the semaphore will // be destroyed by Windows. // however, the semaphore is a managed object, and so when the finalizer runs it // might have been finalized already, and then we get a, ObjectDisposedException // in the finalizer - which is bad. // in order to prevent this we do two things // - use a GCHandler to ensure the semaphore is still there when the finalizer // runs, so we can actually release it // - wrap the finalizer code in a try...catch to make sure it never throws ~NamedSemaphoreReleaser() { try { Dispose(false); } catch { // we do NOT want the finalizer to throw - never ever } } } private class SemaphoreSlimReleaser : IDisposable { private readonly SemaphoreSlim _semaphore; internal SemaphoreSlimReleaser(SemaphoreSlim semaphore) { _semaphore = semaphore; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { // normal _semaphore.Release(); } } ~SemaphoreSlimReleaser() { Dispose(false); } } } }