Tuesday, March 24, 2009

Not my code, but worth repeating

Scenario: A piece of software works with a configuration file. This file is shared between multiple installations of this software in a distributed environment. It holds common values and it is very important that these values not get out of sync. What do you do?

One solution is to use this very cool chunk from Koders. And ofcourse, I had to reboot and lost the link. Needless to say, this one ain't mine. I'm not this smart.


using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;


namespace Program
{
///
/// A non-reentrant mutex that is implemented using
/// a lock file, and thus works across processes,
/// sessions, and machines (as long as the underlying
/// FS provides robust r/w locking).
///
/// To use:
///
/// FileLock fLock = new FileLock(@"c:\foo\my.lock");
///
/// using (fLock.Acquire())
/// {
/// // protected operations
/// }
///

internal class LockFile
{


private readonly string filepath;
private readonly DisposeHelper disposeHelper;
private Stream stream;

public LockFile(string filepath)
{
this.filepath = filepath;
this.disposeHelper = new DisposeHelper(this);
}

public IDisposable Acquire()
{
string dir = Path.GetDirectoryName(filepath);

lock (this)
{
while (stream != null)
Monitor.Wait(this);

while (true)
{
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
try
{
Debug.Assert(stream == null, "Stream was not null--programmer error");
stream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None, 8, false);
return disposeHelper;
}
catch (IOException)
{
Thread.Sleep(50);
continue;
// int errorCode = Marshal.GetHRForException(ioe) & 0xFFFF;
// switch (errorCode)
// {
// case 32:
// case 33:
// case 32 | 0x1620:
// case 33 | 0x1620:
// Thread.Sleep(50);
// continue;
// default:
// throw;
// }
}
}
}
}

internal void Release()
{
lock (this)
{
// Doesn't hurt to pulse. Note that waiting threads will not actually
// continue to execute until this critical section is exited.
Monitor.PulseAll(this);

if (stream == null)
throw new InvalidOperationException("Tried to dispose a FileLock that was not owned");
try
{
stream.Close();
try
{
File.Delete(filepath);
} catch(IOException) { /* could fail if already acquired elsewhere */ }
}
finally
{
stream = null;
}
}
}

private class DisposeHelper : IDisposable
{
private readonly LockFile lockFile;

public DisposeHelper(LockFile lockFile)
{
this.lockFile = lockFile;
}

public void Dispose()
{
lockFile.Release();
}
}
}
}

No comments: