Adding a new file locking service

- ILockFileManager to create and check lock files
- ILockFile represent an acquired lock file
- Lock files expire after 10 minutes by default
- Lock files are created in ~/App_Data
- Default implementation provided (as it is in Orchard.Framework)

--HG--
branch : indexing
This commit is contained in:
Sebastien Ros
2011-03-01 22:51:09 -08:00
parent 9bf5d06068
commit 919585be1a
8 changed files with 272 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
using System.IO;
using System.Linq;
using NUnit.Framework;
using Orchard.FileSystems.AppData;
using Orchard.FileSystems.LockFile;
using Orchard.Tests.Stubs;
namespace Orchard.Tests.FileSystems.LockFile {
[TestFixture]
public class LockFileManagerTests {
private string _tempFolder;
private IAppDataFolder _appDataFolder;
private ILockFileManager _lockFileManager;
private StubClock _clock;
public class StubAppDataFolderRoot : IAppDataFolderRoot {
public string RootPath { get; set; }
public string RootFolder { get; set; }
}
public static IAppDataFolder CreateAppDataFolder(string tempFolder) {
var folderRoot = new StubAppDataFolderRoot {RootPath = "~/App_Data", RootFolder = tempFolder};
var monitor = new StubVirtualPathMonitor();
return new AppDataFolder(folderRoot, monitor);
}
[SetUp]
public void Init() {
_tempFolder = Path.GetTempFileName();
File.Delete(_tempFolder);
_appDataFolder = CreateAppDataFolder(_tempFolder);
_clock = new StubClock();
_lockFileManager = new DefaultLockFileManager(_appDataFolder, _clock);
}
[TearDown]
public void Term() {
Directory.Delete(_tempFolder, true);
}
[Test]
public void LockShouldBeGrantedWhenDoesNotExist() {
ILockFile lockFile = null;
var granted = _lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
Assert.That(granted, Is.True);
Assert.That(lockFile, Is.Not.Null);
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.True);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
}
[Test]
public void ExistingLockFileShouldPreventGrants() {
ILockFile lockFile = null;
_lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
Assert.That(_lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile), Is.False);
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.True);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
}
[Test]
public void ReleasingALockShouldAllowGranting() {
ILockFile lockFile = null;
_lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
using (lockFile) {
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.True);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
}
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.False);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(0));
}
[Test]
public void ReleasingAReleasedLockShouldWork() {
ILockFile lockFile = null;
_lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.True);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
lockFile.Release();
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.False);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(0));
lockFile.Release();
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.False);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(0));
}
[Test]
public void ExpiredLockShouldBeAvailable() {
ILockFile lockFile = null;
_lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
_clock.Advance(DefaultLockFileManager.Expiration);
Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.False);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
}
[Test]
public void ShouldGrantExpiredLock() {
ILockFile lockFile = null;
_lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
_clock.Advance(DefaultLockFileManager.Expiration);
var granted = _lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);
Assert.That(granted, Is.True);
Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
}
}
}

View File

@@ -245,6 +245,7 @@
<Compile Include="Environment\OrchardStarterTests.cs" />
<Compile Include="Environment\ShellBuilders\DefaultShellContainerFactoryTests.cs" />
<Compile Include="Environment\ShellBuilders\DefaultShellContextFactoryTests.cs" />
<Compile Include="FileSystems\LockFile\LockFileManagerTests.cs" />
<Compile Include="FileSystems\Dependencies\AssemblyProbingFolderTests.cs" />
<Compile Include="FileSystems\Dependencies\DependenciesFolderTests.cs" />
<Compile Include="Localization\CultureManagerTests.cs" />

View File

@@ -18,6 +18,7 @@ using Orchard.Environment.Descriptor;
using Orchard.Events;
using Orchard.FileSystems.AppData;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.LockFile;
using Orchard.FileSystems.VirtualPath;
using Orchard.FileSystems.WebSite;
using Orchard.Logging;
@@ -55,6 +56,7 @@ namespace Orchard.Environment {
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);
RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder);
RegisterVolatileProvider<DefaultLockFileManager, ILockFileManager>(builder);
RegisterVolatileProvider<Clock, IClock>(builder);
RegisterVolatileProvider<DefaultDependenciesFolder, IDependenciesFolder>(builder);
RegisterVolatileProvider<DefaultAssemblyProbingFolder, IAssemblyProbingFolder>(builder);

View File

