6

Earlier today I was debugging something that went a bit like this:

class Foo {

    void AccessCriticalSection() {
        try {
            if (IO.File.Exists("\\path\to\lock.txt")
                throw new Exception();
            System.IO.File.Create("\\path\to\lock.txt");
            criticalSection();          
        } catch (Exception ex) {
            // ignored
        } 
    }

    void CriticalSection() {
        // banana banana banana
        System.IO.File.Delete("\\path\to\lock.txt");
    }

}

Let's not even get into how terrible this is… but it's essentially trying to use a file called lock.txt as its mutex. The operation isn't atomic, other processes are just not getting through the critical section if another process is using it (they're intended to be able to continue after the lock is released if you can believe it), etc. etc. Obviously it needs to be fixed.

How do I properly obtain a lock to synchronize access to the filesystem across multiple processes? The processes are multiple instances of the same process, so they can share some protocol rather than specifically locking the directory (i.e., they can easily use what is the equivalent to all instances of some class locking on something like private final static Object lock = new Object(); to synchronize access to static methods)

Joseph Nields
  • 5,527
  • 2
  • 32
  • 48

1 Answers1

11

You should use a Mutex.

A synchronization primitive that can also be used for interprocess synchronization.

Mutexes are either local to a process or named. To support interprocess synchronization you'll need to use a named mutex.

Named system mutexes are visible throughout the operating system, and can be used to synchronize the activities of processes.


Here's a sample program that demonstrate interprocess synchronization using a shared mutex.

class Program
{
    static void Main(string[] args)
    {
        const string SHARED_MUTEX_NAME = "something";
        int pid = Process.GetCurrentProcess().Id;

        using (Mutex mtx = new Mutex(false, SHARED_MUTEX_NAME))
        {
            while (true)
            {
                Console.WriteLine("Press any key to let process {0} acquire the shared mutex.", pid);
                Console.ReadKey();

                while (!mtx.WaitOne(1000))
                {
                    Console.WriteLine("Process {0} is waiting for the shared mutex...", pid);
                }

                Console.WriteLine("Process {0} has acquired the shared mutex. Press any key to release it.", pid);
                Console.ReadKey();

                mtx.ReleaseMutex();
                Console.WriteLine("Process {0} released the shared mutex.", pid);
            }
        }            
    }
}

Sample program output:

mutex_test

The purple lines show points where control of the shared mutex is transferred between the two processes.


To synchronize access to individual files you should use a mutex name that is based on a normalized path of the protected file.

Here's how you can create a normalized path:

public static string NormalizePath(string path)
{
    return Path.GetFullPath(new Uri(path).LocalPath)
               .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
               .ToUpperInvariant();
}
Community
  • 1
  • 1
Mårten Wikström
  • 11,074
  • 5
  • 47
  • 87
  • 2
    I second that - To be specific - Use Named Mutex for cross process Synchronization. – Vinay Dwivedi May 13 '15 at 04:30
  • 7
    Note, by default named mutexes are user session specific, if you want the mutex to be shared accross multiple concurrent users of the machine you must start the name with `Global\ ` for example `const string SHARED_MUTEX_NAME = "Global\something";` – Scott Chamberlain May 13 '15 at 05:04
  • @ScottChamberlain That only applies to a server that is running *Terminal Services*. – Mårten Wikström May 13 '15 at 05:11
  • @MårtenWikström: that was true in Windows 2000, but Windows XP introduced Fast User Switching, and Windows Vista introduced service isolation. These are both implemented using Terminal Services sessions. – Harry Johnston May 13 '15 at 06:02
  • @HarryJohnston Are you saying that two services running as different user accounts (for example LOCAL SYSTEM and NETWORK SERVICE) would not be able to share a named mutex (unless prefixed with `Global\\`)? That would be an important thing to note in the MSDN docs! It's just saying: *"On a server that is running Terminal Services, a named system mutex can have two levels of visibility..."* – Mårten Wikström May 13 '15 at 06:06
  • 1
    No, but an application run by a logged on user has to use the Global prefix in order to share a named mutex with a service. What page on MSDN are you looking at? The page for CreateMutex, https://msdn.microsoft.com/en-us/library/windows/desktop/ms682411(v=vs.85).aspx points out that Fast User Switching uses Terminal Services sessions, and contains a link to the Kernel Object Namespaces page for more details. – Harry Johnston May 13 '15 at 07:12
  • 1
    @HarryJohnston Thanks! That's an important lesson to learn. I was looking at the class page for Mutex (.NET): https://msdn.microsoft.com/en-us/library/system.threading.mutex(v=vs.110).aspx – Mårten Wikström May 13 '15 at 07:15
  • It's disappointing that the .NET documentation is so inaccurate. I wouldn't have expected it to be. – Harry Johnston May 13 '15 at 07:35
  • I have a process A in server 1, and process B in server 2. How-to do processes synchronization A and B ? – Kiquenet Apr 07 '20 at 08:30