mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
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:
116
src/Orchard.Tests/FileSystems/LockFile/LockFileManagerTests.cs
Normal file
116
src/Orchard.Tests/FileSystems/LockFile/LockFileManagerTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
55
src/Orchard/FileSystems/LockFile/DefaultLockFileManager.cs
Normal file
55
src/Orchard/FileSystems/LockFile/DefaultLockFileManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Orchard/FileSystems/LockFile/ILockFile.cs
Normal file
8
src/Orchard/FileSystems/LockFile/ILockFile.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Orchard.FileSystems.LockFile
|
||||
{
|
||||
public interface ILockFile : IDisposable {
|
||||
void Release();
|
||||
}
|
||||
}
|
||||
26
src/Orchard/FileSystems/LockFile/ILockFileManager.cs
Normal file
26
src/Orchard/FileSystems/LockFile/ILockFileManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
60
src/Orchard/FileSystems/LockFile/LockFile.cs
Normal file
60
src/Orchard/FileSystems/LockFile/LockFile.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user