Using Lock files for simple interprocess co-ordination mechanism
Hi folks, once I published this article I got a number of response stating this was very bad practice for web based applications, and I fully agree and this was never my intent. We currently only use this method in Windows Services that are running inside the firewall and I would never expect this method to be used in any web-sites or web-services for what i would consider very obvious reasons.
When developing robust reliable services you often need the ability to run multiple instances of those services on the same or different servers for reliability or scalability reasons. When managing processes outside of a database you may need a mechanism to block access to a resource (say an email in-box) while another instance of your service is working on that resource.
To manage such cases we often use a mechanism of accessing a file using an exclusive lock on it, and if that lock cannot be acquired then we know something else is working on that resource.
Code example.
Public Class InBoxReader
Public Sub Main()
Dim emailInBox As String = "myAutoInbox"
Using lockHelper As New LockFileHelper(String.Format("\SomeSharedLocation\{0}.lock", emailInBox))
If lockHelper.LockAcquire(100) Then
' O.k have lock now do processing on the InBox.
' The lock is automatically released in the Dispose method of the class.
End If
End Using
End Sub
End Class
''' <summary>
''' Lock file helper.
''' </summary>
''' <remarks>
'''-------------------------------------------------------------------------------------------------
''' 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 on different machines.
'''-------------------------------------------------------------------------------------------------
''' </remarks>
Public Class LockFileHelper
Implements IDisposable
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
''' <summary>
''' Simple check to see if the file is already locked.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function IsLocked() As Boolean
Dim isFileLocked As Boolean = False
Try
If Exists() Then
_ioStream = IO.File.Open(_lockFileName, IO.FileMode.Create, IO.FileAccess.ReadWrite, IO.FileShare.None)
_ioStream.Close()
End If
Catch ex As System.IO.IOException
' File is in used by another process.
isFileLocked = True
Catch ex As Exception
' Any other type of exception, re throw as we don't know what to do with it.
Throw
End Try
Return isFileLocked
End Function
''' <summary>
''' Attempt to acquire a lock within the given timeout, but throw an exception if we can't acquire it.
''' </summary>
''' <param name="TimeOutMilliseconds"></param>
''' <remarks></remarks>
Public Sub LockAcquireWithException(ByVal TimeOutMilliseconds As Int32)
If Not LockAcquire(TimeOutMilliseconds) Then
Throw New Exception(String.Format("Timed out trying to acquire a lock on the file {0}", _lockFileName))
End If
End Sub
''' <summary>
''' Attempt to acquire a lock within the given timeout.
''' </summary>
''' <param name="TimeOutMilliseconds"></param>
''' <returns>
''' True - If an exclusive lock was successfully acquired on the file
''' False - If could not get an exclusive lock on the file
''' </returns>
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
''' <summary>
''' Release the lock on the file (If we have a lock that is.)
''' </summary>
''' <remarks></remarks>
Public Sub LockRelease()
If _acquiredLock Then
_acquiredLock = False
If Not IsNothing(_ioStream) Then
_ioStream.Close()
_ioStream = Nothing
End If
End If
End Sub
#Region " IDisposable Support "
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
' 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