-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Deliver Posix signals in reverse order of their registration #116557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0070a8e
bc0d5b3
fcf65ca
cae16d4
c5cb8b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,23 +8,20 @@ namespace System.Runtime.InteropServices | |
| { | ||
| public sealed partial class PosixSignalRegistration | ||
| { | ||
| private static readonly HashSet<Token> s_registrations = new(); | ||
| private static readonly Dictionary<int, List<Token>> s_registrations = new(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is structured the same way as the non-Windows signal registrations. |
||
|
|
||
| private static unsafe PosixSignalRegistration Register(PosixSignal signal, Action<PosixSignalContext> handler) | ||
| { | ||
| switch (signal) | ||
| int signo = signal switch | ||
| { | ||
| case PosixSignal.SIGINT: | ||
| case PosixSignal.SIGQUIT: | ||
| case PosixSignal.SIGTERM: | ||
| case PosixSignal.SIGHUP: | ||
| break; | ||
|
|
||
| default: | ||
| throw new PlatformNotSupportedException(); | ||
| } | ||
|
|
||
| var token = new Token(signal, handler); | ||
| PosixSignal.SIGINT => Interop.Kernel32.CTRL_C_EVENT, | ||
| PosixSignal.SIGQUIT => Interop.Kernel32.CTRL_BREAK_EVENT, | ||
| PosixSignal.SIGTERM => Interop.Kernel32.CTRL_SHUTDOWN_EVENT, | ||
| PosixSignal.SIGHUP => Interop.Kernel32.CTRL_CLOSE_EVENT, | ||
| _ => throw new PlatformNotSupportedException() | ||
| }; | ||
|
|
||
| var token = new Token(signal, signo, handler); | ||
| var registration = new PosixSignalRegistration(token); | ||
|
|
||
| lock (s_registrations) | ||
|
|
@@ -35,7 +32,12 @@ private static unsafe PosixSignalRegistration Register(PosixSignal signal, Actio | |
| throw Win32Marshal.GetExceptionForLastWin32Error(); | ||
| } | ||
|
|
||
| s_registrations.Add(token); | ||
| if (!s_registrations.TryGetValue(signo, out List<Token>? tokens)) | ||
| { | ||
| s_registrations[signo] = tokens = new List<Token>(); | ||
| } | ||
|
|
||
| tokens.Add(token); | ||
| } | ||
|
|
||
| return registration; | ||
|
|
@@ -49,17 +51,25 @@ private unsafe void Unregister() | |
| { | ||
| _token = null; | ||
|
|
||
| s_registrations.Remove(token); | ||
| if (s_registrations.Count == 0 && | ||
| !Interop.Kernel32.SetConsoleCtrlHandler(&HandlerRoutine, Add: false)) | ||
| if (s_registrations.TryGetValue(token.SigNo, out List<Token>? tokens)) | ||
| { | ||
| // Ignore errors due to the handler no longer being registered; this can happen, for example, with | ||
| // direct use of Alloc/Attach/FreeConsole which result in the table of control handlers being reset. | ||
| // Throw for everything else. | ||
| int error = Marshal.GetLastPInvokeError(); | ||
| if (error != Interop.Errors.ERROR_INVALID_PARAMETER) | ||
| tokens.Remove(token); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand this correctly, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, nvm, I see no reason to assume that an arbitrary call to Unregister should be trying to remove the most recently registered signal. |
||
| if (tokens.Count == 0) | ||
| { | ||
| throw Win32Marshal.GetExceptionForWin32Error(error); | ||
| s_registrations.Remove(token.SigNo); | ||
| } | ||
|
|
||
| if (s_registrations.Count == 0 && | ||
| !Interop.Kernel32.SetConsoleCtrlHandler(&HandlerRoutine, Add: false)) | ||
| { | ||
| // Ignore errors due to the handler no longer being registered; this can happen, for example, with | ||
| // direct use of Alloc/Attach/FreeConsole which result in the table of control handlers being reset. | ||
| // Throw for everything else. | ||
| int error = Marshal.GetLastPInvokeError(); | ||
| if (error != Interop.Errors.ERROR_INVALID_PARAMETER) | ||
| { | ||
| throw Win32Marshal.GetExceptionForWin32Error(error); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -69,38 +79,14 @@ private unsafe void Unregister() | |
| [UnmanagedCallersOnly] | ||
| private static Interop.BOOL HandlerRoutine(int dwCtrlType) | ||
| { | ||
| PosixSignal signal; | ||
| switch (dwCtrlType) | ||
| { | ||
| case Interop.Kernel32.CTRL_C_EVENT: | ||
| signal = PosixSignal.SIGINT; | ||
| break; | ||
| Token[]? tokens = null; | ||
|
|
||
| case Interop.Kernel32.CTRL_BREAK_EVENT: | ||
| signal = PosixSignal.SIGQUIT; | ||
| break; | ||
|
|
||
| case Interop.Kernel32.CTRL_SHUTDOWN_EVENT: | ||
| signal = PosixSignal.SIGTERM; | ||
| break; | ||
|
|
||
| case Interop.Kernel32.CTRL_CLOSE_EVENT: | ||
| signal = PosixSignal.SIGHUP; | ||
| break; | ||
|
|
||
| default: | ||
| return Interop.BOOL.FALSE; | ||
| } | ||
|
|
||
| List<Token>? tokens = null; | ||
| lock (s_registrations) | ||
| { | ||
| foreach (Token token in s_registrations) | ||
| if (s_registrations.TryGetValue(dwCtrlType, out List<Token>? registrations)) | ||
| { | ||
| if (token.Signal == signal) | ||
| { | ||
| (tokens ??= new List<Token>()).Add(token); | ||
| } | ||
| tokens = new Token[registrations.Count]; | ||
| registrations.CopyTo(tokens); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -109,10 +95,14 @@ private static Interop.BOOL HandlerRoutine(int dwCtrlType) | |
| return Interop.BOOL.FALSE; | ||
| } | ||
|
|
||
| var context = new PosixSignalContext(signal); | ||
| foreach (Token handler in tokens) | ||
| var context = new PosixSignalContext(0); | ||
|
|
||
| // Iterate through the tokens in reverse order to match the order of registration. | ||
| for (int i = tokens.Length - 1; i >= 0; i--) | ||
| { | ||
| handler.Handler(context); | ||
| Token token = tokens[i]; | ||
| context.Signal = token.Signal; | ||
| token.Handler(context); | ||
| } | ||
|
|
||
| return context.Cancel ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ public sealed partial class PosixSignalRegistration : IDisposable | |
| /// <exception cref="PlatformNotSupportedException"><paramref name="signal"/> is not supported by the platform.</exception> | ||
| /// <exception cref="IOException">An error occurred while setting up the signal handling or while installing the handler for the specified signal.</exception> | ||
| /// <remarks> | ||
| /// The handlers are executed in reverse order of their registration. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will submit this to the official docs too once this is merged. |
||
| /// Raw values can be provided for <paramref name="signal"/> on Unix by casting them to <see cref="PosixSignal"/>. | ||
| /// Default handling of the signal can be canceled through <see cref="PosixSignalContext.Cancel"/>. | ||
| /// <see cref="PosixSignal.SIGINT"/> and <see cref="PosixSignal.SIGQUIT"/> can be canceled on both | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<Token>will require O(N) linear search for unregistration. I do not expect it to be a problem since the number of these registrations is expected to be fairly small.