Repository: ipython/ipython

Issue:
MultiKernelManager.shutdown_all() can't be called twice
Currently you can't call `MultiKernelManager.shutdown_all()` twice as the the method does not remove the kernels from the internal `self._kernels` list (or checks that they are still active) and in the second call tries to redo the shutdown for the first round of kernels.

Example:

``` python
try:
    from queue import Empty  # Py 3
except ImportError:
    from Queue import Empty  # Py 2
from IPython.kernel.multikernelmanager import MultiKernelManager
km = MultiKernelManager()
km.list_kernel_ids() # -> empty list
ret = km.start_kernel()
km.list_kernel_ids() # -> has one kernel inside
km.shutdown_all()
km.list_kernel_ids() # -> has still the kernel inside
km.shutdown_all() # raises, as it tries to shutdown the kernel again, but no open soccets are found
```

Error message:

```
Traceback (most recent call last):
  File "c:\data\external\knitpy\knitpy\knitpyapp.py", line 207, in convert_documents
    outfilenames = kp.render(document_filename, output=self.export_format)
  File "c:\data\external\knitpy\knitpy\knitpy.py", line 523, in render
    self.convert(parsed, md_temp)
  File "c:\data\external\knitpy\knitpy\knitpy.py", line 195, in convert
    self._km.shutdown_all()
  File "C:\portabel\miniconda\envs\ipython\lib\site-packages\IPython\kernel\multikernelmanager.py", line 160, in shutdown_all
    self.request_shutdown(kid)
  File "C:\portabel\miniconda\envs\ipython\lib\site-packages\IPython\kernel\multikernelmanager.py", line 33, in wrapped
    r = method(*args, **kwargs)
  File "C:\portabel\miniconda\envs\ipython\lib\site-packages\IPython\kernel\manager.py", line 252, in request_shutdown
    self.session.send(self._control_socket, msg)
  File "C:\portabel\miniconda\envs\ipython\lib\site-packages\IPython\kernel\zmq\session.py", line 660, in send
    stream.send_multipart(to_send, copy=copy)
AttributeError: 'NoneType' object has no attribute 'send_multipart'
```

Not sure whats the problem is here, but I suspect that either should `cleanup` call `remove_kernel` or that should happen in the loop. 



Discussion:

Resolving patch:
diff --git a/IPython/kernel/multikernelmanager.py b/IPython/kernel/multikernelmanager.py
--- a/IPython/kernel/multikernelmanager.py
+++ b/IPython/kernel/multikernelmanager.py
@@ -161,6 +161,7 @@ def shutdown_all(self, now=False):
         for kid in kids:
             self.finish_shutdown(kid)
             self.cleanup(kid)
+            self.remove_kernel(kid)
 
     @kernel_method
     def interrupt_kernel(self, kernel_id):


Test patch:
diff --git a/IPython/kernel/tests/test_multikernelmanager.py b/IPython/kernel/tests/test_multikernelmanager.py
--- a/IPython/kernel/tests/test_multikernelmanager.py
+++ b/IPython/kernel/tests/test_multikernelmanager.py
@@ -62,6 +62,15 @@ def test_tcp_lifecycle(self):
         km = self._get_tcp_km()
         self._run_lifecycle(km)
     
+    def test_shutdown_all(self):
+        km = self._get_tcp_km()
+        kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
+        self.assertIn(kid, km)
+        km.shutdown_all()
+        self.assertNotIn(kid, km)
+        # shutdown again is okay, because we have no kernels
+        km.shutdown_all()
+    
     def test_tcp_cinfo(self):
         km = self._get_tcp_km()
         self._run_cinfo(km, 'tcp', localhost())