2

Is there anything except for Mutex to synchronise two processes in a fault-tolerant fashion? Please bear with me...

There is a process A, it's a bit flaky, it needs to start process B in the background and continue. If process A successfully does its thing, it needs to signal process B to dispose, and moves on (it doesn't terminate and thread is reused). If process A dies due to exception, termination, etc. process B needs to detect it quickly and dispose of itself on its own. Process A is not a "process" rather a library executed by various hosts hence process B can't just wait for process A's name to disappear.

Enter Mutex.

Here process A represented by a test fixture, if successful it'll call TestFixtureTearDown and move on, or test runner might be killed and TestFixtureTearDown is never executed. As with the actual process, TestFixtureTearDown might be called by a different thread to one that ran TestFixtureSetUp and created the mutex, hence ReleaseMutex sometimes throws ApplicationException : Object synchronization method was called from an unsynchronized block of code.

  1. Can I force ReleaseMutex in TestFixtureTearDown if it's being executed by a different thread or abandon mutex some other way?

  2. Is there an alternative to Mutex that I can use for such fault-tolerant "reverse" wait/monitor scenario? Preferably without implementing process A sending heartbeats to process B and process B tracking intervals and timing out? Mutex felt like such an elegant solution except for occasional ApplicationException on asyncs.

.

namespace ClassLibrary1 { public class Class1 { private Mutex _mutex; private Process _process; [TestFixtureSetUp] public void TestFixtureSetUp() { _mutex = new Mutex(true, "foo"); _process = Process.Start("ConsoleApplication1.exe"); } [Test] public void Test1() { /* Do stuff */ } [Test] public void Test2() { /* Do async stuff */ } [TestFixtureTearDown] public void TestFixtureTearDown() { _mutex.ReleaseMutex(); _process.WaitForExit(); } } } 

.

namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var mutex = Mutex.OpenExisting("foo"); // Start doing stuff try { mutex.WaitOne(); } catch (AbandonedMutexException) { } finally { mutex.ReleaseMutex(); } // Finish doing stuff } } } 

2 Answers 2

1

Semaphores do not have thread affinity. You can release a semaphore on a different thread than it was acquired on. Use a semaphore with a count of 1.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, I was thinking about semaphores but couldn't find anything on how to replicate the AbandonedMutexException behaviour, i.e. when the starting process is killed unexpectedly and does not signal completion. It all when back to heartbeats on one side and timeouts on the other. Does it have an abandoned state/event?
I didn't think of that. I don't think semaphores are the answer then. Next idea would be to make the library transmit the PID it runs under to the "watcher" process using any IPC mechanism of choice. You can then wait for process termination.
1

I ended up using a mix of Mutex, Thread and ManualResetEvent. For the googling folk of the future here's a verbose test:

using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; namespace MutexResetEvent.Tests { public class Class1 { private Mutex _mutex; private Thread _thread; private Process _process; private ManualResetEvent _event; [SetUp] public void SetUp() { Console.WriteLine("SetUp: #{0}", Thread.CurrentThread.ManagedThreadId); _event = new ManualResetEvent(false); _thread = new Thread(() => { Console.WriteLine("Thread: #{0}", Thread.CurrentThread.ManagedThreadId); _mutex = new Mutex(true, "MutexResetEvent"); _process = new Process { StartInfo = { FileName = "MutexResetEvent.Worker.exe", //UseShellExecute = false, //RedirectStandardOutput = true } }; //_process.OutputDataReceived += (o, a) => Console.WriteLine(a.Data); _process.Start(); //_process.BeginOutputReadLine(); while (!_event.WaitOne(1000)) Console.WriteLine("Thread: ..."); Console.WriteLine("Thread: #{0}", Thread.CurrentThread.ManagedThreadId); _mutex.ReleaseMutex(); _process.WaitForExit(); }); } [Test] public void Test() { Console.WriteLine("Test: #{0}", Thread.CurrentThread.ManagedThreadId); _thread.Start(); for (var i = 0; i < 3; i++) { Console.WriteLine("Test: ..."); Thread.Sleep(1000); } /* if (Guid.NewGuid().GetHashCode() % 3 == 0) Environment.Exit(1); //*/ } [TearDown] public void TearDown() { Console.WriteLine("TearDown: #{0}", Thread.CurrentThread.ManagedThreadId); Task.Run(() => { Console.WriteLine("Task: #{0}", Thread.CurrentThread.ManagedThreadId); _event.Set(); //_thread.Join(); }).Wait(); for (var i = 0; i < 3; i++) { Console.WriteLine("TearDown: ..."); Thread.Sleep(1000); } } } } 

.

using System; using System.Threading; namespace MutexResetEvent.Worker { class Program { static void Main(string[] args) { Console.WriteLine("Worker: #{0}", Thread.CurrentThread.ManagedThreadId); var mutex = Mutex.OpenExisting("MutexResetEvent"); try { while (!mutex.WaitOne(1000)) Console.WriteLine("Worker: ..."); } catch (AbandonedMutexException) { Console.WriteLine("Worker: AbandonedMutexException"); } Console.WriteLine("Worker: #{0}", Thread.CurrentThread.ManagedThreadId); mutex.ReleaseMutex(); Console.WriteLine("Worker: WOO HOO"); Console.ReadLine(); } } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.