diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 9170428ba..bb7129f0c 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -1707,8 +1707,42 @@ class NotebookApp(JupyterApp): pc = ioloop.PeriodicCallback(self.shutdown_no_activity, 60000) pc.start() + def _init_asyncio_patch(self): + """set default asyncio policy to be compatible with tornado + + Tornado 6 (at least) is not compatible with the default + asyncio implementation on Windows + + Pick the older SelectorEventLoopPolicy on Windows + if the known-incompatible default policy is in use. + + do this as early as possible to make it a low priority and overrideable + + ref: https://github.com/tornadoweb/tornado/issues/2608 + + FIXME: if/when tornado supports the defaults in asyncio, + remove and bump tornado requirement for py38 + """ + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + try: + from asyncio import ( + WindowsProactorEventLoopPolicy, + WindowsSelectorEventLoopPolicy, + ) + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + @catch_config_error def initialize(self, argv=None): + self._init_asyncio_patch() + super(NotebookApp, self).initialize(argv) self.init_logging() if self._dispatching: diff --git a/notebook/tests/launchnotebook.py b/notebook/tests/launchnotebook.py index 916d6072d..e84e15642 100644 --- a/notebook/tests/launchnotebook.py +++ b/notebook/tests/launchnotebook.py @@ -140,9 +140,6 @@ class NotebookTestBase(TestCase): started = Event() def start_thread(): - if 'asyncio' in sys.modules: - import asyncio - asyncio.set_event_loop(asyncio.new_event_loop()) app = cls.notebook = NotebookApp( port=cls.port, port_retries=0, @@ -156,6 +153,10 @@ class NotebookTestBase(TestCase): allow_root=True, token=cls.token, ) + if 'asyncio' in sys.modules: + app._init_asyncio_patch() + import asyncio + asyncio.set_event_loop(asyncio.new_event_loop()) # don't register signal handler during tests app.init_signal = lambda : None # clear log handlers and propagate to root for nose to capture it