-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
add a pidfd child process watcher #82873
Comments
Recent versions of Linux has built out support for pidfd, a way to do process management with file descriptors. I wanted to try it out, so implemented a pidfd-based child watcher for asyncio. My WIP progress patch is attached. It passes all asyncio tests. |
Awesome! I think the patch can be splitted in os.pidfd_open() and asyncio part itself. os modification is clear. asyncio part looks correct after the brief view. |
Nathaniel, you may be interested in the pidfd_open(), at least in adding the function to os module. |
I like the idea of exposing pidfd_open() syscall as os.pidfd_open(). Benjamin: Would you mind to create a PR based on your patch, but without your asyncio change?
I would prefer to avoid this part of the patch. You should accept an optional flags parameter, no? |
+ self._loop._remove_reader(pidfd) Note: Maybe do these operations in the reverse order. I expect a higher risk of exception when calling Python self._loop._remove_reader(), than on closing a FD that we opened. |
Thanks for the CC. It would be nice to get AFAICT the other bits of the pidfd API right now are some extra flags to clone, and an extra flag to waitid. The waitid flag is nice-to-have but not really urgent, since it's easy enough to use a flag even if the stdlib doesn't explicitly expose it. The clone flags are super interesting, but before we can use them, we need to have some way to access clone itself, and that's a big complicated project, so we definitely shouldn't worry about it here. |
Yes, I will be submitting followup changes for pidfd_send_signal and the other goodies. I would like to use pidfds in subprocess, but as you as you say, that's another kettle of fish. |
See also bpo-38630 "subprocess.Popen.send_signal() should poll the process". |
Perhaps in the __init__ we could do something like this: class PidfdChildWatcher(AbstractChildWatcher):
def __init__(self):
if not hasattr(os, "pidfd_open"):
raise RuntimeError("os.pidfd_open() is unavailable.")
... Thoughts?
I think we could also specifically look for race conditions with I'd be glad to work on testing and opening a PR to asyncio if needed (while giving Benjamin credit for the patch of course). |
hasattr is useful for supporting old versions of the os module, but asyncio doesn't have to care about that. You should probably try calling pidfd_open and see whether you get -ESYSCALL, and also maybe try passing it to poll(), since I think there might have been a release where the syscall existed but it wasn't pollable. Not sure what the error for that looks like. |
pidfd_open was added after pidfd pollling, so it should suffice to make sure pidfd_open doesn't ENOSYS. |
Would it be useful to use a pidfd in subprocess.Popen to fix bpo-38630 root issue, when pidfd is available? |
On Wed, Nov 6, 2019, at 09:57, STINNER Victor wrote:
Probably, but as noted above, we need to figure out the best way to integrate pidfds into subprocess. (Probably not in this issue.) |
I got a failure in newly added test_pidfd_open: ====================================================================== Traceback (most recent call last):
File "/build/python-git/src/cpython/Lib/test/test_posix.py", line 1479, in test_pidfd_open
self.assertEqual(cm.exception.errno, errno.EINVAL)
AssertionError: 1 != 22 I'm running kernel 5.3.7-x86_64-linode130 with Arch Linux. At first I suspect that it's related to system call filters from systemd as tests are run in a systemd-nspawn container. However, there are still failures after patching systemd with systemd/systemd@9e48626. How about also skipping the test if pidfd_open returns EPERM? |
I think you must still be experiencing some sort of sandboxing. I don't know how else you would get an EPERM out of pidfd_open. |
I believe Benjamin is correct. On a native install of Arch Linux with kernel 5.3.7 (using latest updates from the official repos), I received no failures in test_posix. [aeros:~/repos/benjaminp-cpython]$ ./python -m test test_posix (asyncio-pidfd) == Tests result: SUCCESS == 1 test OK. Total duration: 544 ms To confirm there weren't intermittent failures, I also ran test_posix indefinitely, sending SIGINT after ~2500 iterations. No failures occurred: [aeros:~/repos/benjaminp-cpython]$ ./python -m test test_pty -F (asyncio-pidfd) == Tests result: INTERRUPTED == 2506 tests OK. Total duration: 1 min 31 sec It seems that the issue is likely specific to Chih-Hsuan Yen's environment. |
Oops, looks like I copied the wrong results of a separate test I was running earlier. I'll post the results of ~1000 iterations of test_posix below, once it is completed again. |
[aeros:~/repos/benjaminp-cpython]$ ./python -m test test_posix -F (asyncio-pidfd) 1008 tests OK. Total duration: 8 min 52 sec |
It seems like systemd-nspawn is just breaking everything: https://sourceware.org/ml/libc-alpha/2019-11/msg00277.html |
Benjamin Peterson, Kyle Stanley: Thank you both very much for intensive testing and helpful information! Yes on my Arch Linux box tests are also fine without systemd-nspawn. However, as I have to use systemd-nspawn for some reasons, and investigating how it blocks system calls is beyond my ability, I just add a workaround for now: https://aur.archlinux.org/cgit/aur.git/tree/python-git-issue38692.diff?h=python-git. |
Well, we can try to argue to not block syscalls, but I'm not sure that we can win such battle :-) For os.urandom(), I chose to handle EPERM as ENOSYS in bpo-27955. Extract of Python/bootstrap_hash.c: /* ENOSYS: the syscall is not supported by the kernel.
EPERM: the syscall is blocked by a security policy (ex: SECCOMP)
or something else. */
if (errno == ENOSYS || errno == EPERM) {
getrandom_works = 0;
return 0;
} We can just skip the test if the syscall fails with EPERM. |
We should not claim to support running our tests in weird syscall sandboxes. There's an infinite number of possible sandboxing configurations, and we can't fix them all. |
There is no request to support an "an infinite number of possible sandboxing configurations". Only to skip the test if the syscall fails with EPERM. That sounds reasonable to me. |
Sure, that change on it's own looks small and harmless. My point is that it's a slippery slope. Why is that sandbox configuration important enough to handle? It won't be tested by our CI and no one will know whether they can ever remove the EPERM skipping case. |
It's just convenient for users who use/test Python in a Linux sandbox. The fact is that 2 days after you merged the new test, a first user reported a failure. I expect more issues if we don't simply fix the test :-) Sandboxes on Linux are more and more common. |
It will be fixed, though, as soon as the user upgrades systemd. |
Also, adding to this, the child watchers are one of the least used components of asyncio's public API. So, I think the deprecation and removal cost will be fairly minimal. See the GitHub code usage (includes exact copies of Lib/asyncio/unix_events.py, so there's some redundancy): MultiLoopChildWatcher: https://github.com/search?l=Python&q=MultiLoopChildWatcher&type=Code (20 results, just added in 3.8) |
Thanks everyone for the reviews. I will go ahead and merge the PR. Shall we return to bpo-38591 for planning the future of child watching? |
Sorry for the late review. PidfdChildWatcher should be enumerated in unix_events.py:all to make the class visible by asyncio import rules. Kyle, would you make a post-fix PR? |
I actually just noticed that myself and was coming back to the bpo issue to mention that it was missing from __all__. I'll take of that, no problem. |
I have consistent behavior on Fedora 32 in mock [0] and podman [1]. Wanted to test docker as well, but my docker setup is currently broken. # python3.9
Python 3.9.0a1 (default, Nov 20 2019, 00:00:00)
[GCC 9.2.1 20190827 (Red Hat 9.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.pidfd_open(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
PermissionError: [Errno 1] Operation not permitted => the test fails. [0] https://github.com/rpm-software-management/rpm |
BTW my kernel is: 5.3.7-301.fc31.x86_64 Sorry for commenting twice, I forgot to mention that. |
I get a different error on Fedora 31 with Linux kernel 5.3.9-300.fc31.x86_64: $ ./python
Python 3.9.0a1+ (heads/method_freelist:e34fa9b8d7, Nov 20 2019, 12:09:54)
[GCC 9.2.1 20190827 (Red Hat 9.2.1-1)] on linux
>>> import os
>>> os.pidfd_open(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument |
I don't know about podman, but it sounds like mock and docker both use buggy sandboxing: https://bugzilla.redhat.com/show_bug.cgi?id=1770154 |
I reopen the issue since the discussion restarted. |
As I already proposed previously, I proposed PR 17290 to simply skip test_pidfd_open() if os.pidfd_open() fails with a PermissionError. I don't propose to change os.pidfd_open(), only the test unit for practical reasons: not bother users who have to use a strict Linux sandbox using a syscalls whitelist. |
I pushed my "Skip test_posix.test_pidfd_open() on EPERM" change just for practical reasons. We can hope that Linux sandboxes will shortly be updated to allow pidfd_open() syscall. It should be safe for most use cases ;-) I close again the issue. |
Thank you very much for the commit "Skip test_posix.test_pidfd_open() on EPERM"! I can confirm it makes test_posix pass inside a systemd-nspawn container on Arch Linux. On the other hand, I found that tests were broken as both systemd libseccomp on Arch Linux were out-dated. With recently-pushed systemd 243.162-2 and locally-updated libseccomp 2.4.2, os.pidfd_open() works fine. Apparently [1] and [2] are relevant fixes. By the way, upgrading systemd and libseccomp also fixes test_signal.PidfdSignalTest [3], which is added two days ago [4]. [1] systemd/systemd-stable@51ea58a |
Great! |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: