Skip to content

Commit

Permalink
Merge pull request #281 from minrk/asyncio-fix
Browse files Browse the repository at this point in the history
handle KeyboardInterrupt with %gui asyncio
  • Loading branch information
takluyver authored Nov 27, 2017
2 parents a563626 + fa814da commit 21882bd
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 10 deletions.
19 changes: 15 additions & 4 deletions ipykernel/eventloops.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,23 @@ def kernel_handler():
loop.call_later(kernel._poll_interval, kernel_handler)

loop.call_soon(kernel_handler)
try:
if not loop.is_running():
# loop is already running (e.g. tornado 5), nothing left to do
if loop.is_running():
return
while True:
error = None
try:
loop.run_forever()
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
except KeyboardInterrupt:
continue
except Exception as e:
error = e
if hasattr(loop, 'shutdown_asyncgens'):
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
if error is not None:
raise error
break

def enable_gui(gui, kernel=None):
"""Enable integration with a given GUI"""
Expand Down
17 changes: 17 additions & 0 deletions ipykernel/tests/_asyncio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""test utilities that use async/await syntax
a separate file to avoid syntax errors on Python 2
"""

import asyncio


def async_func():
"""Simple async function to schedule a task on the current eventloop"""
loop = asyncio.get_event_loop()
assert loop.is_running()

async def task():
await asyncio.sleep(1)

loop.create_task(task())
44 changes: 44 additions & 0 deletions ipykernel/tests/test_eventloop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Test eventloop integration"""

import sys
import time

import IPython.testing.decorators as dec
from .utils import flush_channels, start_new_kernel, execute

KC = KM = None


def setup():
"""start the global kernel (if it isn't running) and return its client"""
global KM, KC
KM, KC = start_new_kernel()
flush_channels(KC)


def teardown():
KC.stop_channels()
KM.shutdown_kernel(now=True)


async_code = """
from ipykernel.tests._asyncio import async_func
async_func()
"""


@dec.skipif(sys.version_info < (3, 5), "async/await syntax required")
def test_asyncio_interrupt():
flush_channels(KC)
msg_id, content = execute('%gui asyncio', KC)
assert content['status'] == 'ok', content

flush_channels(KC)
msg_id, content = execute(async_code, KC)
assert content['status'] == 'ok', content

KM.interrupt_kernel()

flush_channels(KC)
msg_id, content = execute(async_code, KC)
assert content['status'] == 'ok'
16 changes: 10 additions & 6 deletions ipykernel/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

from __future__ import print_function

import atexit
import os
import sys

from contextlib import contextmanager
from subprocess import PIPE, STDOUT
Expand All @@ -18,19 +21,14 @@

from jupyter_client import manager

#-------------------------------------------------------------------------------
# Globals
#-------------------------------------------------------------------------------

STARTUP_TIMEOUT = 60
TIMEOUT = 15

KM = None
KC = None

#-------------------------------------------------------------------------------
# code
#-------------------------------------------------------------------------------

def start_new_kernel(**kwargs):
"""start a new kernel, and return its Manager and Client
Expand All @@ -43,6 +41,7 @@ def start_new_kernel(**kwargs):
kwargs.update(dict(stdout=stdout, stderr=STDOUT))
return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs)


def flush_channels(kc=None):
"""flush any messages waiting on the queue"""
from .test_message_spec import validate_message
Expand Down Expand Up @@ -76,6 +75,11 @@ def execute(code='', kc=None, **kwargs):
validate_message(execute_input, 'execute_input', msg_id)
nt.assert_equal(execute_input['content']['code'], code)

# show tracebacks if present for debugging
if reply['content'].get('traceback'):
print('\n'.join(reply['content']['traceback']), file=sys.stderr)


return msg_id, reply['content']

def start_global_kernel():
Expand Down

0 comments on commit 21882bd

Please sign in to comment.