Open kouvel opened 3 months ago
Tagging subscribers to this area: @mangod9 See info in area-owners.md if you want to be subscribed.
Do we have any customers asking for emulation of named Windows synchronization APIs beyond Mutexes on non-Windows platforms?
Only named mutexes are supported on Unixes, but similar APIs are also proposed for other named wait handles.
Are the reliability guarantees of the Unix emulation of the other named wait handles going to match Windows in case of abnormal process termination?
Do we have any customers asking for emulation of named Windows synchronization APIs beyond Mutexes on non-Windows platforms?
Not that I've heard of.
Are the reliability guarantees of the Unix emulation of the other named wait handles going to match Windows in case of abnormal process termination?
It might be feasible on Linux where pthread process-shared robust mutexes and process-shared conditions are available. On OSX/BSD it may be more challenging or may need the use of some other primitive.
Not that I've heard of.
I do not think we should be expanding the set of emulated named synchronization primitives outside Windows then. It is very niche functionality and fixing the security and reliability problems in the existing named mutex costed us a lot over time.
I do not think we should be expanding the set of emulated named synchronization primitives outside Windows then.
I'm not suggesting expanding the implementations of named events and semaphores into Unixes, I'm only suggesting expanding their APIs similarly, mostly for Windows, as even there it could still be beneficial to have an easy and explicit way of specifying the user scope of a named wait handle.
true
.[Obsolete]
. Right now SYSLIB0057 is the next available diagnostic ID, but use whatever is next in Obsoletions.cs
when creating the PR.namespace System.Threading
{
public struct NamedWaitHandleOptions
{
private bool _allUsers;
private bool _allSessions;
// Note that for default(NamedWaitHandleOptions), both of these properties will return `true`.
public bool CurrentUserOnly { get => !_allUsers; set => _allUsers = !value; }
public bool CurrentSessionOnly { get => !_allSessions; set => _allSessions = !value; }
}
public sealed partial class Mutex : WaitHandle
{
//
// Proposed
//
public Mutex(string? name, NamedWaitHandleOptions options) { }
public Mutex(bool initiallyOwned, string? name, NamedWaitHandleOptions options) { }
public Mutex(bool initiallyOwned, string? name, NamedWaitHandleOptions options, out bool createdNew) { }
public static Mutex OpenExisting(string name, NamedWaitHandleOptions options) { }
public static bool TryOpenExisting(string name, NamedWaitHandleOptions options, [NotNullWhen(true)] out Mutex? result) { }
//
// Existing
//
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public Mutex(bool initiallyOwned, string? name) { }
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public Mutex(bool initiallyOwned, string? name, out bool createdNew) { }
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public static Mutex OpenExisting(string name) { }
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public static bool TryOpenExisting(string name, [NotNullWhen(true)] out Mutex? result) { }
}
public sealed partial class Semaphore : WaitHandle
{
//
// Proposed
//
public Semaphore(int initialCount, int maximumCount, string? name, NamedWaitHandleOptions options) { }
public Semaphore(int initialCount, int maximumCount, string? name, NamedWaitHandleOptions options, out bool createdNew) { }
[SupportedOSPlatform("windows")]
public static Semaphore OpenExisting(string name, NamedWaitHandleOptions options) { }
[SupportedOSPlatform("windows")]
public static bool TryOpenExisting(string name, NamedWaitHandleOptions options, [NotNullWhen(true)] out Semaphore? result) { }
//
// Existing
//
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public Semaphore(int initialCount, int maximumCount, string? name) { }
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public Semaphore(int initialCount, int maximumCount, string? name, out bool createdNew) { }
[SupportedOSPlatform("windows")]
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public static Semaphore OpenExisting(string name) { }
[SupportedOSPlatform("windows")]
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public static bool TryOpenExisting(string name, [NotNullWhen(true)] out Semaphore? result) { }
}
public partial class EventWaitHandle : WaitHandle
{
//
// Proposed
//
public EventWaitHandle(bool initialState, EventResetMode mode, string? name, NamedWaitHandleOptions options) { }
public EventWaitHandle(bool initialState, EventResetMode mode, string? name, NamedWaitHandleOptions options, out bool createdNew) { }
[SupportedOSPlatform("windows")]
public static EventWaitHandle OpenExisting(string name, NamedWaitHandleOptions options) { }
[SupportedOSPlatform("windows")]
public static bool TryOpenExisting(string name, NamedWaitHandleOptions options, [NotNullWhen(true)] out EventWaitHandle? result) { }
//
// Existing
//
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public EventWaitHandle(bool initialState, EventResetMode mode, string? name) { }
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public EventWaitHandle(bool initialState, EventResetMode mode, string? name, out bool createdNew) { }
[SupportedOSPlatform("windows")]
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public static EventWaitHandle OpenExisting(string name) { }
[SupportedOSPlatform("windows")]
[Obsolete("some message", DiagnosticId = "SYSLIB0057")]
public static bool TryOpenExisting(string name, [NotNullWhen(true)] out EventWaitHandle? result) { }
}
}
public struct NamedWaitHandleOptions { private bool _allUsers; private bool _allSessions; // Note that for default(NamedWaitHandleOptions), both of these properties will return `true`. public bool CurrentUserOnly { get => !_allUsers; set => _allUsers = !value; } public bool CurrentSessionOnly { get => !_allSessions; set => _allSessions = !value; } }
How would the application initialize a non-default NamedWaitHandleOptions value -- is a constructor missing from this API?
Never mind, I didn't notice that the properties have setters.
Background and motivation
Mutex
,Semaphore
, andEventWaitHandle
) are openable by any user (by default on Windows unless ACL APIs are used, and always on Unixes). In many cases, it would be sufficient to share a named wait handle restricted to the current user. It would be desirable to have a cross-platform way of specifying that a named wait handle should be restricted to the current user. Only named mutexes are supported on Unixes, but similar APIs are also proposed for other named wait handles.AllUsers
for backward compatibility. A consideration may be to eventually deprecate the current APIs.Local\
andGlobal\
refer to sessions but they are ambiguous and have been mistaken to refer to usersAPI Proposal
Notes about some behaviors
CurrentSessionOnly
andAllSessions
, for instance, there can be two different mutexes with the same name, one usingCurrentSessionOnly
and the other usingAllSessions
. In addition, when usingCurrentSessionOnly
, each session uses a different namespace, so for instance, there can be multiple different mutexes usingCurrentSessionOnly
in different sessions.CurrentUserOnly
andAllUsers
CurrentUserOnly
andAllUsers
. In addition, when usingCurrentUserOnly
, each user uses a different namespace.ArgumentException
options
argument does not specifyCurrentUserOnly
orAllUsers
name
argument doesn't have a session prefix, and theoptions
argument does not specifyCurrentSessionOnly
orAllSessions
options
argument specifies bothCurrentUserOnly
andAllUsers
options
argument specifies bothCurrentSessionOnly
andAllSessions
name
argument has a session prefix and theoptions
argument specifiesCurrentSessionOnly
orAllSessions
, but the two don't matchIOException
CurrentUserOnly
, the relevant files would also be restricted to the current user. If for some reason that would not be possible, anIOException
would be thrown.API Usage
Similarly for other enum values and other named wait handles.
Alternative Designs
@"Global\User\MyMutex"
to specify that it should be restricted to the current user and available to all sessions. Aside from what was covered in the background/motivation section regarding defaults and ambiguity, an argument is more clear and more purposed. This is also similar to the existingPipeOptions.CurrentUserOnly
for named pipes.Mutex
(covered in background/motivation)NamedMutexOptions
. It seems unlikely that there would be new type-specific options for the different types, and if there are, they could be added in new arguments.Risks
/tmp
may not have the sticky bit, or its file system may not support some things (such as if it's a mounted share), or something like that, in which caseCurrentUserOnly
may not work