Opening notebook without kernelspec/language metadata causes SessionAlreadyStarting error
- 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.


---
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
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