Blame SOURCES/roslyn-57003-mono-named-mutex.patch

b8d0d9
Index: tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Core/Portable/InternalUtilities/PlatformInformation.cs
b8d0d9
===================================================================
b8d0d9
--- tarball.6.0.1-rc2-6.0.100-rc2.orig/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Core/Portable/InternalUtilities/PlatformInformation.cs
b8d0d9
+++ tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Core/Portable/InternalUtilities/PlatformInformation.cs
b8d0d9
@@ -31,5 +31,24 @@ namespace Roslyn.Utilities
b8d0d9
                 }
b8d0d9
             }
b8d0d9
         }
b8d0d9
+        /// <summary>
b8d0d9
+        /// Are we running on .NET 5 or later using the Mono runtime?
b8d0d9
+        /// Will also return true when running on Mono itself; if necessary
b8d0d9
+        /// we can use IsRunningOnMono to distinguish.
b8d0d9
+        /// </summary>
b8d0d9
+        public static bool IsUsingMonoRuntime
b8d0d9
+        {
b8d0d9
+            get
b8d0d9
+            {
b8d0d9
+                try
b8d0d9
+                {
b8d0d9
+                    return !(Type.GetType("Mono.RuntimeStructs", throwOnError: false) is null);
b8d0d9
+                }
b8d0d9
+                catch
b8d0d9
+                {
b8d0d9
+                    return false;
b8d0d9
+                }
b8d0d9
+            }
b8d0d9
+        }
b8d0d9
     }
b8d0d9
 }
b8d0d9
Index: tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/BuildClientTests.cs
b8d0d9
===================================================================
b8d0d9
--- tarball.6.0.1-rc2-6.0.100-rc2.orig/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/BuildClientTests.cs
b8d0d9
+++ tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/BuildClientTests.cs
b8d0d9
@@ -79,7 +79,7 @@ namespace Microsoft.CodeAnalysis.Compile
b8d0d9
                 // to connect. When it fails it should fall back to in-proc
b8d0d9
                 // compilation.
b8d0d9
                 bool holdsMutex;
