Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the right pattern for waiting on a file lock to be released?

Tags:

c#

.net

locking

I need to open a file but if it's currently not available I need to wait until it's ready. What's the best approach to take?

SCENARIO

I'm using files as a persistent caching mechanism for application data. This data needs to be read and deserialized often (written only once, and deleted occasionally). I have a cleanup process that runs on a separate thread that determines which files are no longer needed and deletes them. Opening and reading of files may happen concurrently (rarely, but could happen) and I want the process to wait and try to read the data again.

Thanks!

like image 888
Micah Avatar asked Sep 27 '10 22:09

Micah


2 Answers

I'm not a huge fan of the try/catch IOException because:

  1. The reason for the exception is unknown.
  2. I dislike 'expected' exceptions as I often run with break on exception.

You can do this without exceptions by calling CreateFile and returning a stream when/if it finally returns a handle:

public static System.IO.Stream WaitForExclusiveFileAccess(string filePath, int timeout)
{
    IntPtr fHandle;
    int errorCode;
    DateTime start = DateTime.Now;

    while(true)
    {
        fHandle = CreateFile(filePath, EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero,
                             ECreationDisposition.OpenExisting, EFileAttributes.Normal, IntPtr.Zero);

        if (fHandle != IntPtr.Zero && fHandle.ToInt64() != -1L)
            return new System.IO.FileStream(fHandle, System.IO.FileAccess.ReadWrite, true);

        errorCode = Marshal.GetLastWin32Error();

        if (errorCode != ERROR_SHARING_VIOLATION)
            break;
        if (timeout >= 0 && (DateTime.Now - start).TotalMilliseconds > timeout)
            break;
        System.Threading.Thread.Sleep(100);
    }


    throw new System.IO.IOException(new System.ComponentModel.Win32Exception(errorCode).Message, errorCode);
}

#region Win32
const int ERROR_SHARING_VIOLATION = 32;

[Flags]
enum EFileAccess : uint
{
    GenericRead = 0x80000000,
    GenericWrite = 0x40000000
}

[Flags]
enum EFileShare : uint
{
    None = 0x00000000,
}

enum ECreationDisposition : uint
{
    OpenExisting = 3,
}

[Flags]
enum EFileAttributes : uint
{
    Normal = 0x00000080,
}

[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr CreateFile(
   string lpFileName,
   EFileAccess dwDesiredAccess,
   EFileShare dwShareMode,
   IntPtr lpSecurityAttributes,
   ECreationDisposition dwCreationDisposition,
   EFileAttributes dwFlagsAndAttributes,
   IntPtr hTemplateFile);

#endregion
like image 53
csharptest.net Avatar answered Sep 20 '22 11:09

csharptest.net


The more generic version of the csharptest.net's method could look like this (also, used SafeFileHandle and removed exception throwing on timeout, you can get enum values at http://www.pinvoke.net/default.aspx/kernel32.createfile):

    public static FileStream WaitForFileAccess(string filePath, FileMode fileMode, FileAccess access, FileShare share, TimeSpan timeout)
    {
        int errorCode;
        DateTime start = DateTime.Now;

        while (true)
        {
            SafeFileHandle fileHandle = CreateFile(filePath, ConvertFileAccess(access), ConvertFileShare(share), IntPtr.Zero,
                                                   ConvertFileMode(fileMode), EFileAttributes.Normal, IntPtr.Zero);

            if (!fileHandle.IsInvalid)
            {
                return new FileStream(fileHandle, access);
            }

            errorCode = Marshal.GetLastWin32Error();

            if (errorCode != ERROR_SHARING_VIOLATION)
            {
                break;
            }

            if ((DateTime.Now - start) > timeout)
            {
                return null; // timeout isn't an exception
            }

            Thread.Sleep(100);
        }

        throw new IOException(new Win32Exception(errorCode).Message, errorCode);
    }

    private static EFileAccess ConvertFileAccess(FileAccess access)
    {
        return access == FileAccess.ReadWrite ? EFileAccess.GenericRead | EFileAccess.GenericWrite : access == FileAccess.Read ? EFileAccess.GenericRead : EFileAccess.GenericWrite;
    }

    private static EFileShare ConvertFileShare(FileShare share)
    {
        return (EFileShare) ((uint) share);
    }

    private static ECreationDisposition ConvertFileMode(FileMode mode)
    {
        return mode == FileMode.Open ? ECreationDisposition.OpenExisting : mode == FileMode.OpenOrCreate ? ECreationDisposition.OpenAlways : (ECreationDisposition) (uint) mode;
    }

    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern SafeFileHandle CreateFile(
       string lpFileName,
       EFileAccess dwDesiredAccess,
       EFileShare dwShareMode,
       IntPtr lpSecurityAttributes,
       ECreationDisposition dwCreationDisposition,
       EFileAttributes dwFlagsAndAttributes,
       IntPtr hTemplateFile);
like image 21
Gman Avatar answered Sep 24 '22 11:09

Gman