Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using LockFileEX in C#

Background

I'm trying to implement block file locking in my C# application. The built-in FileStream.Lock method throws an exception if it is unable to acquire the lock.

The underlying LockFile method returns a status code however I'd prefer not to use a spin-lock to wait for the file to be unlocked.

Question

Does anyone have any code snippets in C# showing how to properly construct the OVERLAPPED structure with a wait handle and pass it to LockFileEx and wait for the operation to complete? I am trying to avoid using the Overlapped.Pack methods partially because they're unsafe but mostly because they require an IOCompletionCallback which isn't what I'm trying to achieve.

I have the declarations but the construction & use of the OverLapped structure seems to be a little bit more complicated.

Note: I know I need to manually pin the overlapped structure until the wait completes. My current code looks like:

ManualResetEvent evt = new ManualResetEvent(false);
OVERLAPPED overlapped = new OVERLAPPED();
overlapped.OffsetLow = offsetLow;
overlapped.OffsetHigh = offsetHigh;
overlapped.hEvent = evt.SafeHandle;
GCHandle h = GCHandle.Alloc(overlapped, GCHandleType.Pinned);
int hr = Win32.LockFileEX(_handle, LockFlags.Exclusive, 0, offsetLow, offsetHigh, 
GCHandle.ToIntPtr(h));
if(hr == 0)
{
    int error = Marshal.GetLastWin32Error();
    if(error = Win32.ERROR_IO_PENDING)
    {
        evt.WaitOne();
    }
    else
    {
        //ohpoo
    }
}

Resolution

The code that ended up working as I wanted was:

[StructLayout(LayoutKind.Sequential)]
public struct OVERLAPPED
{
    public uint internalLow;
    public uint internalHigh;
    public uint offsetLow;
    public uint offsetHigh;
    public IntPtr hEvent;
}

[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool LockFileEx(SafeFileHandle handle, uint flags, uint reserved, uint countLow, uint countHigh, ref OVERLAPPED overlapped);

private const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002;

public static void Lock(FileStream stream, ulong offset, ulong count)
{
    uint countLow = (uint)count;
    uint countHigh = (uint)(count >> 32);

    OVERLAPPED overlapped = new OVERLAPPED()
    { 
        internalLow = 0,
        internalHigh = 0,
        offsetLow = (uint)offset,
        offsetHigh = (uint)(offset >> 32),
        hEvent = IntPtr.Zero,
    };

    if (!LockFileEx(stream.SafeFileHandle, LOCKFILE_EXCLUSIVE_LOCK, 0, countLow,
        countHigh, ref overlapped))
    {
        //TODO: throw an exception
    }
}  

This code will block until the exclusive lock on the region can be acquired.

like image 413
JeffreyABecker Avatar asked Nov 23 '09 16:11

JeffreyABecker


1 Answers

The following should be "very close" to a good solution. The only part I really don't like is using reflection to access an mscorlib internal method, but that method does an excellent job converting Win32 error codes to an IOException. Since you already have unsafe code for NativeOverlapped*, permissions aren't an issue.

It could probably be further improved by creating a SafeFileLockHandle or similar to provide a lightweight IDisposable object for unlocking the file derived from CriticalFinalizerObject.

    private const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002;
    private static readonly Action WinIOError;

    static Win32Native()
    {
        BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        var winIOErrorMethod = typeof(string).Assembly.GetType("System.IO.__Error").GetMethod("WinIOError", bindingAttr, null, Type.EmptyTypes, null);
        WinIOError = (Action)Delegate.CreateDelegate(typeof(Action), winIOErrorMethod);
    }

    public static void LockFile(SafeFileHandle handle, bool exclusive, long offset, long length, Action action)
    {
        if (handle == null)
            throw new ArgumentNullException("handle");
        if (handle.IsInvalid)
            throw new ArgumentException("An invalid file handle was specified.", "handle");
        if (offset < 0)
            throw new ArgumentOutOfRangeException("The offset cannot be negative.", "offset");
        if (length < 0)
            throw new ArgumentOutOfRangeException("The length cannot be negative.", "length");
        if (action == null)
            throw new ArgumentNullException("action");

        LockFileUnsafe(handle, exclusive, offset, length, action);
    }

    private static unsafe void LockFileUnsafe(SafeFileHandle handle, bool exclusive, long offset, long length, Action action)
    {
        Overlapped overlapped = new Overlapped();
        overlapped.OffsetHigh = (int)(offset >> 32);
        overlapped.OffsetLow = (int)offset;

        IOCompletionCallback callback =
            (errorCode, numBytes, nativeOverlapped) =>
            {
                try
                {
                    action();
                }
                finally
                {
                    Overlapped.Free(nativeOverlapped);
                }
            };

        NativeOverlapped* native = overlapped.Pack(callback, null);
        uint flags = exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0;
        if (!LockFileEx(handle, flags, 0, (int)length, (int)(length >> 32), native))
        {
            Overlapped.Free(native);
            WinIOError();
        }
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static unsafe extern bool LockFileEx(SafeFileHandle handle, uint flags, uint mustBeZero, int countLow, int countHigh, NativeOverlapped* overlapped);
like image 143
Sam Harwell Avatar answered Nov 04 '22 10:11

Sam Harwell