Fun with file locking

If you are developing code that uses distributed synchronization or messaging, you sometimes might need to use files as a locking mechanism.  This can be useful because files are persistent (beyond thread, process, or even power session lifetime), and access to them is synchronized between multiple processes if you select the proper file access and sharing modes. 

C# example of taking a file lock:

using (FileStream lockFile = new FileStream(

            lockPath,

            FileMode.OpenOrCreate,

            FileAccess.ReadWrite,

            FileShare.Delete

       ))

{

    // 1. Read lock information from file

    // 2. If not locked, write the lock information

}

If multiple threads or processes are accessing this lock file, the first process to open the file will lock it, blocking others from accessing it (more on why FileShare.Delete and not  FileShare.None in a moment).

This provides the synchronization necessary for safe concurrent access of the file lock.  Clients using the code should try to open the file in a try/catch loop, until a certain timeout is reached, to provide the blocking behavior for the file lock.

Where it gets a bit more interesting is when you are done with the lock, and want to release it.  

A good way to do this is to delete the file (you could also write the file to indicate that you no longer hold the lock, but who wants to have left over lock files?).

Naturally, you’ll want to first open the file with the same exclusive access as you did when locking it, to insure that you are still the one that holds the lock.  Unfortunately, it turns out that there isn’t a way to delete the file using the file handle that you have already opened.  You have to use the File.Delete(string path) API which calls WIN32 DeleteFile() and re-opens the file in order to delete it, or re-open the file with FileOptions.DeleteOnClose (FILE_FLAG_DELETE_ON_CLOSE).  This is where you get into trouble because you already have the file open under a lock to prevent other writers from taking it.

This is where our FileShare.Delete (FILE_SHARE_DELETE) comes in.  By opening the file with this flag, we are prohibiting any lock taking operations from being performed, but allowing the file to be deleted by someone else.

C# example of releasing the file lock:

using (FileStream lockFile = new FileStream(

            lockPath,

            FileMode.OpenOrCreate,

            FileAccess.ReadWrite,

            FileShare.Delete

       ))

{

    // 1. Read lock information from file

 

    // 2. If locked by us, delete the file:

    File.Delete(lockPath);

}

The file will be deleted as soon as our handle to the file is closed at the end of the using {} scope.

Note that this example is meant as a mechanism for cooperative persistent file locking between multiple threads or processes.  It is not meant as a way to guard against malicious or misbehaving code that wants to access the file, because anyone can break the rules while the file is not locked.

The benefit of this approach is that you can create persistent file locks that do not necessarily go away when an owning process terminates, and do not require you to keep the file exclusively locked for the duration of the lock.  You can also provide lock override or timeout semantics on top of this mechanism that would not be possible with an exclusive lock approach.

You can also download the example file lock library and source code (as is, no guarantees, no limitations on use).

Using this library you can create file locks like this:

using (FileLock l = new FileLock(path, lockId, “mylock”))

{

    // do stuff under the file lock …

} // lock automatically released here

Of course, there are other ways to do inter-process locking on Windows, including global mutexes, that may be more appropriate depending on the situation.

If you work with the file system often, be sure to check out the CreateFile documentation: http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx.  CreateFile is really the swiss army knife of Windows – having a good understanding of the access modes, sharing modes, and file flags can unlock a number of useful possibilities for your code. 

Best,

 

Mike

15 Comments

  1. Anonymous

    I didn’t get why you have to delete the file. Wouldn’t you rather take a lock on the file, release it and let other threads in the process use it for mutex?

  2. Mike Volodarsky

    I chose to delete it primarily to keep leftover lock files from staying around.

    You could write a lock scavenger instead, or delete the file at the end of your "task" if it has a well defined end.  In my scenarios, deleting was is the simplest approach.

    Thanks,

    Mike

  3. Anonymous

    .NET C# 4.0 : Named Arguments – New Extension Method “Zip” Back To Basics: Generational Garbage Collection

  4. Anonymous

    Interesting read. Each post has been a great learning experience for me. I enjoy seeing these nuggets of IIS7 knowledge you drop on us.

  5. Anonymous

    Thanks for this code. Suggestion … you might want to change all the DateTime.Now’s to DateTime.UtcNow to avoid any issues around daylight savings time changes when DateTime.Now can go back and repeat time or leap forward.

    DateTime.Now is best avoided for the most part IMHO.

  6. Anonymous

    DateTime.Now should be replaced with DateTime.UtcNow to avoid issues around daylight savings time changes.

  7. Anonymous

    Seems to me that you should just open the file with create-always and don’t worry about deleting it. the next thread to lock the file will just overwrite it. shared delete is more effort than is necessary.

    • nir_hp

      You can’t use system mutexes between different machine and OSs.

      When you use file lock, you can mutex between processes from different machines which has access to shared path.

  8. nir_hp

    Since your code link is broken, I uploaded my implementation:
    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;

    namespace Gigya.FunctionalTestFramework.Common
    {
    ///
    /// Preform mutex by creating a file and locking it.
    /// This allows to synchronize resources access from different machines which has access to a file in shared path.
    ///
    class MutexByFile : IDisposable
    {
    ///
    /// Lock file to create / use.
    ///
    private readonly string FilePath;

    ///
    /// File stream of the locked file.
    ///
    private FileStream LockFileStream;

    ///
    ///
    ///
    /// Name of the lock file to be created. The file will be deleted when this class will be disposed.
    public MutexByFile(string filePath)
    {
    FilePath = filePath;
    }

    ///
    /// Wait for access. The locked file will be checked for access every 300 miliseconds.
    ///
    /// How much time to wait for mutex before failing.
    ///
    public async Task WaitForAccess(TimeSpan maximumLockWait)
    {
    var cts = new CancellationTokenSource();
    cts.CancelAfter(maximumLockWait);
    var cancellationToken = cts.Token;

    return await Task.Run(
    async () =>
    {
    while (true)
    {
    try
    {
    LockFile();
    return true;
    }
    catch (IOException e)
    {
    if (e.Message.Contains(“being used by another process”))
    try
    {
    await Task.Delay(300, cancellationToken);
    }
    catch (TaskCanceledException)
    {
    return false;
    }

    }
    }
    },
    cancellationToken);
    }

    ///
    /// Lock the file, but allow deleting it while under lock.
    ///
    void LockFile()
    {
    LockFileStream = new FileStream(
    FilePath,
    FileMode.OpenOrCreate,
    FileAccess.ReadWrite,
    FileShare.Delete
    );
    }

    ///
    /// Delete the lock file and release the mutex.
    ///
    public void Dispose()
    {
    if (LockFileStream != null)
    try
    {
    File.Delete(FilePath);
    }
    finally
    {
    LockFileStream.Dispose();
    }

    }
    }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *