closes bpo-38692: Add a pidfd child process watcher to asyncio.#17069
closes bpo-38692: Add a pidfd child process watcher to asyncio.#17069benjaminp merged 1 commit intopython:masterfrom benjaminp:asyncio-pidfd
Conversation
aeros
left a comment
There was a problem hiding this comment.
Thanks for the PR @benjaminp. I have a few recommendations (mostly minor):
Doc/library/asyncio-policy.rst
Outdated
There was a problem hiding this comment.
I think we could remove recent, as this will likely become outdated in the relatively near future.
| and only work on recent (5.3+) kernels. | |
| and only work on 5.3+ kernels. |
There was a problem hiding this comment.
The time period before a Linux kernel becomes non-"recent" is long. There will be RHEL users in 5 years that can't use PidfdChildWatcher.
There was a problem hiding this comment.
There will be RHEL users in 5 years that can't use PidfdChildWatcher.
Good point. I hadn't considered that it would be quite that long, especially as someone who primarily uses the faster to update distros (such as Arch and Fedora).
Lib/asyncio/unix_events.py
Outdated
There was a problem hiding this comment.
IMO, it seems like close() should clear the callbacks rather than attach_loop().
| self.attach_loop(None) | |
| self._callbacks.clear() | |
| self.attach_loop(None) |
This is the current behavior in all three of the other child watchers that use a callbacks dict:
SafeChildWatcher:
cpython/Lib/asyncio/unix_events.py
Lines 959 to 960 in 5c0c325
FastChildWatcher:
cpython/Lib/asyncio/unix_events.py
Lines 1038 to 1039 in 5c0c325
MultiLoopChildWatcher:
cpython/Lib/asyncio/unix_events.py
Lines 1153 to 1154 in 5c0c325
There was a problem hiding this comment.
As far as I'm aware, the examples you list don't keep state in the event loop. Closing a PidfdChildWatcher should remove it completely from the loop.
Lib/asyncio/unix_events.py
Outdated
There was a problem hiding this comment.
A warning is also logged in MultiLoopWatcher if a pid with the given value does not exist in the callbacks. This can be helpful for debugging purposes:
| pidfd, callback, args = self._callbacks.pop(pid) | |
| try: | |
| pidfd, callback, args = self._callbacks.pop(pid) | |
| except KeyError: | |
| logger.warning("Child watcher got an unexpected pid: %r", | |
| pid, exc_info=True) |
There was a problem hiding this comment.
_do_wait being called multiple times indicates a serious programming error, and I don't think knowing the affected pid is going to be more interesting that the traceback from an exception.
Lib/asyncio/unix_events.py
Outdated
There was a problem hiding this comment.
It's not clear to me if this should be callback(pid, returncode, *args) or self._loop.call_soon_threadsafe(callback, pid, returncode, *args), based on: https://github.com/python/cpython/blob/7725352d5ae92e018e6e590e50bc0f465b404807/Lib/asyncio/unix_events.py#L825-L834
Is it inherently thread-safe because PidfdChildWatcher doesn't utilize threads (outside of the main thread)?
There was a problem hiding this comment.
The thread safety requirement is on callback not my invocation of it. There also aren't any threads in PidfdChildWatcher, and _do_wait runs from the event loop.
There was a problem hiding this comment.
The thread safety requirement is on callback not my invocation of it
Ah, I see. I had thought it was on both ends for some reason. Thanks for the clarification.
There was a problem hiding this comment.
Well, I'm just reading the docstring you quoted. :)
There was a problem hiding this comment.
Well, I'm just reading the docstring you quoted. :)
Yeah, fair point. I had just misunderstood "callback() must be thread-safe", as the invocation and the callback itself must be thread-safe, rather than just the callback. I'm also the most familiar with ThreadedChildWatcher (since that one is the default), where multiple threads are utilized and loop.call_soon_threadsafe() is a must (within its _do_waitpid() method).
cpython/Lib/asyncio/unix_events.py
Line 1325 in 5c0c325
Lib/asyncio/unix_events.py
Outdated
There was a problem hiding this comment.
I'm not sure which is preferable (it doesn't come up very often), but I find this slightly easier to read when showing that the remaining items in the tuple aren't significant and there's more than one. Not particularly important though.
| pidfd, _, _ = self._callbacks.pop(pid) | |
| pidfd, *_ = self._callbacks.pop(pid) |
There was a problem hiding this comment.
I don't think that syntax adds value here. If someone changes the shape of self._callbacks, it's probably good for them to consider this unpacking site.
There was a problem hiding this comment.
I don't think that syntax adds value here.
Yeah it's fine as it is then, it's not a big deal either way.
|
Thanks @benjaminp! Meant to approve this after the most recent commit, everything looks good to me. |
https://bugs.python.org/issue38692