jupyter / jupyter/notebook

Opening notebook without kernelspec/language metadata causes SessionAlreadyStarting error

Open
#5,183 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Jupyter Notebook
Stars
13.3k
Forks
5.8k
Avg merge
6d 11h
Merged PRs (30d)
7

Description

Steps to reproduce:

1. Run `jupyter notebook`
2. At the default startup page `http://localhost:8888/tree`, click `New` -> `Python 3`
3. Close the notebook window which opened (without saving the new notebook)
4. Reload `http://localhost:8888/tree`
5. Select the notebook and click the `Shutdown` button
6. Open the notebook

The browser console reports `Exception in event handler for spec_changed.Kernel`, with name `SessionAlreadyStarting`. Additionally, the kernel indicator name is not updated from its default value of "Kernel"; and the actual running kernel is the default kernel, regardless of which kernel was specified when creating the notebook.

![Screen Shot 2020-01-24 at 3 33 35 PM](https://user-images.githubusercontent.com/44622583/73111068-ef7b9a80-3ed6-11ea-9dde-3ee1a1045e01.png)
![Screen Shot 2020-01-24 at 3 35 09 PM](https://user-images.githubusercontent.com/44622583/73111067-ef7b9a80-3ed6-11ea-9acd-6ecc9b51ee91.png)

---

I'm not totally sure specifically where the true underlying error (or errors) occur, but I believe I have traced out the sequence of events involved. (Apologies for the length -- it's pretty complicated!)

First, what I *think* happens in step 2: Clicking `New` -> `Python 3` first sends a POST request to `/contents` to create the notebook, receives a response with the new notebook's path, and then sends a GET request to `/notebooks/` with `kernel_name=python3` as its query string. The notebook was originally created with no knowledge of the kernel: the GET request will start a kernel session on the server and return its metadata to the browser; but it was just a GET request, so the server does not update the stored notebook with the kernelspec unless the notebook is separately saved. Steps 3-5 prevent that from happening, so the stored notebook remains in its initial, kernel-agnostic state; and there are no active sessions on the server: the `kernel_name` the user initially requested has been lost. (I think this behavior might separately be worth changing; but the rest of this report focuses only on the JavaScript error.)

---

If that sounds correct so far, here is what I believe happens in the browser in step 6. (I recommend using Chrome if you want to set breakpoints and follow along, since I can't figure out how to get Firefox's dev tools to automatically open in pop-up windows.)

If there's no active session (we shut it down in step 5), [`Notebook.prototype.load_notebook_success`] looks first at the `kernel_name` URL parameter (which is included in the initial GET request to open a newly created notebook, but not when opening an existing one), then at `this.metadata.kernelspec` and `this.metadata.language`: if none of these values are truthy, it calls `this.start_session()` without providing a value for its `kernel_name` argument.

[`Notebook.prototype.start_session`] sets `this._session_starting = true` and later `this.session = new session.Session(options)`. `options` includes `kernel_name: kernel_name`, which was not provided for this call and so is undefined.

[`Session.prototype.start`] makes an [ajax POST request] to `this.session_service_url` with `data: JSON.stringify(this._get_model())`. `this._get_model()` has the value:

```javascript
{
path: "Untitled.ipynb",
type: "notebook",
name: "",
"kernel": {
id: null,
name: undefined
}
}
```

which stringifies as `"{"path":"Untitled.ipynb","type":"notebook","name":"","kernel":{"id":null}}"`.

Since the request did not specify a kernel name, the server starts up the default kernel.

(Okay, we're almost there!)

The [`on_success`] callback from `Session.prototype.start` receives the kernel data and sets `that.kernel`, then triggers a `kernel_created.Session` event.

`KernelSelector.prototype.bind_events` has registered a [callback for `kernel_created.Session`] which calls `that.set_kernel(data.kernel.name)`. `set_kernel` calls `_set_kernel`, which triggers a [`spec_changed.Kernel` event].

`Notebook.prototype.bind_events` has registered a [callback for `spec_changed.Kernel`] which calls `that.start_session(data.name)`.

`Notebook.prototype._session_started` hasn't been called yet (that happens [after the `kernel_created.Session` event]), so `this._session_starting` (set in the original `start_session` call) is still true; so this call to `start_session` [throws SessionAlreadyStarting].

(Whew!)

---

Like I said, I'm not sure where the actual error(s) happen(s) here: maybe the [callback for `spec_changed.Kernel`] should only start a new session if one isn't already starting?

I've confirmed that first checking `if (!that._session_starting)` prevents the `SessionAlreadyStarting` error, and allows the kernel indicator name to be properly updated -- BUT, the kernel created is still just the server's default: the kernel the client initially requested (via the `kernel_name` query parameter) was never saved, and has been lost. (See analysis of step 2 above: I'll probably write up a separate issue and/or PR for that.)

I'm not familiar enough with the overall architecture of this code to know whether that fix sounds correct; but if a project maintainer could give their feedback (and point me to the proper test suite to update), I'd be happy to submit a PR.

Thanks so much for your attention!

[`Notebook.prototype.load_notebook_success`]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/notebook/js/notebook.js#L3248-L3270

[`Notebook.prototype.start_session`]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/notebook/js/notebook.js#L2237-L2261

[`Session.prototype.start`]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/services/sessions/session.js#L102-L134

[ajax POST request]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/services/sessions/session.js#L124-L133

[`on_success`]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/services/sessions/session.js#L104-L116

[callback for `kernel_created.Session`]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/notebook/js/kernelselector.js#L338-L340

[`spec_changed.Kernel` event]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/notebook/js/kernelselector.js#L249

[callback for `spec_changed.Kernel`]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/notebook/js/notebook.js#L356

[after the `kernel_created.Session` event]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/services/sessions/session.js#L114

[throws SessionAlreadyStarting]: https://github.com/jupyter/notebook/blob/43df5af2b614088b4b297fae90a70b6505b9bf84/notebook/static/notebook/js/notebook.js#L2239

Contributor guide

Open the contributing guide

Research direction

Reproduce the six-step workflow, then read notebook/static/notebook/js/notebook.js, notebook/static/notebook/js/kernelselector.js, and notebook/static/services/sessions/session.js at the linked callbacks. Trace session_starting through kernel_created.Session and spec_changed.Kernel. Done means opening a notebook without kernelspec or language metadata no longer raises SessionAlreadyStarting, updates the kernel indicator, and uses the intended kernel behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, jupyter-notebook
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.