-
Notifications
You must be signed in to change notification settings - Fork 949
Allows lab manager to load state from kernel/notebook #2265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
One issue for a TODO:
|
Note, this resolves a TODO on |
…gister the jlab widgets.
… reused, we use the same widget manager. This makes widgets work across multiple views of a notebook, since they all share the same widget manager.
…endering widgets until the appropriate widget manager is set. This allows a notebook to open and start ‘rendering’ widgets as soon as it opens, and the real widget rendering will actually happen asynchronously after the widget manager is created.
Unfortunately, this makes it backwards incompatible, which technically means we should bump the base package up a major version number. It feels like we should have a bigger carrot for a major version bump in the base package. How about we separate these two changes, so we can merge the jlab manager changes in immediately with essentially no change in base, and we take up fixing the get_model function in the next base major release? (I'm okay with the typings enhancements you did in base, as well as the function you added, so I imagine this would be a minor base version bump, just not a major one yet.) |
The reason I think we should have a bigger reason to bump a major version is that a major bump of base requires everyone to release their widgets again (presuming they use the standard Maybe this conflation points us to having the widget manager base as a separate package from the widgets base package in some future version. |
@vidartf - FYI, I'm working on further enhancements to this PR... |
@jasongrout OK, give me a ping when you are satisfied, and I'll have a look. |
Move to base manager so it can be reused.
Remaining, know issues: - When restarting kernel, widgets are not set as disconnected. - Maybe a problem of deadlock for interdependent widgets? Maybe it was like that from before?
Calling code should handle the case where get_model returns a rejected promise, so subclasses may still choose to return a rejected promise if the model is not found.
* Convert many functions to use async/await * Rework the restoring promise and initial restore logic
I rebased on master to pull in changes needed to work with the current jlab prerelease. |
If the widget save setting is 'false', should we clear the existing widget state in the notebook, or just leave it alone? Or perhaps we should clear it from the model after we load it? Also, does it seem reasonable to only save live widgets? This may be a good first attempt. This may mean that opening a notebook with saved state, running just a few cells, and saving means that other cells with saved widgets then stop working. Perhaps it's better to somehow save live widgets plus any widgets referenced from a view (hard)? Or perhaps any widgets that have been displayed at least once (again, probably hard)? Or perhaps we just have an option to save all widget state, including unseen and unused widgets (easy, but confusing, I think)? |
And another possible TODO: right now, if we can't find a widget model, we just leave a "Loading widget..." message. Perhaps we should start a timeout when we do that, so that if the model doesn't load after a while (5 seconds?), we just explain there is an error, rather than having the user keep waiting for the widget to load. |
I did this in the latest commit. |
Wrapping this up until we can discuss with @vidartf (or whoever else wants to review it). This I think requires a minor version bump in base and a major version bump in jlab manager. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few points to discuss.
// For any widgets that have already been rendered with the placeholder, set | ||
// the renderer. This iteration structure is closely tied to the structure of | ||
// the notebook widget. | ||
nb.widgets.forEach(cell => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop is tied to the DOM, and won't pick up on e.g. "New view for output" that can be restored on layout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just pushed a commit that (a) refactors the awkward logic into a self-contained iterator, and (b) attempts to also iterate through linked views, using even more awkward logic that relies on specific jlab details. It's hard for me to test, though, since linked output views aren't restored in the latest jlab release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(note that both of these awkward iterators are just an interim solution until there might be a more supported clean way of iterating through renderers in jlab itself)
* Set the dirty state of the notebook model if applicable. | ||
* | ||
* TODO: should also set dirty when auto-save state is turned on (easy), and when any | ||
* model changes any data (likely hard right now). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: Just before merging, if this TODO note is still here, it should be replaced by an issue.
Not really, as widgets in the state will be restored (will become live). Your scenario will only happen if you are working super fast (and/or the restore is super slow). Still, it could leave to some weird behavior. |
// error message to the user. | ||
setTimeout(() => { | ||
if (this._rerenderMimeModel) { | ||
this.node.textContent = 'Error displaying widget: model not found'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should tweak this error message to indicate that, while no actual error has occurred, getting the widget state is taking a lot longer than expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a commit that tries to make the error messages more intelligent, based on whether the manager has restored its status or not.
…r functions. Also, loop through all output views that have been created.
@vidartf - I think I addressed the issues you raised above. Thoughts now? |
I was talking about dead widgets that only live in the notebook metadata, not restored widgets from the kernel. |
I was mistaken about this, widgets in the notebook without a kernel equivalent, will not become live (they won't get a comm). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add some minor typings, and also have a few minor points that should be checked before merging. @jasongrout Would you mind checking those?
_handleKernelStatusChange(args: Kernel.Status) { | ||
switch (args) { | ||
case 'connected': | ||
// TODO: should we clear away any old widgets before restoring? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we answer this? If so, we should change this comment to explain what we chose (and why).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also reset this._restoredStatus
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the only thing left to resolve here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so. I.e. both the TODO, and reviewing if this._restoredStatus
should ever be reset (and if so, when and where).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO: should we clear away any old widgets before restoring?
YES
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO: should we clear away any old widgets before restoring?
YES
😏 we were discussing this in person with @jasongrout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and reset the restored status in disconnect. I think it makes most sense to reset it there, since a reconnect will then restore widgets again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this actually done? Since you force pushed, the commit log doesn't show any partial diffs for this, so its super hard to track what was changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, and in general this PR was already hard to track being so long and involved. Are you asking if the TODO was fixed, or if the restored status was reset?
@@ -78,7 +98,7 @@ class BackboneViewWrapper extends Widget { | |||
*/ | |||
export | |||
class WidgetManager extends ManagerBase<Widget> implements IDisposable { | |||
constructor(context: DocumentRegistry.IContext<DocumentRegistry.IModel>, rendermime: RenderMimeRegistry) { | |||
constructor(context: DocumentRegistry.IContext<INotebookModel>, rendermime: RenderMimeRegistry, settings: WidgetManager.Settings) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for self: support the second argument in Voila's widget manager.
_handleKernelStatusChange(args: Kernel.Status) { | ||
switch (args) { | ||
case 'connected': | ||
// TODO: should we clear away any old widgets before restoring? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO: should we clear away any old widgets before restoring?
YES
_handleKernelStatusChange(args: Kernel.Status) { | ||
switch (args) { | ||
case 'connected': | ||
// TODO: should we clear away any old widgets before restoring? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO: should we clear away any old widgets before restoring?
YES
😏 we were discussing this in person with @jasongrout.
async clear_state(): Promise<void> { | ||
await super.clear_state(); | ||
this._modelsSync = new Map(); | ||
this.setDirty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- note: clear_state and register_model seem to be overriden just for the
_modelsSync
andsetDirty()
settings.
if (modelPromise === undefined) { | ||
throw new Error('widget model not found'); | ||
} | ||
return modelPromise; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for self: seem to be overriden to make the function trully async and not be able to return undefined.
*/ | ||
async restoreWidgets(notebook: INotebookModel): Promise<void> { | ||
await this._loadFromKernel(); | ||
await this._loadFromNotebook(notebook); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- note for self:
restoreWidgets
should be overridden invoila
.
await this._loadFromKernel(); | ||
await this._loadFromNotebook(notebook); | ||
this._restoredStatus = true; | ||
this._restored.emit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be two signals for _restoredFromKernel
and _restoredFromNotebookStatus
. The former would be set to false
in disconnect?
This didn’t get pushed to the PR to be included.
To answer the conversation above: should we clear away old widgets before restoring? Actually, since we're only storing live widget models right, I think it doesn't hurt to leave old widget models in - we won't get file bloating from disconnected widgets. |
When connecting to a new kernel in lab, this code now:
This attempts to mirror the logic in the widgetsnbextension code, and mostly the code is a hard copy (not DRY as such, and maintainers should note the possible need to keep these in sync in the future).