5 ms·
5.10 is the first time I've ever used a release candidate kernel to get some of my code to work. Specifically non blocking pidfd's. Traditionally processes hav
by gpm 6y ago
5.10 is the first time I've ever used a release candidate kernel to get some of my code to work. Specifically non blocking pidfd's.
Traditionally processes have reaped dead children by waiting on them, the problem with this is it's a synchronous operation that blocks a thread per child.
The only historical non-blocking solution was to listen for signals. Signals are great and all, except that the kernel will sometimes (unavoidably) merge multiple signals together, preventing you from getting a list of dead children. This is problematic if you want to know exactly which children died.
Linux 5.3 introduced a third system to listen for dead children, pidfd. It's a file descriptor which you can pass to epoll to know when a specific child dies. This is great, but it didn't yet support O_NONBLOCK, which meant that to use it in asynchronous (rust) code we need to create an extra thread which just sits around listening for it.
Linux 5.10 brings the ability to make this fd non blocking, so we can integrate this seamlessly into async event loops. Here's some code of mine that does that: https://github.com/gmorenz/async-transpiled-xv6-shell/blob/master/src/spawn.rs https://github.com/gmorenz/async-transpiled-xv6-shell/blob/m...
Thanks to Christian Brauner, Josh Triplett, and Oleg Nesterov: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6da73d15258a1e5e86d03d4ffba8776d17a8a287 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
- dataking 6y ago> Once you get over the fact that the code makes your eyes bleed, it has a sort of beauty. as a c2rust contributor, this made me smile :) thx friend!
- rixed 6y ago> [wait] the problem with this is it's a synchronous operation that blocks a thread per child. Wouldn't the wait syscall wait for any child?
- megous 6y agoI just do non-blocking waitpid in a loop (until nothing is returned) after I get signal via signalfd. Not sure why this would not work for the OP or miss some process terminations due to merged SIGCHLD signals? I guess using pidfd may be more straightforward, but I don't think this was some unsolved problem previously.
- gpm 6y agoI mention that in a comment in the source, it wouldn't quite work because I intend this to work as a library and reaping children I didn't spawn would be problematic. Also mentioned in the source is that I could do a non blocking waitpid on every child I have, that just strikes me as slow and not clean code. Of course if I want to deploy code like this in any serious way I'll have to implement another solution than non blocking pidfds...
- fullstop 6y agoI do the same thing, call waitpid with WNOHANG after getting the signal.
- dataflow 6y agoIf you're just looking for detection of child (owned) process death, I don't think you should need pidfds? Have you tried monitoring their stdio pipes (or a UNIX socket or similar that you dupe into it) for closure? To my understanding pidfds were meant to take care of the more general case of awaiting the termination of an arbitrary process that you don't control.
- gpm 6y agoIt's a shell, I don't necessarily have any pipes from the child. Moreover a child playing games with it's file descriptors shouldn't fool me into thinking it's dead and either cause me to block waiting for it to actually die or move on and never reap it.
- dataflow 6y agoWell you could create a pipe from the shell if you're spawning it (just create a pipe() when you fork() it), assuming it's inheriting your stdio so you can't already just wait on those. This is what I've done in the past. That said, if your child process has genuine potential to acting adversarially, I don't believe even pidfds would be enough; you'll need to make sure it doesn't spawn e.g. a child process to break away. However, I would guess that's not your actual use case? Rather, I'd assume you're trying to prevent against accidents. If you're in this boat like I'm assuming, I'd suggest thinking over whether the scenario you proposed is actually plausible. The only real-world instances I've seen a process playing "games" with file descriptors it did not previously open itself are (a) closing FDs after a fork, and (b) user code in a shell, doing something like exec 2>&3 or whatever. I assume you're worried about the latter given the former isn't relevant. In that case, you could work around it via dup2() after you fork(), by duping into a random high FD that nobody would be likely to mess with (like a random high number plus its own PID). This should solve it for practical purposes. Not that you shouldn't use pidfds necessarily, but I'm just throwing these out there because I've been in this situation before and these might help you solve your problem on other systems that don't support that.
- gpm 6y ago
- JoshTriplett 6y agoI have an async-pidfd crate that might help you, to avoid having to open-code your pidfd handling: https://github.com/joshtriplett/async-pidfd https://github.com/joshtriplett/async-pidfd