So I have already posted some mails to this list and here is one more. This
time it is about cleanly exiting the event loop. The problem is pretty
simple: I run my event loop using run_forever and it really does run
forever, in an infinite loop until the user requests a stop. The stop is
generally requested by hitting ^C, i.e. raising a KeyboardInterrupt.
Contrary to a single-threaded application, an event loop that runs
everything will always receive the KeyboardInterrupt exception (unless
caught by a task/coroutine). Because of that I literally didn't include any
handling inside the running loop for it. Instead I have code like this:
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
loop.run_until_complete(shutdown())
Where shutdown would be a coroutine that handles cleaning up all
still-running tasks by cancelling them (I fact there is a single task that
is cancelled and cancels all tasks it started in turn). This way, here is
the only place where I catch a KeyboardInterrupt since I assumed it would
travel up the stack without anything catching it. And that certainly seems
to be the case: Hitting ^C actually exits the loop and starts the shutdown
coroutine. But here is where it gets weird: The shutdown fails because a
KeyboardInterrupt is raised, even though I only sent one and already caught
that! And even more weird: The stack trace actually looks like one loop was
running inside the other:
Traceback (most recent call last):
File "/home/javex/mypkg/__init__.py", line 261, in run
event_loop.run_until_complete(shutdown())
File
"/home/javex/.virtualenvs/misc/lib/python3.3/site-packages/asyncio/base_events.py",
line 203, in run_until_complete
self.run_forever()
...
File
"/home/javex/.virtualenvs/misc/lib/python3.3/site-packages/asyncio/futures.py",
line 243, in result
raise self._exception
File "/home/javex/mypkg/__init__.py", line 245, in run
asyncio.get_event_loop().run_forever()
...
File
"/home/javex/.virtualenvs/misc/lib/python3.3/site-packages/asyncio/unix_events.py",
line 487, in _start
universal_newlines=False, bufsize=bufsize, **kwargs)
File "/usr/lib64/python3.3/subprocess.py", line 819, in __init__
restore_signals, start_new_session)
File "/usr/lib64/python3.3/subprocess.py", line 1409, in _execute_child
part = _eintr_retry_call(os.read, errpipe_read, 50000)
File "/usr/lib64/python3.3/subprocess.py", line 479, in _eintr_retry_call
return func(*args)
KeyboardInterrupt
Full Trace <http://pastebin.com/jGppikdZ>
If you look at the stack trace, you can see that we are inside the
run_until_complete part (above inside the finally block) and that it in
turn winds up inside the run_forever part from above. Of course the bottom
part of the stack trace always varies as it depends on where the function
is currently working. However, the part above is always the same: It looks
like one loop runs inside the other.
So here I am stuck. I don't know how to handle this cleanly. Previously I
did try a solution similar to the one described in Detect exceptions not
consumed<https://docs.python.org/3.4/library/asyncio-dev.html#detect-exceptions-not-consumed>
where
I forced myself to wrap any task in it. That worked but it was error prone
and felt dirty. Instead, I now handle exception for each task individually
which means I can react to them much better. However, additionally to
handling normal exceptions, I handled a KeyboardInterrupt here, calling
loop.stop(). However, that would only trigger when inside one of those
tasks, not when running polling of the loop. So I had to handle this
additionally similar to the above try-except-finally block which
essentially meant having two different code paths handling the same thing
while it feels like there should be a central point.
After reading the PEP; this list and searching for a solution, I could not
find anything on it. So here it is: How do I exit a running loop cleanly by
sending a KeyboardInterrupt? Note that I am aware of the Example: Set
signal handlers for SIGINT and
SIGTERM<https://docs.python.org/3.4/library/asyncio-eventloop.html#example-set-signal-handlers-for-sigint-and-sigterm>
but
that is Unix specific and my application should also run on Windows in the
future. Otherwise this would be the perfect solution (btw: Why isn't
Windows supported? From the
signal<https://docs.python.org/3.4/library/signal.html#signal.signal>
documentation
it seems that some signals (including SIGINT) can be caught on Windows as
well.).
Thanks in advance for any suggestion on how to acheive the desired behavior.
Regards,
Florian