diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 7eec55bc3..6f5b302af 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -229,9 +229,12 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): # start the pinging if self.ping_interval > 0: - self.last_ping = ioloop.IOLoop.instance().time() # Remember time of last ping + loop = ioloop.IOLoop.current() + self.last_ping = loop.time() # Remember time of last ping self.last_pong = self.last_ping - self.ping_callback = ioloop.PeriodicCallback(self.send_ping, self.ping_interval) + self.ping_callback = ioloop.PeriodicCallback( + self.send_ping, self.ping_interval, io_loop=loop, + ) self.ping_callback.start() def send_ping(self): @@ -242,7 +245,7 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): # check for timeout on pong. Make sure that we really have sent a recent ping in # case the machine with both server and client has been suspended since the last ping. - now = ioloop.IOLoop.instance().time() + now = ioloop.IOLoop.current().time() since_last_pong = 1e3 * (now - self.last_pong) since_last_ping = 1e3 * (now - self.last_ping) if since_last_ping < 2*self.ping_interval and since_last_pong > self.ping_timeout: @@ -254,4 +257,4 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): self.last_ping = now def on_pong(self, data): - self.last_pong = ioloop.IOLoop.instance().time() + self.last_pong = ioloop.IOLoop.current().time() diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 6b798c821..291094ce2 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -888,7 +888,7 @@ class NotebookApp(BaseIPythonApplication): line = sys.stdin.readline() if line.lower().startswith('y') and 'n' not in line.lower(): self.log.critical("Shutdown confirmed") - ioloop.IOLoop.instance().stop() + ioloop.IOLoop.current().stop() return else: print("No answer for 5s:", end=' ') @@ -897,11 +897,11 @@ class NotebookApp(BaseIPythonApplication): # set it back to original SIGINT handler # use IOLoop.add_callback because signal.signal must be called # from main thread - ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler) + ioloop.IOLoop.current().add_callback(self._restore_sigint_handler) def _signal_stop(self, sig, frame): self.log.critical("received signal %s, stopping", sig) - ioloop.IOLoop.instance().stop() + ioloop.IOLoop.current().stop() def _signal_info(self, sig, frame): print(self.notebook_info()) @@ -1004,13 +1004,21 @@ class NotebookApp(BaseIPythonApplication): b = lambda : browser.open(url_path_join(self.connection_url, uri), new=2) threading.Thread(target=b).start() + + self.io_loop = ioloop.IOLoop.current() try: - ioloop.IOLoop.instance().start() + self.io_loop.start() except KeyboardInterrupt: info("Interrupted...") finally: self.cleanup_kernels() self.remove_server_info_file() + + def stop(self): + def _stop(): + self.http_server.stop() + self.io_loop.stop() + self.io_loop.add_callback(_stop) def list_running_servers(profile='default'): diff --git a/IPython/html/services/contents/tests/test_manager.py b/IPython/html/services/contents/tests/test_manager.py index 647578c70..e2a1bc186 100644 --- a/IPython/html/services/contents/tests/test_manager.py +++ b/IPython/html/services/contents/tests/test_manager.py @@ -78,7 +78,6 @@ class TestContentsManager(TestCase): self.td = self._temp_dir.name self.contents_manager = FileContentsManager( root_dir=self.td, - log=logging.getLogger() ) def tearDown(self): diff --git a/IPython/html/tests/launchnotebook.py b/IPython/html/tests/launchnotebook.py index 7775e2895..c78bef4e2 100644 --- a/IPython/html/tests/launchnotebook.py +++ b/IPython/html/tests/launchnotebook.py @@ -6,11 +6,12 @@ import sys import time import requests from contextlib import contextmanager -from subprocess import Popen, STDOUT +from threading import Thread, Event from unittest import TestCase -import nose +from tornado.ioloop import IOLoop +from ..notebookapp import NotebookApp from IPython.utils.tempdir import TemporaryDirectory MAX_WAITTIME = 30 # seconds to wait for notebook server to start @@ -50,35 +51,46 @@ class NotebookTestBase(TestCase): @classmethod def wait_until_dead(cls): """Wait for the server process to terminate after shutdown""" - for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)): - if cls.notebook.poll() is not None: - return - time.sleep(POLL_INTERVAL) - - raise TimeoutError("Undead notebook server") + cls.notebook_thread.join(timeout=MAX_WAITTIME) + if cls.notebook_thread.is_alive(): + raise TimeoutError("Undead notebook server") @classmethod def setup_class(cls): cls.ipython_dir = TemporaryDirectory() cls.notebook_dir = TemporaryDirectory() - notebook_args = [ - sys.executable, '-c', - 'from IPython.html.notebookapp import launch_new_instance; launch_new_instance()', - '--port=%d' % cls.port, - '--port-retries=0', # Don't try any other ports - '--no-browser', - '--ipython-dir=%s' % cls.ipython_dir.name, - '--notebook-dir=%s' % cls.notebook_dir.name, - ] - cls.notebook = Popen(notebook_args, - stdout=nose.iptest_stdstreams_fileno(), - stderr=STDOUT, + app = cls.notebook = NotebookApp( + port=cls.port, + port_retries=0, + open_browser=False, + ipython_dir=cls.ipython_dir.name, + notebook_dir=cls.notebook_dir.name, ) + + # clear log handlers and propagate to root for nose to capture it + # needs to be redone after initialize, which reconfigures logging + app.log.propagate = True + app.log.handlers = [] + app.initialize(argv=[]) + app.log.propagate = True + app.log.handlers = [] + started = Event() + def start_thread(): + loop = IOLoop.current() + loop.add_callback(started.set) + try: + app.start() + finally: + # set the event, so failure to start doesn't cause a hang + started.set() + cls.notebook_thread = Thread(target=start_thread) + cls.notebook_thread.start() + started.wait() cls.wait_until_alive() @classmethod def teardown_class(cls): - cls.notebook.terminate() + cls.notebook.stop() cls.wait_until_dead() cls.ipython_dir.cleanup() cls.notebook_dir.cleanup()