diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 510e51240c..e07bfcf507 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2092,7 +2092,34 @@ def _init_asyncio_patch(self): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 + + With the introduction of the async kernel, the existing sync kernel + requires the use of nested loops in order to run code synchronously. + This is done in `jupyter_client` using the helper util `run_sync`: + + ref: https://github.com/jupyter/jupyter_client/blob/f453b51eeeff9e905c583b7da3905c0e35cfbdf0/jupyter_client/utils.py#L11 + + which creates a new event loop and relies on `nest_asyncio` patching + to allow nested loops. This requires that *all* potential tasks are + patched before executing. When only some tasks are patched it leads to + the following issue: + + ref: https://github.com/jupyter/notebook/issues/6164 + + So we must call `nest_asyncio.apply()` method as early as possible. It + is preferable to do this in the consuming application rather than the + `jupyter_client` as it is a global patch and would impact all consumers + rather than just the ones that rely on synchronous kernel behavior. """ + import nest_asyncio + + try: + nest_asyncio.apply() + except RuntimeError: + # nest_asyncio requires a running loop in order to patch. + # In tests the loop may not have been created yet. + pass + if sys.platform.startswith("win") and sys.version_info >= (3, 8): import asyncio try: diff --git a/notebook/tests/launchnotebook.py b/notebook/tests/launchnotebook.py index bb5f8b7781..426ce43ffd 100644 --- a/notebook/tests/launchnotebook.py +++ b/notebook/tests/launchnotebook.py @@ -167,10 +167,16 @@ def start_thread(): token=cls.token, **bind_args ) - if 'asyncio' in sys.modules: - app._init_asyncio_patch() - import asyncio - asyncio.set_event_loop(asyncio.new_event_loop()) + if "asyncio" in sys.modules: + app._init_asyncio_patch() + import asyncio + + asyncio.set_event_loop(asyncio.new_event_loop()) + # Patch the current loop in order to match production + # behavior + import nest_asyncio + + nest_asyncio.apply() # don't register signal handler during tests app.init_signal = lambda : None # clear log handlers and propagate to root for nose to capture it diff --git a/setup.py b/setup.py index 4ae0febdc6..c54f9a1594 100755 --- a/setup.py +++ b/setup.py @@ -121,6 +121,7 @@ 'jupyter_client>=5.3.4', 'nbformat', 'nbconvert', + 'nest-asyncio>=1.5', 'ipykernel', # bless IPython kernel for now 'Send2Trash>=1.8.0', 'terminado>=0.8.3',