Skip to content
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

[IDEA] expose "run while unblocked" abstraction to allow running safe Rust code in signal handler #109

Closed
godmar opened this issue Jun 3, 2021 · 3 comments

Comments

@godmar
Copy link

godmar commented Jun 3, 2021

signal-hook seems to be the "go to" library for signal handling in Rust and I have already learned a lot about Rust from reading it. Thank you very much for this library, and my apologies in advance if sharing my idea here is not the appropriate place.

I asked on the Rust users how one might implement SIGCHLD handling in a single threaded shell written in Rust.

In this particular use case (or at least in one possible implementation), imagine that a signal (like SIGCHLD) is blocked most of the time, except during short sequences of code where user input is read via a libc system call. For instance, if I were to use the rustyline crate, it would be libc::read. I should note that it's acceptable in this use case to block the signal most of the time while the shell is running and not sitting at the prompt - latency is not an issue.

The proposed abstraction would

  • accept a lambda as signal handler
  • block the signal upon initialization
  • provide a run_unblocked API that accepts a lambda to the restricted code that would be run while the signal is unblocked.
  • uninstall and unblock the signal upon destruction

My question is whether such an abstraction would enable us to run unrestricted, safe Rust code in the signal handler as long as the user ensures that only async-signal-safe libc functions run while an UnblockSignal object lives. Alternatively, a suitably defined subset of Rust code could be allowed within such sections of code.

The idea is to restrict concurrency to small sections of the code so as to run safe Rust code inside signal handlers and in most of the code, but disallow safe Rust code during small sections in which signals may be delivered.

In pseudo code:

let sigmanager = sigmanager{
   signal: SIGCHLD,
   signal_handler: |s| { /* unrestricted safe Rust code here */ }
}; // blocks SIGCHLD and installs signal handler
loop {
   sigmanager.run_unblocked(|| {
         libc::read(..&input..)
   })
   // process input in safe Rust code
}
@vorner
Copy link
Owner

vorner commented Jun 5, 2021

Hello

Sharing the idea here is fine. But I'm afraid this won't work for several reasons.

  • The POSIX documentation explicitly states that putting any kind of non async-signal-safe things inside a signal handler is an undefined behavior. There are no exceptions to that and even if it worked on some system, any other (past, current or future) system could break that and be correct. So unrestricted Rust code can not go into a signal handler.
  • While your use case may be single-threaded, the library can't reasonably assume a single-threaded program. And one would have to ensure that the inside of the run_unblocked doesn't use anything „fishy“. So even if the above worked, any kind of run_unblocked would have to be unsafe with requirements that are hard to uphold in single-threaded program and impossible to uphold in multi-threaded program. I'd therefore not like to have such abstraction with very limited usability as part of signal-hook. And blocking signals would prevent other SIGCHLD subscribers from getting the signals in timely manner, which doesn't look nice either. If it worked, this could be built on top of signal hook and wouldn't necessarily need to be in the same library.

Anyway, for practical reasons (not having to deal with all the low-level details), I'd suggest you either go with more threads (unless there's a good reason not to), use some kind of asynchronous framework (tokio?) or go with some kind of self-pipe-trick (provided by signal hook) and doing a select/poll/epoll on stdin+the pipe.

(I'll close this as I don't think this belongs into this library, if anywhere, but if I misunderstood or if you have ideas how to make it work, feel free to reopen)

@vorner vorner closed this as completed Jun 5, 2021
@godmar
Copy link
Author

godmar commented Jun 5, 2021

Thanks for the reply. I understand your arguments, and it's good that you didn't bring up any Rust-related concerns I wasn't aware of. Basically, there's no free lunch here.

The POSIX documentation explicitly states that putting any kind of non async-signal-safe things inside a signal handler is an undefined behavior.

I checked the 2008 version (which I have at hand) and I do not read it the same way you do. It says:

"In the presence of signals, all functions defined by this volume of POSIX.1-2008 shall behave as defined when called from or interrupted by a signal-catching function, with a single exception: when a signal interrupts an unsafe function and the signal-catching function calls an unsafe function, the behavior is undefined." (emphasis mine) (Vol. 2: System Interfaces, Issue 7, pg 489/490).

In other words, there's nothing magical about signal handlers other than their possible interaction with functions that are already in progress by some thread, which is what POSIX specifies. Merely calling a function does not trigger undefined behavior unless an unsafe function is interrupted.

@vorner
Copy link
Owner

vorner commented Jun 5, 2021

I see. Then this could actually work. Still, Rust programs are often multi-threaded and it is quite an uncommon pattern, and one that needs to be unsafe to call for the listed reasons.

So, if you are interested in this functionality, could you create it as a separate crate on top of signal hook (or, signal-hook-registry, as it probably won't need anything from signal-hook itself ‒ that one allows registration of arbitrary hooks)? I can review the code if you want, or help out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants