On Thu, Sep 03, 2020 at 04:56:59PM -0700, Josh Triplett wrote:
On Wed, Sep 02, 2020 at 12:21:28PM +0200, Christian Brauner wrote:
Passing a non-blocking pidfd to waitid() currently has no effect, i.e. is not supported. There are users which would like to use waitid() on pidfds that are O_NONBLOCK and mix it with pidfds that are blocking and both pass them to waitid(). The expected behavior is to have waitid() return -EAGAIN for non-blocking pidfds and to block for blocking pidfds without needing to perform any additional checks for flags set on the pidfd before passing it to waitid(). Non-blocking pidfds will return EAGAIN from waitid() when no child process is ready yet. Returning -EAGAIN for non-blocking pidfds makes it easier for event loops that handle EAGAIN specially.
It also makes the API more consistent and uniform. In essence, waitid() is treated like a read on a non-blocking pidfd or a recvmsg() on a non-blocking socket. With the addition of support for non-blocking pidfds we support the same functionality that sockets do. For sockets() recvmsg() supports MSG_DONTWAIT for pidfds waitid() supports WNOHANG. Both flags are per-call options. In contrast non-blocking pidfds and non-blocking sockets are a setting on an open file description affecting all threads in the calling process as well as other processes that hold file descriptors referring to the same open file description. Both behaviors, per call and per open file description, have genuine use-cases.
The implementation should be straightforward, we simply raise the WNOHANG flag when a non-blocking pidfd is passed and when do_wait() returns without finding an eligible task and the pidfd is non-blocking we set EAGAIN. If no child process exists non-blocking pidfd users will continue to see ECHILD but if child processes exist but have not yet exited users will see EAGAIN.
A concrete use-case that was brought on-list was Josh's async pidfd library. Ever since the introduction of pidfds and more advanced async io various programming languages such as Rust have grown support for async event libraries. These libraries are created to help build epoll-based event loops around file descriptors. A common pattern is to automatically make all file descriptors they manage to O_NONBLOCK.
For such libraries the EAGAIN error code is treated specially. When a function is called that returns EAGAIN the function isn't called again until the event loop indicates the the file descriptor is ready. Supporting EAGAIN when waiting on pidfds makes such libraries just work with little effort.
Link: https://lore.kernel.org/lkml/20200811181236.GA18763@localhost/ Link: https://github.com/joshtriplett/async-pidfd Cc: Kees Cook keescook@chromium.org Cc: Sargun Dhillon sargun@sargun.me Cc: Jann Horn jannh@google.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Ingo Molnar mingo@kernel.org Cc: Oleg Nesterov oleg@redhat.com Cc: "Peter Zijlstra (Intel)" peterz@infradead.org Suggested-by: Josh Triplett josh@joshtriplett.org Signed-off-by: Christian Brauner christian.brauner@ubuntu.com
With or without the discussed change to WNOHANG behavior for compatibility: Reviewed-by: Josh Triplett josh@joshtriplett.org
I think that WNOHANG compatibility change might be a good idea. So I've changed this to:
ret = do_wait(&wo); if (!ret && !(options & WNOHANG) && (f_flags & O_NONBLOCK)) ret = -EAGAIN;
Also, I think you should flip the order of patches 1 and 2, so that there isn't a one-patch window in kernel history where you can create an O_NONBLOCK pidfd with pidfd_open but it has no effect. I'd expect userspace to use pidfd_open accepting or EINVAL-ing the flag as an indication of whether it'll work.
Good point! I've changed the order now.
Thanks! Christian