Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interactively using Mutexes (et al) in Powershell

While debugging an application that uses Semaphores for cross-process synchronization, I stumbled upon the idea of using PowerShell to take the place of the "other" process. Doing something like this in PowerShell works fine:

// In C# application:
var sem = new Semaphore(0, 1, "CrossProcSem");
sem.WaitOne();

# In PowerShell session:
[1] C:\Projects $ $sem = New-Object System.Threading.Semaphore(0, 1, "CrossProcSem")
[2] C:\Projects $ $sem.Release()

And I can call WaitOne() and Release() repeatedly on that same instance of a Semaphore, as often as I need to.

But when I try to do the same thing with a Mutex, PowerShell keeps claiming that the mutex was abandoned:

[1] C:\Projects $ $mtx = New-Object System.Threading.Mutex($false, "CrossProcMtx")
[2] C:\Projects $ $mtx.WaitOne()
True
[3] C:\Projects $ $mtx.ReleaseMutex()
[4] C:\Projects $ $mtx.WaitOne()
Exception calling "WaitOne" with "0" argument(s): "The wait completed due to an abandoned mutex."
At line:1 char:13
+ $mtx.WaitOne <<<< ()
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : DotNetMethodException

The error seems to happen any time I call WaitOne() after having acquired the mutex once before, either a previous WaitOne call or asking for it to be initially owned in the constructor:

[5] C:\Projects $ $mtx2 = New-Object System.Threading.Mutex($true)
[6] C:\Projects $ $mtx2.WaitOne()
Exception calling "WaitOne" with "0" argument(s): "The wait completed due to an abandoned mutex."
At line:1 char:14
+ $mtx2.WaitOne <<<< ()
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : DotNetMethodException

[7] C:\Projects $ $mtx3 = New-Object System.Threading.Mutex
[8] C:\Projects $ $mtx3.WaitOne()
True
[9] C:\Projects $ $mtx3.WaitOne()
Exception calling "WaitOne" with "0" argument(s): "The wait completed due to an abandoned mutex."
At line:1 char:14
+ $mtx3.WaitOne <<<< ()
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : DotNetMethodException

Is Powershell doing some wierd thread shenanigans in the background or am I just completely forgetting how Mutexes work?

like image 302
Michael Edenfield Avatar asked Oct 05 '11 16:10

Michael Edenfield


2 Answers

Using this thread as inspiration, built a simple implementation of a process locking mechanism using Mutexes in powershell:

function Wait-OnMutex
{
    param(
    [parameter(Mandatory = $true)][string] $MutexId
    )

    try
    {
        $MutexInstance = New-Object System.Threading.Mutex -ArgumentList 'false', $MutexId

        while (-not $MutexInstance.WaitOne(1000))
        {
            Start-Sleep -m 500;
        }

        return $MutexInstance
    } 
    catch [System.Threading.AbandonedMutexException] 
    {
        $MutexInstance = New-Object System.Threading.Mutex -ArgumentList 'false', $MutexId
        return Wait-OnMutex -MutexId $MutexId
    }

}

##
## main method

$MutexInstance = Wait-OnMutex -MutexId 'SomeMutexId12345'

Write-Host "my turn"
#this is where you do work inside the "lock"
Read-Host 

$MutexInstance.ReleaseMutex()

Hope This helps somebody.

like image 161
MSC Avatar answered Sep 28 '22 05:09

MSC


By default, powershell v2.0 (at the console, not the graphical ISE) uses an MTA threadpool. What this means is that each interactive line is executed on a different thread:

PS> [threading.thread]::CurrentThread.ManagedThreadId
13
PS> [threading.thread]::CurrentThread.ManagedThreadId
10
PS> [threading.thread]::CurrentThread.ManagedThreadId
8
PS> [threading.thread]::CurrentThread.ManagedThreadId
4

However, a non-interactive script will run under a single thread, that is to say, the thread that invoked the command to run it:

PS> $script = {
>> [threading.thread]::CurrentThread.ManagedThreadId
>> [threading.thread]::CurrentThread.ManagedThreadId
>> [threading.thread]::CurrentThread.ManagedThreadId
>> [threading.thread]::CurrentThread.ManagedThreadId
>> }
>>
PS> & $script
16
16
16
16

If you want to run powershell interactively with a single thread, start the shell with the -STA switch. You can do this interactively:

PS> powershell -sta
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS> $host.runspace | select threadoptions, apartmentstate
ThreadOptions     ApartmentState
-------------     --------------
ReuseThread                  STA

As you can see, powershell will use a single-threaded apartment to execute interactive commands. This is usually the desired choice for working with WPF or WinForms, or if you want to play with system-wide mutexes:

PS> $mtx = New-Object System.Threading.Mutex($false, "CrossProcMtx")
PS> $mtx.WaitOne()
True
PS> $mtx.ReleaseMutex()
PS> $mtx.WaitOne()
True

Btw, powershell v3 (shipping with windows 8 and also available downlevel on win 7) uses -STA as the default mode for the console. The graphical powershell ISE always uses -STA, both in v2 and v3.

like image 22
x0n Avatar answered Sep 28 '22 06:09

x0n