Files
Umbraco-CMS/src/Umbraco.Core/AsyncLock.cs

185 lines
6.5 KiB
C#
Raw Normal View History

2015-04-13 19:10:56 +02:00
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
//
2015-05-19 20:08:52 +02:00
// 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
//
2015-04-13 19:10:56 +02:00
internal class AsyncLock
{
private readonly SemaphoreSlim _semaphore;
private readonly Semaphore _semaphore2;
private readonly IDisposable _releaser;
private readonly Task<IDisposable> _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);
2015-04-13 19:10:56 +02:00
}
public Task<IDisposable> LockAsync()
{
var wait = _semaphore != null
? _semaphore.WaitAsync()
: _semaphore2.WaitOneAsync();
2015-04-13 19:10:56 +02:00
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<IDisposable> 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);
}
2015-04-13 19:10:56 +02:00
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);
}
}
}
}