views:

685

answers:

2

Okay, some background first. We needed an inter-process reader/writer lock. We decided to use a file and lock the first byte using LockEx and UnlockEx. The class creates a file in the system temp folder when it is created. The file is created with readwrite access and share read|write|delete. We also specify DeleteOnClose so we don't leave loads of temp files laying around. Obviously AcquireReader and AcquireWriter call LockEx with appropriate flags and ReleaseLock calls UnlockEx.
We have tested this class using a small application that you can run several instances of and it works perfectly. The application that uses it has a problem, which we have managed to re-produce in another small test app. In pseudo code it is

Create InterProcessReaderWriter
Dispose InterProcessReaderWriter without acquiring any locks
Launch a child process which takes a reader lock

The first time this runs, it works fine. If you attempt to run it again, while the child process from the first time is still holding the lock, we get an UnauthorisedAccessException when trying to open the file.
This appears to be a permission issue, not a sharing violation but all the processes in this test case are running as the same user. Does anyone here have any ideas?

I have noticed the other question that suggests using a mutex and a semaphore to achive what we want. I might change our implementation, but I would still like to know what is causing this problem.

A: 

It sounds to me like the child process is trying to access the same file twice.

are the temp file names unique?

Scott Herbert
We are trying to access the same file. We are trying to implement something similar to how access works with its .ldb file. But only one lock per file, in out case the first byte.
pipTheGeek
A: 

Why not lock the whole file? rather than the 1st byte. The sample class below has the benefit that it can work across processes on different machines, rather than limiting it to just a single machine.

Here's a simple example using the lockfilehelper class below.

Module Module1
    Sub Main()
        Using lockFile As New LockFileHelper("\\sharedfolder\simplefile.lock")
            If lockFile.LockAcquire(1000) Then
                ' Do your work here.
            Else
                ' Manage timeouts here.
            End If
        End Using
    End Sub
End Module

Here's the code for the Helper class.

Public Class LockFileHelper
    Implements IDisposable
    '-------------------------------------------------------------------------------------------------
    ' We use lock files in various places in the system to provide a simple co-ordination mechanism
    ' between different threads within a process and for sharing access to resources with the same
    ' process running across different machines.
    '-------------------------------------------------------------------------------------------------
    Private _lockFileName As String
    Private _ioStream As IO.FileStream
    Private _acquiredLock As Boolean = False
    Private _wasLocked As Boolean = False
    Public Sub New(ByVal LockFileName As String)
        _lockFileName = LockFileName
    End Sub
    Public ReadOnly Property LockFileName() As String
        Get
            Return _lockFileName
        End Get
    End Property
    Public ReadOnly Property WasLocked() As Boolean
        Get
            Return _wasLocked
        End Get
    End Property
    Public Function Exists() As Boolean
        Return IO.File.Exists(_lockFileName)
    End Function
    Public Function IsLocked() As Boolean
        '-------------------------------------------------------------------------------------------------
        ' If this file already locked?
        '-------------------------------------------------------------------------------------------------
        Dim Result As Boolean = False
        Try
            _ioStream = IO.File.Open(_lockFileName, IO.FileMode.Create, IO.FileAccess.ReadWrite, IO.FileShare.None)
            _ioStream.Close()
        Catch ex As System.IO.IOException
            ' File is in used by another process.
            Result = True
        Catch ex As Exception
            Throw ex
        End Try

        Return Result
    End Function
    Public Sub LockAcquireWithException(ByVal TimeOutMilliseconds As Int32)
        If Not LockAcquire(TimeOutMilliseconds) Then
            Throw New Exception("Timed out trying to acquire a lock on the file " & _lockFileName)
        End If
    End Sub
    Public Function LockAcquire(ByVal TimeOutMilliseconds As Int32) As Boolean
        '-------------------------------------------------------------------------------------------------
        ' See have we already acquired the lock. THis can be useful in situations where we are passing
        ' locks around to various processes and each process may want to be sure it has acquired the lock.
        '-------------------------------------------------------------------------------------------------
        If _acquiredLock Then
            Return _acquiredLock
        End If

        _wasLocked = False
        Dim StartTicks As Int32 = System.Environment.TickCount
        Dim TimedOut As Boolean = False
        If Not IO.Directory.Exists(IO.Path.GetDirectoryName(_lockFileName)) Then
            IO.Directory.CreateDirectory(IO.Path.GetDirectoryName(_lockFileName))
        End If
        Do
            Try
                _ioStream = IO.File.Open(_lockFileName, IO.FileMode.Create, IO.FileAccess.ReadWrite, IO.FileShare.None)
                _acquiredLock = True
            Catch ex As System.IO.IOException
                ' File is in used by another process.
                _wasLocked = True
                Threading.Thread.Sleep(100)
            Catch ex As Exception
                Throw ex
            End Try
            TimedOut = ((System.Environment.TickCount - StartTicks) >= TimeOutMilliseconds)
        Loop Until _acquiredLock OrElse TimedOut
        '-------------------------------------------------------------------------------------------------
        ' Return back the status of the lock acquisition.
        '-------------------------------------------------------------------------------------------------
        Return _acquiredLock
    End Function
    Public Sub LockRelease()
        '-------------------------------------------------------------------------------------------------
        ' Release the lock (if we got it in the first place)
        '-------------------------------------------------------------------------------------------------
        If _acquiredLock Then
            _acquiredLock = False
            If Not IsNothing(_ioStream) Then
                _ioStream.Close()
                _ioStream = Nothing
            End If
        End If
    End Sub
    Private disposedValue As Boolean = False        ' To detect redundant calls
    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                Call LockRelease()
            End If

            ' TODO: free shared unmanaged resources
        End If
        Me.disposedValue = True
    End Sub
#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region
End Class

Kind Regards

Noel

Bigtoe
We originally went for locking a range in the file because it allowed us to use DeleteOnClose. I'm not sure how that would work when you are having to close the file in order to change lock level.
pipTheGeek