diff --git a/CHANGELOG.md b/CHANGELOG.md index da0b438c4..7efa67758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- [SIL.Core] Added macOS support for `GlobalMutex` + ## [14.1.1] - 2024-05-23 ### Fixed diff --git a/SIL.Core/Threading/GlobalMutex.cs b/SIL.Core/Threading/GlobalMutex.cs index 5b6bb0804..da2a9bd3d 100644 --- a/SIL.Core/Threading/GlobalMutex.cs +++ b/SIL.Core/Threading/GlobalMutex.cs @@ -15,6 +15,10 @@ namespace SIL.Threading /// /// This is needed because Mono does not support system-wide, named mutexes. Mono does implement the Mutex class, /// but even when using the constructors with names, it only works within a single process. + /// + /// Note that in .NET 8.0 and later (and perhaps starting sooner than version 8), a named mutex can be used across + /// processes in Linux and macOS. However, software using the previous method of locking would not recognize the + /// new method of locking, so Linux continues to use the old, file-based approach internally. /// public class GlobalMutex : DisposableBase { @@ -24,14 +28,18 @@ public class GlobalMutex : DisposableBase /// /// Initializes a new instance of the class. + /// A object is only intended to work with other objects. + /// The name provided may not be the exact name/ID used by the internal OS object(s) used to enforce the mutex. /// public GlobalMutex(string name) { _name = name; - if (!Platform.IsWindows) + if (Platform.IsWindows) + _adapter = new WindowsGlobalMutexAdapter(name); + else if (Platform.IsLinux) _adapter = new LinuxGlobalMutexAdapter(name); else - _adapter = new WindowsGlobalMutexAdapter(name); + _adapter = new ExplicitGlobalMutexAdapter(name); } /// @@ -285,5 +293,19 @@ protected override void DisposeManagedResources() _mutex.Dispose(); } } + + /// + /// A .NET native Mutex object works cross-process on all OSes if we prepend its name with "Global\". + /// On multi-user systems (e.g., Terminal Server on Windows) this can cause one user to grab the lock that another user + /// would like to get. If this is an important scenario, then a login session ID and/or username could be included in + /// the name of the Mutex. Without prepending "Global\", though, named Mutexes don't work cross-process on OSes other + /// than Windows. + /// + private class ExplicitGlobalMutexAdapter: WindowsGlobalMutexAdapter + { + private const string GLOBAL = "Global\\"; + + public ExplicitGlobalMutexAdapter(string name) : base(name.StartsWith(GLOBAL) ? name : $"{GLOBAL}{name}") {} + } } }