microsoft/vscode-jupyter

reconnecting to an existing kernel also creates a new kernel

zpincus opened this issue · 3 comments

Summary

vscode interacts badly with existing (external to vscode) jupyter servers, spawning and never cleaning up numerous unwanted kernels. There are also various other incidental bugs that also pop up related to the kernel picker and existing jupyter servers, noted below.

Context: In order to work around #1378 and #1654, I start up my own jupyter server rather than letting vscode start one up. This way, reconnecting to a remote session (or having to reload the vscode window, etc.) doesn't terminate and restart all the running kernels every open notebook.

Specifically, there is feature (mentioned in the above bug reports) whereby if you are running your own jupyter server, then vscode remembers which kernel ID a particular notebook was connected to, and attempts to connect to that kernel when re-opening the notebook (or reloading the UI etc).

However, every time vscode tries to reconnect to a kernel of an existing jupyter server, it also spawns a new kernel. This occurs whether the the server is on localhost or is "local" to a remote-ssh session (i.e. running local to the machine that vscode is connected to over remote-ssh); I have not tested an actual remote server. The vscode insider build has the same problem.

Environment data

  • VS Code version:
    Version: 1.56.1 (Universal)
    Commit: e713fe9b05fc24facbec8f34fb1017133858842b
    Date: 2021-05-06T10:08:24.325Z
    Electron: 12.0.4
    Chrome: 89.0.4389.114
    Node.js: 14.16.0
    V8: 8.9.255.24-electron.0
    OS: Darwin x64 20.4.0
  • Jupyter Extension version: 2021.6.832593372
  • Python Extension version: 2021.5.829140558
  • OS (Windows | Mac | Linux distro) and version: macOS 11.3.1 (Big Sur)
  • Python and/or Anaconda version: 3.8.8
  • Type of virtual environment used: conda
  • Jupyter server running: existing server on localhost

Expected behaviour

(Re)connecting to an existing kernel will not spawn new kernels.

Actual behaviour

(Re)connecting to an existing kernel spawns a new kernel for each connection attempt.

This can be easily seen from the "Running Notebooks" list on the server's webpage. These kernels are invisible to the vscode kernel picker list until the window is reloaded.

Steps to reproduce:

  1. Start up an external jupyter server via jupyter notebook from a terminal, and note the server URL.
  2. Open the server URL in a web page and click on the "running" tab to confirm that no notebook kernels are presently running.
  3. Create a new vscode window, and then choose jupyter: create new blank notebook from the command palette. (Opening an existing notebook from the filesystem works too.)
  4. Click on the jupyter server name to bring up the "choose how to connect to jupyter" dialog (or choose jupyter: specify local or remote jupyter server for connections from the command palette). Choose "existing" and then paste in the server URL. Allow the window to reload when prompted.
  5. Switch to the jupyter server webpage showing the running kernels to confirm that there are already two kernels listed. Bring up the vscode kernel picker (command palette: jupyter: select kernel or click on the python name next to the server name at the top of the vscode window) and note that it shows two kernels -- one with a long ID, and one unlabeled. The long ID matches the first kernel listed in the jupyter server page, and choosing the unlabeled one will is supposed to start a new kernel. Note further that the second kernel on the server page is not visible in the vscode picker.

image

image

  1. Make it possible to distinguish between the different kernels. Simplest is to type e.g. foo = 5 into one cell, run it, and type print(foo) into the next cell. If you reconnect to the same kernel, then running this cell will output 5. If you connect to a new kernel in which foo hasn't yet been defined, then running only that second cell will produce an error.

  2. [Optional; exposes a different bug] Try to create a new kernel by choosing the unlabeled kernel from the vscode picker. At this point you're not actually in a new kernel: running the print(foo) cell yields no error. This is unexpected; it should have created a new kernel (and in fact this same action will create a new kernel, so long as you run the next step below (of reconnecting to the current kernel) first.

  3. Choose the existing kernel from the vscode kernel picker (top line). Again verify that running the print(foo) cell yields no error. Examining the running list in the jupyter server webpage shows that the second (unused) kernel from above has been shut down and a new (unused) one started.

image

  1. Again, choose the existing kernel from the vscode kernel picker (top line). Again, verify that running the print(foo) cell yields no error. Now a third, new kernel has been spawned, visible to the jupyter server webpage but still invisible to vscode. Repeating this step will spawn a new kernel each time.

image

image

  1. Use vscode to explicitly create a new kernel by choosing the unlabeled kernel from the kernel picker. Finally a new kernel is created: running only the print(foo) cell yields the expected error. Observe that two additional kernels are now running in the jupyter server webpage. The first time you summon the vscode kernel picker after this, the new kernel is not visible (another bug!), but dismissing it and re-opening the kernel picker finally now shows two kernels to choose among, and correctly indicates that the newer one has a single active connection (the notebook that is open in vscode).

image

image

  1. Reload the vscode window, forcing a reconnection. Observe that one of the last new kernels started in step 9 was shut down, but two new ones have appeared in the server webpage! Now, all but one of these are visible to vscode.

image

image

Notes

I think this is related to #4442, or maybe even the same, but it's hard to tell because there's not a lot of detail. In any case, there are at least three different incorrect behaviors illustrated by the steps above. First is the spawning of new kernels that are invisible to and unkillable from vscode. Second is the fact that you can't intentionally create a new kernel from vscode without at least once selecting the existing kernel in the kernel picker first (step 7 vs. step 9 should not have different results). Third is the fact that once you do manage to create a new kernel, the picker list doesn't update to show it until the second time it is displayed.

There's also a minor feature-request in that it is very difficult to determine which kernel is actively connected to the open notebook from the kernel picker.

Also, note that the steps above are using a new, blank, un-saved notebook. If you use an existing notebook that you open from the filesystem, the same thing happens. Likewise, the basic gist of the bug is identical when using today's insider build, though the kernel-picker UI is harder to get to.

Thanks for the bug.

The two kernels are there so that restart is super fast (and doesn't require us to actually wait for ipykernel to try and restart, which sometimes it can't).

The second restart kernel on reconnect is a bug though. It should recognize the original 'restart' kernel and just continue to use that one.

It'll also need to make sure to remember the restart kernel across reloads / remote disconnects so that they don't get un-hidden in the vscode UI after those events (see step 10 above)...

I believe the 'extra' kernel has been removed. We don't create this 'restart' kernel anymore.
8dfc17c