zig icon indicating copy to clipboard operation
zig copied to clipboard

Proposal: `std.debug.setAbortSignalHandler`

Open wooster0 opened this issue 1 year ago • 2 comments

I propose adding a function with a name such as setAbortSignalHandler to std.debug.

Motivation

std.debug.setAbortSignalHandler is supposed to make it easier to catch an abort signal (mainly caused by pressing Ctrl+C in a terminal) and handle it with specific code, in a relatively cross-platform way.

Ctrl+C is mainly used by developers and closely related to debugging, which is why I propose putting it in std.debug. This handler is supposed to be used for non-critical cleanups or resets of state. One common example is terminal applications that use escape sequences to manipulate the terminal, like changing the color and such: when the user Ctrl+Cs out of the app, the terminal's state would be messed up; maybe the cursor still is hidden, input is not echoed, the terminal is in an alternate screen buffer, etc. std.debug.setAbortSignalHandler is supposed to make it easier to make that happen less often. Apart from that, it could also be useful for just some simple test programs.

Again, the handler should not be relied on being run so it should not be used for anything critical; it's best-effort. All this can be mentioned in the function's docs.

Implementation

On POSIX we can use sigaction.

The main problem here might be how to do it on Windows:

  • Windows has signal in its libc: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal?view=msvc-170 This means that std.debug.setAbortSignalHandler would depend on libc if you're on Windows. This is preferably avoided.

  • Windows also has SetConsoleCtrlHandler: https://learn.microsoft.com/en-us/windows/console/setconsolectrlhandler, which is probably the better option. This can handle both Ctrl+C and Ctrl+Break:

    CTRL+BREAK is always treated as a signal, but typical CTRL+C behavior can be changed in three ways that prevent the handler functions from being called:

    I believe this is fine though because setAbortSignalHandler is more of a best-effort than a guarantee that it runs the handler on Ctrl+C. After all, setAbortSignalHandler is more for debugging than real usage in an app, although I think it's fine for that too as long as you don't use it to set a handler that you rely on doing something critical.

    Also, even though the handler registered by this function can also handle Ctrl+Break, I'm having my doubts anyone uses it so we should probably only listen for Ctrl+C, even with those potential problems above.

    Also, I read that SetConsoleCtrlHandler may spawn a thread in order to run its callback, which may be undesirable sometimes and can lead to unexpected behavior. This makes me wonder if we should force the handler passed to setAbortSignalHandler to be noreturn? I think it might still be fine though, as a debug function.

I already wrote an implementation using sigaction and SetConsoleCtrlHandler. I'm yet to test it on Windows but on Linux so far it's definitely fulfilling its purpose.

Or maybe this is just too niche and not worth adding? It's just an idea.

See also: https://www.reddit.com/r/Zig/comments/wviq36/how_to_catch_signals_at_least_sigint/

wooster0 avatar Oct 02 '22 15:10 wooster0

posix-only signal library https://gist.github.com/nektro/5e20de7db63af25d72ecee35e9f8d16a

const signal = @import("signal");

pub fn main() !void {
    signal.listenFor(std.os.linux.SIG.INT, handle_sig);
    signal.listenFor(std.os.linux.SIG.TERM, handle_sig);
}

fn handle_sig() void {
    std.log.info("exiting safely...", .{});
    std.os.exit(0);
}

nektro avatar Oct 02 '22 16:10 nektro

Today I tested my implementation on Windows as well and using SetConsoleCtrlHandler worked just as expected. The program keeps running after the handler is run. Now I will just wait for this to be accepted and then I can add it. The signature looks like this by the way:

fn setAbortSignalHandler(comptime handler: *const fn () void) !void

wooster0 avatar Oct 04 '22 18:10 wooster0