From 57909489cbc23b6cb41fecc7f03b173fe3d806d9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 13 Apr 2015 19:10:56 +0200 Subject: [PATCH] AsyncLock - import --- src/Umbraco.Core/AsyncLock.cs | 198 +++++++++++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 2 files changed, 199 insertions(+) create mode 100644 src/Umbraco.Core/AsyncLock.cs diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs new file mode 100644 index 0000000000..426984ab9f --- /dev/null +++ b/src/Umbraco.Core/AsyncLock.cs @@ -0,0 +1,198 @@ +using System; +using System.Runtime.ConstrainedExecution; +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 + // + 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) + : (IDisposable)new NamedSemaphoreReleaser(_semaphore2); + } + + public Task LockAsync() + { + var wait = _semaphore != null + ? _semaphore.WaitAsync() + : WaitOneAsync(_semaphore2); + + 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; + + internal NamedSemaphoreReleaser(Semaphore semaphore) + { + _semaphore = semaphore; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + // critical + _semaphore.Release(); + } + + // we WANT to release the semaphore because it's a system object + // ie a critical non-managed resource - so we inherit from CriticalFinalizerObject + // which means that the finalizer "should" run in all situations + + // however... that can fail with System.ObjectDisposedException because the + // underlying handle was closed... because we cannot guarantee that the semaphore + // is not gone already... unless we get a GCHandle = GCHandle.Alloc(_semaphore); + // which should keep it around and then we free the handle? + + // so... I'm not sure this is safe really... + + ~NamedSemaphoreReleaser() + { + Dispose(false); + } + } + + 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); + } + } + + // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 + // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html + // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... + // version below should be OK + + private static Task WaitOneAsync(WaitHandle handle) + { + var tcs = new TaskCompletionSource(); + var callbackHandleInitLock = new object(); + lock (callbackHandleInitLock) + { + RegisteredWaitHandle callbackHandle = null; + // ReSharper disable once RedundantAssignment + callbackHandle = ThreadPool.RegisterWaitForSingleObject( + handle, + (state, timedOut) => + { + tcs.SetResult(null); + + // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. + lock (callbackHandleInitLock) + { + // ReSharper disable once PossibleNullReferenceException + // ReSharper disable once AccessToModifiedClosure + callbackHandle.Unregister(null); + } + }, + /*state:*/ null, + /*millisecondsTimeOutInterval:*/ Timeout.Infinite, + /*executeOnlyOnce:*/ true); + } + + return tcs.Task; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5ba9280cca..800842711d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -136,6 +136,7 @@ +