-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
asyncio.ProactorEventLoop mishandles signal wakeup file descriptor #87079
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
Comments
asyncio.ProactorEventLoop uses a socket.socketpair and signal.set_wakeup_fd to wake up a loop that's polling I/O. However it does so with no consideration for file descriptors previously set (i.e. no signal number forwarding). Either by user code or by another instance of asyncio.ProactorEventLoop. The following snippet is enough for the above to cause the loop to hang forever: import asyncio
import gc
asyncio.set_event_loop(asyncio.ProactorEventLoop())
asyncio.set_event_loop(asyncio.ProactorEventLoop())
gc.collect()
asyncio.get_event_loop().run_forever() The first asyncio.ProactorEventLoop instance sets a signal wakeup file descriptor on construction (see cpython/Lib/asyncio/proactor_events.py Line 632 in 187f76d
cpython/Lib/asyncio/proactor_events.py Line 679 in 187f76d
|
Looks this was not tested or thought through with multiple loops and signals (admittedly, using signals is never fun, and even less so on Windows). Can you send a PR with a fix? |
Sorry for taking so long to reply. Sure, I would be happy to contribute. We should probably take care of Unix event loops -- since I opened this ticket I found out those tend to not cooperate with other signal wakeup file descriptor users either. I am a bit short on time right now though, so it will take some time. |
No pressure. If there's an API change needed you will have until 3.10 beta 1. Without API changes this can be fixed any time. |
Circling back. I've been giving some thought to this. While this could be fixed within asyncio.proactor_events.ProactorEventLoop and asyncio.unix_events._UnixSelectorEventLoop implementations (e.g. calling signal.set_wakeup_fd once and forwarding signal numbers to subsequent instances through file descriptors or direct reference), I can't help thinking I've encountered this issue of contending signal API calls too many times already. Rarely two call sites cooperate with each other. So... how do people feel about changing the signal API module to enable multiple file descriptors and multiple signal handlers per signal? It'd be much simpler to use, and not only in asyncio. Implementation-wise, a lock-free singly linked list could be used to store them (may need to expose compare-and-swap atomic primitives in pycore_atomic.h). |
Have you investigated how that could be implemented and what the new API would look like? Personally I don't really like signals (they're not portable and code using them is super hard to get right) and I hesitate to add to their API. OTOH maybe you'll be able to find a champion for such a change among other core devs. I doubt that anyone is going to put any time in this based on a few messages from you though -- you must do the work. |
What is the use case of creating two loops and then deleting them? When |
IIUC the Windows libc emulation does support some limited signal operations (else how would the code work at all?). Presumably the demo program is meant to represent a more realistic scenario where this bug was discovered by the OP. That said, I don't recall any of the details so maybe someone else can help decide how this could be done better (maybe @zooba?). |
The bug can only happen if you manually install a wakeup fd or you are creating two loops on the same thread both of which are unsupported by |
From the wording of the initial comment I think the OP had a manually installed wakeup fd that was getting clobbered by asyncio. I think it's reasonable to see if there's a way asyncio can somehow support this scenario better. |
The only way I can think of to support this would be to first allow multiple signal wakeup handlers as currently only 1 can be added. I expect the change to be non trivial. |
Is there a way to ask whether a fd has a signal handler? Is an asyncio loop without a wakeup fd totally crippled, or merely slightly less performant? I am wondering if an async loop could detect whether a signal wakeup fd is already in use, and then refrain from setting up another one. (And I am reluctant to read the corresponding source code. :-) I don't think changing the |
Not that I am aware of.
It doesn't matter for performance but only for signal handling which on Linux include subprocesses which use
It would break subprocesses on Linux using child watcher which use |
So maybe the minimal solution would be to add something to |
I think there is some confusion here, wakeup fd is not dependent on the OS, it is not some kernel space thing but rather a file descriptor to which ceval writes the signal number to wakeup from blocking select or other blocking calls on Python side. |
That seems rather complicated for little gain, even if the entire child watcher machinery ought to be removed still |
You got me there. But nevertheless this means we could easily add a getter API so asyncio can avoid clobbering it. |
Yes, it's little gain, but I think it's a legitimate bug that asyncio clobbers the wakeup fd when it is already in use by something else, and this is the smallest fix I can think of. (Maybe smaller still would be to pass an explicit flag to new_event_loop() that means "don't use wakeup fd", maybe the OP would be okay with that? @hidmic) |
Thanks for the bump @gvanrossum, and my apologies for not (ever) submitting a patch. It ended up in cold storage.
@kumaraditya303 Well, there's I give you that the repro is artificial though, and as @gvanrossum correctly pointed out, my main issue was with
I understand where you're coming from, but I will point out that it is not the first time I've had to deal with two or more libraries or subsystems not playing along when it comes to signal handling. I think it would be great if Python could manage multiple handlers and wake up file descriptors, precluding this kind of issues entirely.
Alternatively, a warning in In any case, thank you both for Python 🚀 |
The perverse thing here is that your use case is on Windows, which doesn't even have real signals. There's one glimmer of hope -- old_fd = signal.set_wakeup_fd(self._csock.fileno())
if old_fd != -1:
signal.set_wakeup_fd(old_fd)
raise OSError(...) This has two flaws: it doesn't restore the |
A PR raising |
Let's see a PR for the error if it's already set then. There could still be scenarios where this breaks "working" code, but maybe the breakage will do something useful (e.g. alert people that they have multiple subsystems fighting over the wakeup fd). I agree that expanding the API to support multiple wakeup fds or multiple handlers would require more discussion -- for example what would we do to users of the old API? FWIW the actual C code for the wakeup fd is a bit gruesome, I wouldn't want to be the one to make changes there (nearly everything's done one way for Windows and another way for UNIX). |
@hidmic Interested in creating a PR for raising exception ? |
@kumaraditya303 sure |
@hidmic Alas, this broke the SSL tests, which are only run on a buildbot. Do you need help reproducing the failures? |
@gvanrossum I can reproduce. It's I'll try to fix it. |
@hidmic Any progress on fixing this? |
@hidmic or @kumaraditya303 Do either of you wish to make a PR for this issue? Thanks! |
Next action: If there is no further activity on this issue by the September core dev sprint, let's review there and consider closing as stale. |
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: