Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1484

CMutexEx Class for Safer & Cooperative Mutex Handling

$
0
0
CMutex: A cooperative, cancellable, cross-process mutex for VB6


TL;DR — A small VB6 class that wraps the Windows mutex APIs correctly, adds a responsive (message-pumped) wait option with cooperative cancel, supports “short-circuit” names to bail early, distinguishes expected vs unexpected acquisition failures, and exposes a bitmask error policy so you control what raises and what doesn’t. It’s designed as a single-use object: create ? acquire once ? inspect ? dispose.

Quote:

Use cases: one-writer/many-readers coordination between processes, preventing concurrent updaters, keeping UI responsive while waiting, or aborting a wait when some other system signal is present.




Features at a glance
  • Global\ / Local\ namespaces (machine-wide or session-local).
  • Blocking or Cancellable wait (message-pumped with DoEvents) — great for keeping UI alive so user can cancel.
  • Short-circuit list: if any named mutex exists, acquisition stops. For example, you could detect a mutex that your installer creates and abort waiting for another long running process and shutdown.
  • Abandoned detection: surfaces WAIT_ABANDONED_0 so you can react accordingly.
  • Bitmask error policy: choose what raises: Usage, AcquireExpected, AcquireUnexpected, None.
  • Single-shot lifecycle (no re-use quirks) and automatic cleanup on terminate.
  • Optional SDDL so cross-user coordination works, or use the built-in default.



Quick start
Code:

Dim mx As New CMutex

' Acquire in the Global\ namespace, Infinite timeout
mx.AcquireGlobal "MyMutex"

If mx.IsAcquired Then
  ' ... critical section ...
ElseIf mx.IsTimedOut Then
  ' handle timeout
ElseIf mx.IsCancelled Then
  ' user cancelled via event
ElseIf mx.IsShortCircuited Then
  ' another named mutex was present, see mx.ShortCircuits
ElseIf mx.IsFailed Then
  Debug.Print "Unexpected failure: "; mx.LastErrorNumber; mx.LastErrorDescription
End If

Set mx = Nothing  ' Mutex released


API surface (selected)

Acquire methods
Code:

' Core
Function Acquire( ByVal p_MutexName As String, _
Optional ByVal p_TimeoutMs As Long = INFINITE, _
Optional ByVal p_WaitBehaviour As e_MutexWaitBehavior = mxwb_Blocking, _
Optional ByVal p_ErrorPolicy As e_MutexErrorPolicy = mxep_Usage, _
Optional ByVal p_ShortCircuitMutexNameOrArray As Variant) As CMutex

' Convenience wrappers
Function AcquireGlobal( ByVal p_MutexName As String, _
Optional ByVal p_TimeoutMs As Long = INFINITE, _
Optional ByVal p_WaitBehaviour As e_MutexWaitBehavior = mxwb_Blocking, _
Optional ByVal p_ErrorPolicy As e_MutexErrorPolicy = mxep_Usage, _
Optional ByVal p_ShortCircuitMutexNameOrArray As Variant) As CMutex

Function AcquireLocal( ByVal p_MutexName As String, _
Optional ByVal p_TimeoutMs As Long = INFINITE, _
Optional ByVal p_WaitBehaviour As e_MutexWaitBehavior = mxwb_Blocking, _
Optional ByVal p_ErrorPolicy As e_MutexErrorPolicy = mxep_Usage, _
Optional ByVal p_ShortCircuitMutexNameOrArray As Variant) As CMutex

Enums
Code:

Enum e_MutexWaitBehavior
mxwb_Blocking = 0 ' WaitForSingleObject only; no cancel
mxwb_Cancellable = -1 ' Message-pumped wait; cooperative cancel
End Enum

Enum e_MutexErrorPolicy ' bitmask
mxep_None = 0
mxep_Usage = 1 ' bad arguments / illegal sequence
mxep_AcquireUnexpected = 2 ' mxs_Failed (system/logic error)
mxep_AcquireExpected = 4 ' mxs_TimedOut / mxs_Cancelled / mxs_Shorted
mxep_AcquireAll = mxep_AcquireUnexpected Or mxep_AcquireExpected
mxep_All = mxep_Usage Or mxep_AcquireAll
End Enum

State & events
Code:

' States you can query after Acquire()
Enum e_MutexState
mxs_Disposed = &H80000000
mxs_Failed = -4
mxs_Shorted = -3
mxs_TimedOut = -2
mxs_Cancelled = -1
mxs_Initializing = 0
mxs_Acquiring = 1
mxs_Acquired = 2
End Enum

' Events
Event Acquiring(ByRef p_Cancel As Boolean) ' fire periodically in cancellable mode
Event Acquired(ByVal p_IsAbandonedByPreviousOwner As Boolean)
Event Failed(ByVal p_IsExpectedFailure As Boolean)


Short-circuits (CMutexSC)

Give CMutex a list of mutex names. If any exists (even if you don’t own it), acquisition is stopped quickly and state is mxs_Shorted. You can inspect which names were found.

Code:

' Provide one or many names (String or String())
mx.Acquire "JobQueueWriter", , mxwb_Cancellable, mxep_AcquireExpected, _
Array("BackupJobRunning", "ReindexJobRunning")

If mx.IsShortCircuited Then
Dim i As Long
For i = 0 To mx.ShortCircuits.FoundCount - 1
Debug.Print "Found: "; mx.ShortCircuits.FoundByIndex(i)
Next
End If


Cancellation model

In mxwb_Cancellable mode, the wait loop uses MsgWaitForMultipleObjects with small slices (~50ms) and raises Acquiring(ByRef Cancel). Set Cancel = True to abort; state becomes mxs_Cancelled. UI stays responsive.

Code:

Private Sub mx_Acquiring(ByRef Cancel As Boolean)
DoEvents ' let UI breathe
Cancel = UserRequestedStop() ' your own flag/button
End Sub


Error policy (bitmask) examples
Code:

mx.AcquireLocal "PrintQueue", 0, mxwb_Blocking, mxep_Usage
' Only usage errors raise. Timeout (0ms) returns IsTimedOut = True.

mx.AcquireGlobal "DB_Write", 10000, mxwb_Cancellable, mxep_AcquireUnexpected
' Only unexpected run-time failures raise. Timeouts/cancel/shorted do not.

mx.ErrorPolicy = mxep_All
' Everywhere: raise usage + all acquisition outcomes.


Design notes
  • Single-use class: call Acquire once per instance. After it completes (success or any failure), consider the instance “spent”.
  • Abandoned ownership: if acquired with WAIT_ABANDONED_0, Acquired(True) is raised so callers can repair state.
  • High-precision timing: QueryPerformanceCounter is cached on first use; fallback logic is included if QPC isn’t available.
  • Security descriptor: SDDL grants FA to owner/admins and SYNCHRONIZE to authenticated users, enabling cross-user coordination without unsafe DACLs.
  • Namespaces are case-sensitive in the Windows object manager. The class emits "Global" / "Local" with proper casing.



Gotchas / best practices
  • Don’t reuse an instance for multiple acquires—by design.
  • If you expose a “Cancel” button, wire it to set a flag the Acquiring handler reads.
  • Prefer AcquireGlobal when coordinating across interactive services/sessions.
  • Avoid passing a backslash in p_MutexName; the class prepends the namespace itself.



Lightly tested so far, feedback and bug reports are very welcome!


Changelog
  • v1.0 — Initial public release. Single-use lifecycle, cancellable waits, short-circuits, abandoned detection, bitmask error policy, Global/Local helpers, SDDL support.
Attached Files

Viewing all articles
Browse latest Browse all 1484

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>