b8d0d9
-                using (var serverMutex = new Mutex(initiallyOwned: true,
b8d0d9
+                using (var serverMutex = BuildServerConnection.OpenOrCreateMutex(
b8d0d9
                                                    name: BuildServerConnection.GetServerMutexName(_pipeName),
b8d0d9
                                                    createdNew: out holdsMutex))
b8d0d9
                 {
b8d0d9
Index: tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs
b8d0d9
===================================================================
b8d0d9
--- tarball.6.0.1-rc2-6.0.100-rc2.orig/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs
b8d0d9
+++ tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs
b8d0d9
@@ -103,7 +103,7 @@ class Hello
b8d0d9
             var mutexName = BuildServerConnection.GetServerMutexName(pipeName);
b8d0d9
 
b8d0d9
             bool holdsMutex;
b8d0d9
-            using (var mutex = new Mutex(initiallyOwned: true,
b8d0d9
+            using (var mutex = BuildServerConnection.OpenOrCreateMutex(
b8d0d9
                                          name: mutexName,
b8d0d9
                                          createdNew: out holdsMutex))
b8d0d9
             {
b8d0d9
@@ -119,7 +119,7 @@ class Hello
b8d0d9
                 }
b8d0d9
                 finally
b8d0d9
                 {
b8d0d9
-                    mutex.ReleaseMutex();
b8d0d9
+                    mutex.Dispose();
b8d0d9
                 }
b8d0d9
             }
b8d0d9
         }
b8d0d9
Index: tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs
b8d0d9
===================================================================
b8d0d9
--- tarball.6.0.1-rc2-6.0.100-rc2.orig/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs
b8d0d9
+++ tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs
b8d0d9
@@ -304,7 +304,7 @@ End Module")
b8d0d9
             var newTempDir = _tempDirectory.CreateDirectory(new string('a', 100 - _tempDirectory.Path.Length));
b8d0d9
             await ApplyEnvironmentVariables(
b8d0d9
                 new[] { new KeyValuePair<string, string>("TMPDIR", newTempDir.Path) },
b8d0d9
-                async () =>
b8d0d9
+                async () => await Task.Run(async () =>
b8d0d9
             {
b8d0d9
                 using var serverData = await ServerUtil.CreateServer(_logger);
b8d0d9
                 var result = RunCommandLineCompiler(
b8d0d9
@@ -317,7 +317,7 @@ End Module")
b8d0d9
 
b8d0d9
                 var listener = await serverData.Complete();
b8d0d9
                 Assert.Equal(CompletionData.RequestCompleted, listener.CompletionDataList.Single());
b8d0d9
-            });
b8d0d9
+            }));
b8d0d9
         }
b8d0d9
 
b8d0d9
         [Fact]
b8d0d9
Index: tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/VBCSCompilerServerTests.cs
b8d0d9
===================================================================
b8d0d9
--- tarball.6.0.1-rc2-6.0.100-rc2.orig/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/VBCSCompilerServerTests.cs
b8d0d9
+++ tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Server/VBCSCompilerTests/VBCSCompilerServerTests.cs
b8d0d9
@@ -101,7 +101,7 @@ namespace Microsoft.CodeAnalysis.Compile
b8d0d9
 
b8d0d9
                     var thread = new Thread(() =>
b8d0d9
                     {
b8d0d9
-                        using (var mutex = new Mutex(initiallyOwned: true, name: mutexName, createdNew: out created))
b8d0d9
+                        using (var mutex = BuildServerConnection.OpenOrCreateMutex(name: mutexName, createdNew: out created))
b8d0d9
                         using (var stream = NamedPipeUtil.CreateServer(pipeName))
b8d0d9
                         {
b8d0d9
                             readyMre.Set();
b8d0d9
@@ -112,7 +112,7 @@ namespace Microsoft.CodeAnalysis.Compile
b8d0d9
                             stream.Close();
b8d0d9
 
b8d0d9
                             doneMre.WaitOne();
b8d0d9
-                            mutex.ReleaseMutex();
b8d0d9
+                            mutex.Dispose();
b8d0d9
                         }
b8d0d9
                     });
b8d0d9
 
b8d0d9
@@ -153,7 +153,7 @@ namespace Microsoft.CodeAnalysis.Compile
b8d0d9
                     {
b8d0d9
                         using (var stream = NamedPipeUtil.CreateServer(pipeName))
b8d0d9
                         {
b8d0d9
-                            var mutex = new Mutex(initiallyOwned: true, name: mutexName, createdNew: out created);
b8d0d9
+                            var mutex = BuildServerConnection.OpenOrCreateMutex(name: mutexName, createdNew: out created);
b8d0d9
                             readyMre.Set();
b8d0d9
 
b8d0d9
                             stream.WaitForConnection();
b8d0d9
@@ -161,7 +161,6 @@ namespace Microsoft.CodeAnalysis.Compile
b8d0d9
 
b8d0d9
                             // Client is waiting for a response.  Close the mutex now.  Then close the connection 
b8d0d9
                             // so the client gets an error.
b8d0d9
-                            mutex.ReleaseMutex();
b8d0d9
                             mutex.Dispose();
b8d0d9
                             stream.Close();
b8d0d9
 
b8d0d9
Index: tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Shared/BuildServerConnection.cs
b8d0d9
===================================================================
b8d0d9
--- tarball.6.0.1-rc2-6.0.100-rc2.orig/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Shared/BuildServerConnection.cs
b8d0d9
+++ tarball.6.0.1-rc2-6.0.100-rc2/src/roslyn.8e1779e16298415843e85029d8b52a1ae9bb4c30/src/Compilers/Shared/BuildServerConnection.cs
b8d0d9
@@ -543,19 +543,10 @@ namespace Microsoft.CodeAnalysis.Command
b8d0d9
         {
b8d0d9
             try
b8d0d9
             {
b8d0d9
-                if (PlatformInformation.IsRunningOnMono)
b8d0d9
+                if (PlatformInformation.IsUsingMonoRuntime)
b8d0d9
                 {
b8d0d9
-                    IServerMutex? mutex = null;
b8d0d9
-                    bool createdNew = false;
b8d0d9
-                    try
b8d0d9
-                    {
b8d0d9
-                        mutex = new ServerFileMutexPair(mutexName, false, out createdNew);
b8d0d9
-                        return !createdNew;
b8d0d9
-                    }
b8d0d9
-                    finally
b8d0d9
-                    {
b8d0d9
-                        mutex?.Dispose();
b8d0d9
-                    }
b8d0d9
+                    using var mutex = new ServerFileMutex(mutexName);
b8d0d9
+                    return !mutex.CouldLock();
b8d0d9
                 }
b8d0d9
                 else
b8d0d9
                 {
b8d0d9
@@ -572,9 +563,11 @@ namespace Microsoft.CodeAnalysis.Command
b8d0d9
 
b8d0d9
         internal static IServerMutex OpenOrCreateMutex(string name, out bool createdNew)
b8d0d9
         {
b8d0d9
-            if (PlatformInformation.IsRunningOnMono)
b8d0d9
+            if (PlatformInformation.IsUsingMonoRuntime)
b8d0d9
             {
b8d0d9
-                return new ServerFileMutexPair(name, initiallyOwned: true, out createdNew);
b8d0d9
+                var mutex = new ServerFileMutex(name);
b8d0d9
+                createdNew = mutex.TryLock(0);
b8d0d9
+                return mutex;
b8d0d9
             }
b8d0d9
             else
b8d0d9
             {
b8d0d9
@@ -648,19 +641,22 @@ namespace Microsoft.CodeAnalysis.Command
b8d0d9
     }
b8d0d9
 
b8d0d9
     /// <summary>
b8d0d9
-    /// An interprocess mutex abstraction based on OS advisory locking (FileStream.Lock/Unlock).
b8d0d9
+    /// An interprocess mutex abstraction based on file sharing permission (FileShare.None).
b8d0d9
     /// If multiple processes running as the same user create FileMutex instances with the same name,
b8d0d9
     ///  those instances will all point to the same file somewhere in a selected temporary directory.
b8d0d9
-    /// The TryLock method can be used to attempt to acquire the mutex, with Unlock or Dispose used to release.
b8d0d9
+    /// The TryLock method can be used to attempt to acquire the mutex, with Dispose used to release.
b8d0d9
+    /// The CouldLock method can be used to check whether an attempt to acquire the mutex would have
b8d0d9
+    ///  succeeded at the current time, without actually acquiring it.
b8d0d9
     /// Unlike Win32 named mutexes, there is no mechanism for detecting an abandoned mutex. The file
b8d0d9
     ///  will simply revert to being unlocked but remain where it is.
b8d0d9
     /// </summary>
b8d0d9
-    internal sealed class FileMutex : IDisposable
b8d0d9
+    internal sealed class ServerFileMutex : IServerMutex
b8d0d9
     {
b8d0d9
-        public readonly FileStream Stream;
b8d0d9
+        public FileStream? Stream;
b8d0d9
         public readonly string FilePath;
b8d0d9
+        public readonly string GuardPath;
b8d0d9
 
b8d0d9
-        public bool IsLocked { get; private set; }
b8d0d9
+        public bool IsDisposed { get; private set; }
b8d0d9
 
b8d0d9
         internal static string GetMutexDirectory()
b8d0d9
         {
b8d0d9
@@ -670,61 +666,176 @@ namespace Microsoft.CodeAnalysis.Command
b8d0d9
             return result;
b8d0d9
         }
b8d0d9
 
b8d0d9
-        public FileMutex(string name)
b8d0d9
+        public ServerFileMutex(string name)
b8d0d9
         {
b8d0d9
-            FilePath = Path.Combine(GetMutexDirectory(), name);
b8d0d9
-            Stream = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
b8d0d9
+            var mutexDirectory = GetMutexDirectory();
b8d0d9
+            FilePath = Path.Combine(mutexDirectory, name);
b8d0d9
+            GuardPath = Path.Combine(mutexDirectory, ".guard");
b8d0d9
         }
b8d0d9
 
b8d0d9
-        public bool TryLock(int timeoutMs)
b8d0d9
+        /// <summary>
b8d0d9
+        /// Acquire the guard by opening the guard file with FileShare.None.  The guard must only ever
b8d0d9
+        /// be held for very brief amounts of time, so we can simply spin until it is acquired.  The
b8d0d9
+        /// guard must be released by disposing the FileStream returned from this routine.  Note the
b8d0d9
+        /// guard file is never deleted; this is a leak, but only of a single file.
b8d0d9
+        /// </summary>
b8d0d9
+        internal FileStream LockGuard()
b8d0d9
         {
b8d0d9
-            if (IsLocked)
b8d0d9
-                throw new InvalidOperationException("Lock already held");
b8d0d9
-
b8d0d9
-            var sw = Stopwatch.StartNew();
b8d0d9
-            do
b8d0d9
+            // We should be able to acquire the guard quickly.  Limit the number of retries anyway
b8d0d9
+            // by some arbitrary bound to avoid getting hung up in a possibly infinite loop.
b8d0d9
+            for (var i = 0; i < 100; i++)
b8d0d9
             {
b8d0d9
                 try
b8d0d9
                 {
b8d0d9
-                    Stream.Lock(0, 0);
b8d0d9
-                    IsLocked = true;
b8d0d9
-                    return true;
b8d0d9
+                    return new FileStream(GuardPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
b8d0d9
                 }
b8d0d9
                 catch (IOException)
b8d0d9
                 {
b8d0d9
-                    // Lock currently held by someone else.
b8d0d9
+                    // Guard currently held by someone else.
b8d0d9
                     // We want to sleep for a short period of time to ensure that other processes
b8d0d9
                     //  have an opportunity to finish their work and relinquish the lock.
b8d0d9
                     // Spinning here (via Yield) would work but risks creating a priority
b8d0d9
                     //  inversion if the lock is held by a lower-priority process.
b8d0d9
                     Thread.Sleep(1);
b8d0d9
                 }
b8d0d9
+            }
b8d0d9
+            // Handle unexpected failure to acquire guard as error.
b8d0d9
+            throw new InvalidOperationException("Unable to acquire guard");
b8d0d9
+        }
b8d0d9
+
b8d0d9
+        /// <summary>
b8d0d9
+        /// Attempt to acquire the lock by opening the lock file with FileShare.None.  Sets "Stream"
b8d0d9
+        /// and returns true if successful, returns false if the lock is already held by another
b8d0d9
+        /// thread or process.  Guard must be held when calling this routine.
b8d0d9
+        /// </summary>
b8d0d9
+        internal bool TryLockFile()
b8d0d9
+        {
b8d0d9
+            Debug.Assert(Stream is null);
b8d0d9
+            FileStream? stream = null;
b8d0d9
+            try
b8d0d9
+            {
b8d0d9
+                stream = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
b8d0d9
+                // On some targets, the file locking used to implement FileShare.None may not be
b8d0d9
+                // atomic with opening/creating the file.   This creates a race window when another
b8d0d9
+                // thread holds the lock and is just about to unlock: we may be able to open the
b8d0d9
+                // file here, then the other thread unlocks and deletes the file, and then we
b8d0d9
+                // acquire the lock on our file handle - but the actual file is already deleted.
b8d0d9
+                // To close this race, we verify that the file does in fact still exist now that
b8d0d9
+                // we have successfull acquired the locked FileStream.   (Note that this check is
b8d0d9
+                // safe because we cannot race with an other attempt to create the file since we
b8d0d9
+                // hold the guard, and after the FileStream constructor returned we can no race
b8d0d9
+                // with file deletion because we hold the lock.)
b8d0d9
+                if (!File.Exists(FilePath))
b8d0d9
+                {
b8d0d9
+                    // To simplify the logic, we treat this case as "unable to acquire the lock"
b8d0d9
+                    // because it we caught another process while it owned the lock and was just
b8d0d9
+                    // giving it up.  If the caller retries, we'll likely acquire the lock then.
b8d0d9
+                    stream.Dispose();
b8d0d9
+                    return false;
b8d0d9
+                }
b8d0d9
+            }
b8d0d9
+            catch (Exception)
b8d0d9
+            {
b8d0d9
+                stream?.Dispose();
b8d0d9
+                return false;
b8d0d9
+            }
b8d0d9
+            Stream = stream;
b8d0d9
+            return true;
b8d0d9
+        }
b8d0d9
+
b8d0d9
+        /// <summary>
b8d0d9
+        /// Release the lock by deleting the lock file and disposing "Stream".
b8d0d9
+        /// </summary>
b8d0d9
+        internal void UnlockFile()
b8d0d9
+        {
b8d0d9
+            Debug.Assert(Stream is not null);
b8d0d9
+            try
b8d0d9
+            {
b8d0d9
+                // Delete the lock file while the stream is not yet disposed
b8d0d9
+                // and we therefore still hold the FileShare.None exclusion.
b8d0d9
+                // There may still be a race with another thread attempting a
b8d0d9
+                // TryLockFile in parallel, but that is safely handled there.
b8d0d9
+                File.Delete(FilePath);
b8d0d9
+            }
b8d0d9
+            finally
b8d0d9
+            {
b8d0d9
+                Stream.Dispose();
b8d0d9
+                Stream = null;
b8d0d9
+            }
b8d0d9
+        }
b8d0d9
+
b8d0d9
+        public bool TryLock(int timeoutMs)
b8d0d9
+        {
b8d0d9
+            if (IsDisposed)
b8d0d9
+                throw new ObjectDisposedException("Mutex");
b8d0d9
+            if (Stream is not null)
b8d0d9
+                throw new InvalidOperationException("Lock already held");
b8d0d9
+
b8d0d9
+            var sw = Stopwatch.StartNew();
b8d0d9
+            do
b8d0d9
+            {
b8d0d9
+                try
b8d0d9
+                {
b8d0d9
+                    // Attempt to acquire lock while holding guard.
b8d0d9
+                    using var guard = LockGuard();
b8d0d9
+                    if (TryLockFile())
b8d0d9
+                        return true;
b8d0d9
+                }
b8d0d9
                 catch (Exception)
b8d0d9
                 {
b8d0d9
-                    // Something else went wrong.
b8d0d9
                     return false;
b8d0d9
                 }
b8d0d9
+
b8d0d9
+                // See comment in LockGuard.
b8d0d9
+                Thread.Sleep(1);
b8d0d9
             } while (sw.ElapsedMilliseconds < timeoutMs);
b8d0d9
 
b8d0d9
             return false;
b8d0d9
         }
b8d0d9
 
b8d0d9
-        public void Unlock()
b8d0d9
+        public bool CouldLock()
b8d0d9
         {
b8d0d9
-            if (!IsLocked)
b8d0d9
-                return;
b8d0d9
-            Stream.Unlock(0, 0);
b8d0d9
-            IsLocked = false;
b8d0d9
+            if (IsDisposed)
b8d0d9
+                return false;
b8d0d9
+            if (Stream is not null)
b8d0d9
+                return false;
b8d0d9
+
b8d0d9
+            try
b8d0d9
+            {
b8d0d9
+                // Attempt to acquire lock while holding guard, and if successful
b8d0d9
+                // immediately unlock again while still holding guard.  This ensures
b8d0d9
+                // no other thread will spuriously observe the lock as held due to
b8d0d9
+                // the lock attempt here.
b8d0d9
+                using var guard = LockGuard();
b8d0d9
+                if (TryLockFile())
b8d0d9
+                {
b8d0d9
+                    UnlockFile();
b8d0d9
+                    return true;
b8d0d9
+                }
b8d0d9
+            }
b8d0d9
+            catch (Exception)
b8d0d9
+            {
b8d0d9
+                return false;
b8d0d9
+            }
b8d0d9
+
b8d0d9
+            return false;
b8d0d9
         }
b8d0d9
 
b8d0d9
         public void Dispose()
b8d0d9
         {
b8d0d9
-            var wasLocked = IsLocked;
b8d0d9
-            if (wasLocked)
b8d0d9
-                Unlock();
b8d0d9
-            Stream.Dispose();
b8d0d9
-            // We do not delete the lock file here because there is no reliable way to perform a
b8d0d9
-            //  'delete if no one has the file open' operation atomically on *nix. This is a leak.
b8d0d9
+            if (IsDisposed)
b8d0d9
+                return;
b8d0d9
+            IsDisposed = true;
b8d0d9
+            if (Stream is not null)
b8d0d9
+            {
b8d0d9
+                try
b8d0d9
+                {
b8d0d9
+                    UnlockFile();
b8d0d9
+                }
b8d0d9
+                catch (Exception)
b8d0d9
+                {
b8d0d9
+                }
b8d0d9
+            }
b8d0d9
         }
b8d0d9
     }
b8d0d9
 
b8d0d9
@@ -792,56 +903,4 @@ namespace Microsoft.CodeAnalysis.Command
b8d0d9
             }
b8d0d9
         }
b8d0d9
     }
b8d0d9
-
b8d0d9
-    /// <summary>
b8d0d9
-    /// Approximates a named mutex with 'locked', 'unlocked' and 'abandoned' states.
b8d0d9
-    /// There is no reliable way to detect whether a mutex has been abandoned on some target platforms,
b8d0d9
-    ///  so we use the AliveMutex to manually track whether the creator of a mutex is still running,
b8d0d9
-    ///  while the HeldMutex represents the actual lock state of the mutex.
b8d0d9
-    /// </summary>
b8d0d9
-    internal sealed class ServerFileMutexPair : IServerMutex
b8d0d9
-    {
b8d0d9
-        public readonly FileMutex AliveMutex;
b8d0d9
-        public readonly FileMutex HeldMutex;
b8d0d9
-
b8d0d9
-        public bool IsDisposed { get; private set; }
b8d0d9
-
b8d0d9
-        public ServerFileMutexPair(string mutexName, bool initiallyOwned, out bool createdNew)
b8d0d9
-        {
b8d0d9
-            AliveMutex = new FileMutex(mutexName + "-alive");
b8d0d9
-            HeldMutex = new FileMutex(mutexName + "-held");
b8d0d9
-            createdNew = AliveMutex.TryLock(0);
b8d0d9
-            if (initiallyOwned && createdNew)
b8d0d9
-            {
b8d0d9
-                if (!TryLock(0))
b8d0d9
-                    throw new Exception("Failed to lock mutex after creating it");
b8d0d9
-            }
b8d0d9
-        }
b8d0d9
-
b8d0d9
-        public bool TryLock(int timeoutMs)
b8d0d9
-        {
b8d0d9
-            if (IsDisposed)
b8d0d9
-                throw new ObjectDisposedException("Mutex");
b8d0d9
-            return HeldMutex.TryLock(timeoutMs);
b8d0d9
-        }
b8d0d9
-
b8d0d9
-        public void Dispose()
b8d0d9
-        {
b8d0d9
-            if (IsDisposed)
b8d0d9
-                return;
b8d0d9
-            IsDisposed = true;
b8d0d9
-
b8d0d9
-            try
b8d0d9
-            {
b8d0d9
-                HeldMutex.Unlock();
b8d0d9
-                AliveMutex.Unlock();
b8d0d9
-            }
b8d0d9
-            finally
b8d0d9
-            {
b8d0d9
-                AliveMutex.Dispose();
b8d0d9
-                HeldMutex.Dispose();
b8d0d9
-            }
b8d0d9
-        }
b8d0d9
-    }
b8d0d9
-
b8d0d9
 }