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

Reply via email to