-
-
Notifications
You must be signed in to change notification settings - Fork 348
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
IO waits + control-C on Windows #42
Comments
For PyPy, see: https://bitbucket.org/cffi/cffi/issues/306/provide-a-wrapper-for-_pyos_sigintevent-or (tl;dr: pypy 3.5 doesn't yet have any equivalent of |
I guess the other option would be to implement this functionality ourselves from scratch: it turns out that Windows allows a process to register multiple handlers, with |
Further investigation/thoughts:
(The problem with subinterpreters is that if a CFFI callback finds itself running in a new thread that has never run any Python code before, then it uses the The other option would be to write a separate tiny package that just provides the feature of registering a |
Testing this will be tricky, because we'll need a real It might be okay in a restricted way, like: a test that just spawns a subprocess whose only job is to do a single sleep + handle a single event. I'd be inclined to try running it in a new console, + not even trying to get stdout/stderr back, + maybe a thread that just pokes at the console, since doing console IO seems to be correlated with whether The commit that removed the elaborate code attempting to do real |
There's also a complication with race conditions: suppose a signal wakes us up. How do we make sure we stay awake until the Python handler runs? If we call A nice thing about ...here's a wacky option. Just hijack NB |
Oh, there's another option – I guess I read through this too quickly before, but it turns out that |
...so that means that in our current event loop design, with If we switch to IOCP as the primary waiting function then things are more complicated. Basically we have to keep issuing a read on the wakeup socket. One way to do that would be a system task that just loops doing that, but then there's the risk that during shutdown the system task will go away and then we get wedged somehow... we really should have a read outstanding every time we block. I guess it wouldn't be terribly hard to just allocate a single OVERLAPPED for this inside the IOManager, and then every time we get it back issue a new WSARecv. |
Mostly fixed in: cc42cbb Notes:
Leaving this open, because it's probably worth revisiting at some point given these caveats + the likelihood of #52 shaking things up. But for now it is mostly working. |
This might be a dumb idea, but if the problem on Windows is the event loop being reawakened, why not launch a background task that does nothing more than run forever and sleep on 100ms intervals or something? At the very least, it'd take care of the problem of someone calling sleep(100000) and never being awakened until afterwards. |
We actually already have an upper bound on how long we're willing to sleep, but it's currently set to 24 hours. There's no technical obstacle to dropping it down to 100 ms or whatever, except, you know. Polling. Ick :-). It's not obviously better than what we have now, but yeah it's an option to consider. |
I wasn't suggesting changing the upper bound on sleeping. You'd merely have a task that does nothing but wake up every 100ms--in effect forcing an upper limit on the sleep time passed to select() and related functions. On a modern machine, you'd never notice the effect. |
@dabeaz: |
I need more coffee. Or less coffee. |
As compared to the CFFI-based code that this replaces, it's (a) less race-y (the Python C-level signal handler writes to the wakeup_fd *after* setting the flag to run the Python-level signal handler, whereas our old code ran before), (b) avoids arcane CFFI stuff that might be broken in the presence of subinterpreters, (c) slightly less code. The downside of set_wakeup_fd is that writing to the wakeup socket fails with EWOULDBLOCK, then in our context the correct thing to do is to ignore that error (we just want to guarantee a wakeup, and if the send buffer is full then we are definitely going to wake up), but the hardcoded behavior in signalmodule.c is to print a spurious warning to stderr. IMO this is makes it useless to us on Unix-likes, because they get signals all the time, and we configure out wakeup socket to use a tiny send buffer to save on kernel memory. But on Windows the trade-offs are different: Windows insists on using a gargantuan send buffer (see comments in _wakeup_socketpair.py), and the only signal is control-C. It's not that big a deal if someone gets a spurious error message in an extremely rare case when responding to an explicit control-C. (Though it still would be nicer to not have this error message...) See python-triogh-42 for more details.
I figured out why In particular, there's still no test for this, and it would be nice if there were a way to disable the warning when the wakeup fd overflows. (The latter is really much more of an issue on Unix-likes, though, so more relevant to #109) |
#119 is related to this – specifically set_wakeup_fd appears to be somewhat less reliable than hoped :-( |
Windows has no concept of
EINTR
; if you hit control-C while we're blocked inselect
orGetQueuedCompletionStatusEx
, then the C-level signal handler runs, but the blocking call still runs to completion before returning to Python and letting the Python-level signal handler run. So currently if you dotrio.run(trio.sleep, 100000)
then on Windows you can't interrupt this with control-C. That's unfortunate! We should fix it.CPython itself avoids this, e.g.
time.sleep(10000)
is interruptible by hitting control-C. The way they do it is: in the real C-level signal handler, in addition to setting the magic flag saying that a signal arrived, they also (if this is windows and the signal is SIGINT) dowhere
sigint_event
is a global singletonEvent
object which can be retrieved using:So the basic idea is: before entering your blocking call, you (a)
ResetEvent(sigint_event)
, and (b) arrange (somehow) for your blocking call to exit early if this handle becomes signaled. And then Python's regular signal-checking logic resumes and runs the Python-level handler and we're good to go. We already need the machinery for waking up the main thread when an Event becomes signaled, so that part shouldn't be a big deal, though it's not implemented yet. [Edit: and of course we also have to be careful to only turn this logic on if we are running in the main thread. And it might be important to explicitly check for signals after waking up? I'm not sure how often the interpreter checks, and if we wake up and then go back to sleep without checking then that's bad.]Open question: I have no idea what the equivalent of this is on PyPy. It's possible they simply don't have this machinery at all (i.e. I don't even know if pypy-on-windows allows you to break out of
time.sleep(100000)
). As of 2017-02-07 there is no version of pypy that can run trio on Windows (currently the 3.5-compatibility branch is linux-only), so the issue may not arise for a bit.The text was updated successfully, but these errors were encountered: