Reputation: 10432
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.
Can I force ReleaseMutex
in TestFixtureTearDown
if it's being executed by a different thread or abandon mutex some other way?
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
}
}
}
Upvotes: 2
Views: 1731
Reputation: 10432
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();
}
}
}
Upvotes: 1
Reputation: 171206
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.
Upvotes: 1