I recently had the task of debugging a piece of C# that had been translated from C++ by a colleague. All I was told was “it works, most of the time”. I checked out the code from Mercurial, built the project, and executed the binary on my local computer. Although I could replicate the bug on a production system, I could not replicate it on my local machine – it was like the bug was disappearing while I attempted to study it (something I have since found out is known as a Heisenbug).
I include inline below the cut-down logic.
using System; using System.Threading; using System.Threading.Tasks; class Program { static void Main(string[] args) { const int SEMAPHORECOUNT = 8; const int SEMAPHORECOUNTMAXTRIES = SEMAPHORECOUNT + 2; const string SEMAPHORENAME = @"Local\test semaphore name"; Semaphore semaphore = new Semaphore(initialCount: SEMAPHORECOUNT, maximumCount: SEMAPHORECOUNT, name: SEMAPHORENAME); for (int i = 0; i < SEMAPHORECOUNTMAXTRIES; ++i) { Thread.Sleep(500); Task.Factory.StartNew( () => { if (semaphore.WaitOne(1000)) { //Do lots of heavy lifting processing //Stepped out of the code and replaced with a little //Console.WriteLine and a Peek() Console.WriteLine("process"); while (0 != Console.In.Peek()) { Thread.Sleep(100); } semaphore.Release(); } } ); } Console.Write("Waiting"); Console.ReadLine(); } }
An excerpt from the Microsoft .Net documentation for a semaphore is:
Semaphores are of two types: local semaphores and named system semaphores. If you create a Semaphore object using a constructor that accepts a name, it is associated with an operating-system semaphore of that name. Named system semaphores are visible throughout the operating system, and can be used to synchronize the activities of processes.
An excerpt from the Microsoft Windows documentation for a semaphore is
The name can have a “Global\” or “Local\” prefix to explicitly create the object in the global or session namespace.
Collectively these official documentation excerpts describe the semaphore configuration. Herein too lies the problem with the code above, and why the translation from C++ to C# failed. In the production C++ code, the application ran as two daemons each executing 8 threads. In the refactored C# code, the application ran, and in total there were 8 separate tasks/threads executing, with some blocking others (copy/paste the code above, compile, and run in two separate windows – you will see that the semaphore as coded is an OS resource, not a process resource/no name). Creating an unnamed semaphore resolved the problem (the C++ code did not specify a semaphore name). This caught me off-guard too, but it isn’t a bug in the semaphore code as everything behaves as the documentation describes.
The real problem is one of design. The behaviour of the semaphore is different depending on whether it is instantiated with a name. It like saying – the person has a name of Brian, therefore they’re a guy, and you cannot change this. The person might be a guy, they might not be, but the name is a name and their gender is something else and shouldn’t infer other behaviour. Note the deliberate use of the word gender, not sex, Brian might choose to be a woman.
The C++/C# behavioural difference would likely not have occurred if the semaphore had a method or property that enabled the developer to set/configure whether it was process local or OS process scope. Given however that the primary purpose of a semaphore is about object synchronisation and the synchronised control of a limited resource, arguably a semaphore should (it is) be immutable, but the object constructor should parameterise whether it has process or OS process scope, and not exhibit this behaviour as a side-effect of whether the semaphore is named.
The .Net source code for the the semaphore class confirms it is merely a light wrapper around the Windows API. It is a pity that the design decision of the OS has been so crudely wrapped in .Net implementation of the semaphore.
— Published by Mike, 15:44:56 31 January 2017
By Month: November 2022, October 2022, August 2022, February 2021, January 2021, December 2020, November 2020, March 2019, September 2018, June 2018, May 2018, April 2018
Apple, C#, Databases, Faircom, General IT Rant, German, Informatics, LINQ, MongoDB, Oracle, Perl, PostgreSQL, SQL, SQL Server, Unit Testing, XML/XSLT
Leave a Reply