From f18f5551d80d83f5a6cfbcedb281bbe0b8e6cef7 2015-02-06 23:14:09
From: Min RK <benjaminrk@gmail.com>
Date: 2015-02-06 23:14:09
Subject: [PATCH] make Comm's publishing threadsafe

If a Comm tries to send a message from another thread,
schedule it to be called ASAP in the main thread
via IOLoop.add_callback

---

diff --git a/IPython/kernel/comm/comm.py b/IPython/kernel/comm/comm.py
index 3a65cb0..dc51b50 100644
--- a/IPython/kernel/comm/comm.py
+++ b/IPython/kernel/comm/comm.py
@@ -3,8 +3,11 @@
 # Copyright (c) IPython Development Team.
 # Distributed under the terms of the Modified BSD License.
 
+import threading
 import uuid
 
+from zmq.eventloop.ioloop import IOLoop
+
 from IPython.config import LoggingConfigurable
 from IPython.kernel.zmq.kernelbase import Kernel
 
@@ -13,7 +16,7 @@ from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
 
 
 class Comm(LoggingConfigurable):
-    
+    """Class for communicating between a Frontend and a Kernel"""
     # If this is instantiated by a non-IPython kernel, shell will be None
     shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
                      allow_none=True)
@@ -63,6 +66,10 @@ class Comm(LoggingConfigurable):
     
     def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
         """Helper for sending a comm message on IOPub"""
+        if threading.current_thread().name != 'MainThread' and IOLoop.initialized():
+            # make sure we never send on a zmq socket outside the main IOLoop thread
+            IOLoop.instance().add_callback(lambda : self._publish_msg(msg_type, data, metadata, buffers, **keys))
+            return
         data = {} if data is None else data
         metadata = {} if metadata is None else metadata
         content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))