From 1c47a3dbb5d910a3c38bf061e669601e3e336822 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 24 Feb 2016 13:43:33 +0100 Subject: [PATCH 1/2] channel.closed is a method since we weren't calling it, the restart channel was never closed, causing process teardown to hang *sometimes*, depending on garbage collection. --- notebook/services/kernels/kernelmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/services/kernels/kernelmanager.py b/notebook/services/kernels/kernelmanager.py index cc739afec..6297eb5ad 100644 --- a/notebook/services/kernels/kernelmanager.py +++ b/notebook/services/kernels/kernelmanager.py @@ -115,7 +115,7 @@ class MappingKernelManager(MultiKernelManager): def finish(): """Common cleanup when restart finishes/fails for any reason.""" - if not channel.closed: + if not channel.closed(): channel.close() loop.remove_timeout(timeout) kernel.remove_restart_callback(on_restart_failed, 'dead') From ba161b06dd42ab12e6e13d1bae8e63d3fe0a847a Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 24 Feb 2016 13:45:00 +0100 Subject: [PATCH 2/2] teardown zmq Context explicitly at the end of each test group Increases the chances of noticing when we aren't cleaning up our sockets properly. --- notebook/tests/launchnotebook.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/notebook/tests/launchnotebook.py b/notebook/tests/launchnotebook.py index 989148b7d..c49838ddc 100644 --- a/notebook/tests/launchnotebook.py +++ b/notebook/tests/launchnotebook.py @@ -18,6 +18,7 @@ except ImportError: from mock import patch #py2 from tornado.ioloop import IOLoop +import zmq import jupyter_core.paths from ..notebookapp import NotebookApp @@ -131,6 +132,16 @@ class NotebookTestBase(TestCase): cls.notebook_dir.cleanup() cls.env_patch.stop() cls.path_patch.stop() + # cleanup global zmq Context, to ensure we aren't leaving dangling sockets + def cleanup_zmq(): + zmq.Context.instance().term() + t = Thread(target=cleanup_zmq) + t.daemon = True + t.start() + t.join(5) # give it a few seconds to clean up (this should be immediate) + # if term never returned, there's zmq stuff still open somewhere, so shout about it. + if t.is_alive(): + raise RuntimeError("Failed to teardown zmq Context, open sockets likely left lying around.") @classmethod def base_url(cls):