@@ -0,0 +1,55 @@
using System;
using Orchard.FileSystems.AppData;
using Orchard.Services;
namespace Orchard.FileSystems.LockFile {
public class DefaultLockFileManager : ILockFileManager {
private readonly IAppDataFolder _appDataFolder;
private readonly IClock _clock;
public static TimeSpan Expiration { get; private set; }
public DefaultLockFileManager(IAppDataFolder appDataFolder, IClock clock) {
_appDataFolder = appDataFolder;
_clock = clock;
Expiration = TimeSpan.FromMinutes(10);
}
public bool TryAcquireLock(string path, ref ILockFile lockFile) {
try {
if(IsLocked(path)) {
return false;
}
lockFile = new LockFile(_appDataFolder, path, _clock.UtcNow.ToString());
return true;
}
catch {
// an error occured while reading/creating the lock file
return false;
}
}
public bool IsLocked(string path) {
try {
if (_appDataFolder.FileExists(path)) {
var content = _appDataFolder.ReadFile(path);
DateTime creationUtc;
if (DateTime.TryParse(content, out creationUtc)) {
// if expired the file is not removed
// it should be automatically as there is a finalizer in LockFile
// or the next taker can do it, unless it also fails, again
return creationUtc.Add(Expiration) > _clock.UtcNow;
}
}
}
catch {
// an error occured while reading the file
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,8 @@
using System;
namespace Orchard.FileSystems.LockFile
{
public interface ILockFile : IDisposable {
void Release();
}
}

View File

@@ -0,0 +1,26 @@
using Orchard.Caching;
namespace Orchard.FileSystems.LockFile {
/// <summary>
/// Abstraction for lock files creation.
/// </summary>
/// <remarks>
/// All virtual paths passed in or returned are relative to "~/App_Data".
/// </remarks>
public interface ILockFileManager : IVolatileProvider {
/// <summary>
/// Attempts to acquire an exclusive lock file.
/// </summary>
/// <param name="path">The filename of the lock file to create.</param>
/// <param name="lockFile">A reference to the lock file object if the lock is granted.</param>
/// <returns><c>true</c> if the lock is granted; otherwise, <c>false</c>.</returns>
bool TryAcquireLock(string path, ref ILockFile lockFile);
/// <summary>
/// Wether a lock file is already existing.
/// </summary>
/// <param name="path">The filename of the lock file to test.</param>
/// <returns><c>true</c> if the lock file exists; otherwise, <c>false</c>.</returns>
bool IsLocked(string path);
}
}

View File

@@ -0,0 +1,60 @@
using System;
using Orchard.FileSystems.AppData;
namespace Orchard.FileSystems.LockFile {
/// <summary>
/// Represents a Lock File acquire on the file system
/// </summary>
public class LockFile : ILockFile {
private readonly IAppDataFolder _appDataFolder;
private readonly string _path;
private readonly string _content;
private bool _released;
public LockFile(IAppDataFolder appDataFolder, string path, string content) {
_appDataFolder = appDataFolder;
_path = path;
_content = content;
// create the physical lock file
_appDataFolder.CreateFile(path, content);
}
public void Dispose() {
// dispose both managed and unmanaged resources
Dispose(true);
// don't call the finalizer if dispose is called
GC.SuppressFinalize(this);
}
public void Release() {
Dispose(true);
}
protected virtual void Dispose(bool disposing) {
if(disposing) {
// release managed code here
// nothing right now, just a placeholder to preserve the pattern
}
if (_released || !_appDataFolder.FileExists(_path)) {
// nothing to do, night happen if re-granted, and already released
return;
}
_released = true;
// check it has not been granted in the meantime
var current = _appDataFolder.ReadFile(_path);
if (current == _content) {
_appDataFolder.DeleteFile(_path);
}
}
~LockFile() {
// dispose unmanaged resources (file)
Dispose(false);
}
}
}

View File

@@ -183,6 +183,10 @@
<Compile Include="Environment\WorkContextImplementation.cs" />
<Compile Include="Environment\WorkContextModule.cs" />
<Compile Include="Environment\WorkContextProperty.cs" />
<Compile Include="FileSystems\LockFile\ILockFile.cs" />
<Compile Include="FileSystems\LockFile\ILockFileManager.cs" />
<Compile Include="FileSystems\LockFile\LockFile.cs" />
<Compile Include="FileSystems\LockFile\DefaultLockFileManager.cs" />
<Compile Include="FileSystems\Media\FileSystemStorageProvider.cs" />
<Compile Include="Localization\Services\CurrentCultureWorkContext.cs" />
<Compile Include="Localization\Services\DefaultLocalizedStringManager.cs" />