user107586
user107586

Reputation: 81

Is creating a mutex the same as waiting on it?

I need a global mutex to ensure only one instance of my application can run at a time.

I found the following code on SO:

using var mutex = MutexAcl.Create(true, mutexId, out bool mutexCreated, securitySettings);
var hasHandle = false;

try
{
    try
    {
        hasHandle = mutex.WaitOne(1000, false);

        if (hasHandle == false)
            LogError("Timeout waiting for exclusive access.");
    }
    catch (AbandonedMutexException)
    {
        hasHandle = true;
    }

    // do work
}
finally
{
    if (hasHandle)
        mutex.ReleaseMutex();
}

but something vexes me:

Why wait on the mutex when you always create it with initiallyOwned = true. Can't we use the mutexCreated variable in such a case?

If I understand it correctly mutexCreated is true if the global mutex did not exist and it was created with that method call and, since initiallyOwned = true, it is owned by the thread and if mutexCreated is false then it already existed and the current thread does not own it, so it's safe to assume another instance is already running.

Am I missing something? Why do we need to wait on the mutex we already have a call to Create for?

Upvotes: 0

Views: 80

Answers (1)

Thomas Weller
Thomas Weller

Reputation: 59207

You are passing true as the first argument (initiallyOwned). Thus you assume that the Mutex is already owned after you got it.

However, the documentation says (emphasis mine):

initiallyOwned Boolean
true to give the calling thread initial ownership of the named system mutex if the named system mutex is created as a result of this call; otherwise, false.

So, you only have ownership if the Mutex was created. If the Mutex was not created, but already existed, you don't have ownership. But you still have the Mutex.

So, if you pass true for initiallyOwned and createdNew was also set to true as a result of the call, then you don't need to wait.

You can not get rid of the WaitOne() call, because this call is the one that may cause the AbandonedMutexException and thus your single-instance app should still start.

Therefore, you could rewrite the code like this:

using var mutex = MutexAcl.Create(true, mutexId, out bool mutexCreated, securitySettings);
var hasHandle = false;

try
{
    try
    {
        if (mutexCreated)       // <-- new code
        {
            hasHandle = true;
        }
        else /* mutex already existed */
        {
            hasHandle = mutex.WaitOne(1000, false);

            if (hasHandle == false)
                LogError("Timeout waiting for exclusive access.");
        }
    }
    catch (AbandonedMutexException)
    {
        hasHandle = true;
    }

    // do work
}
finally
{
    if (hasHandle)
        mutex.ReleaseMutex();
}

But why? It only makes the code longer and harder to understand. If you call WaitOne() in every case, it's clear that you have access to the Mutex. No need of thinking.

Is creating a mutex the same as waiting on it?

No. Creating a Mutex might have included the waiting (see the initiallyOwned parameter), but not necessarily. Also, you can wait on a Mutex without creating it (see the WaitOne() method).

Demo (broken)

Let's use the following code to demonstrate the problem that you might get when you don't use WaitOne():

#pragma warning disable CA1416
using System.Security.AccessControl;
using System.Security.Principal;
var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var allowEveryoneRule = new MutexAccessRule(everyone, MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);

using var mutex = MutexAcl.Create(true, "singleInstanceAppDemo", out bool mutexCreated, securitySettings);

if (!mutexCreated)
{
    Console.WriteLine("This is a single instance app.");
    Console.WriteLine("Press Enter to confirm this.");
    Console.ReadLine();
    return;
}
Console.WriteLine("You are the single user.");
Console.WriteLine("Press Enter when you're done working.");
Console.ReadLine();
mutex.ReleaseMutex();

Now do the following:

  1. Start the app. It will say "You are the single user.". Keep it open.
  2. Start the app another time. It will say "This is a single instance app.". Keep it open.
  3. Close the first instance. Keep the second instance open.
  4. Note: there's now nobody working with your app.
  5. Start the app another time. It will say "This is a single instance app.", although nobody is working with your app.

Demo (working)

#pragma warning disable CA1416
using System.Security.AccessControl;
using System.Security.Principal;
var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var allowEveryoneRule = new MutexAccessRule(everyone, MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);

using var mutex = MutexAcl.Create(true, "singleInstanceAppDemo", out bool mutexCreated, securitySettings);
try
{
    bool success = mutex.WaitOne(1, false);

    if (!success)
    {
        Console.WriteLine("This is a single instance app.");
        Console.WriteLine("Press Enter to confirm this.");
        Console.ReadLine();
        return;
    }

    Console.WriteLine("You are the single user.");
    Console.WriteLine("Press Enter when you're done working.");
    Console.ReadLine();
    mutex.ReleaseMutex();
}
catch (AbandonedMutexException)
{
    Console.WriteLine("Lucky!");
    Console.WriteLine("Press Enter when you're done working.");
    Console.ReadLine();
    mutex.ReleaseMutex();
}
// TODO: use try/finally to release the Mutex.

If you repeat the experiment with this code, the output of step 5 will be "Lucky!".

Upvotes: 3

Related Questions