-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
WIP Make kernel message handling async and in order #4697
Conversation
40e6302
to
13e9d51
Compare
Rebased on current master so the test output is cleaner, without spurious warnings. |
…serving message order. Fixes jupyterlab#4188
… processing of old kernel messages in the async queue.
13e9d51
to
e905dd8
Compare
…ore like an assert.
This also fixes at least one bug, where the shutdownAll was resolving before each shutdown actually resolved.
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.
After an initial pass, this is looking to be in good shape. My main takeaway is how much easier some of this stuff is to read with async/await.
I'm glad to see tests on your TODOs, there are a lot of API changes here.
try { | ||
continueHandling = hook(msg); | ||
continueHandling = await hook(msg); |
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.
My oh my, is chaining promises easier to read with async
/await
.
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.
:)
* provide a removeCommTarget function instead of returning a disposable. | ||
* Presumably it's just as easy for someone to store the comm target name as | ||
* it is to store the disposable. Since there is only one callback, you don't even | ||
* need to store the callback. |
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.
One very concrete difference between a remove
method vs the disposable approach is that with the latter, it is impossible for anybody other than the initial registrar to remove the item. If you don't have access to the disposable, you will never be able to delete a target. With an ID, it is possible for anybody to delete it by ID.
It may not matter much here, since the IDs are opaque and non-readable. The disposable pattern has rather large consequences for the commands and keyboard shortcuts, however.
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.
Good point, thanks for the reminder. I think you're probably right that it should not have any effect here.
*/ | ||
connectToComm(targetName: string, commId?: string): Promise<Kernel.IComm>; | ||
connectToComm(targetName: string, commId?: string): Kernel.IComm; |
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.
Why did this function become synchronous?
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.
There's nothing asynchronous about it anymore. Since comms messages are now processed asynchronously, the actuals comms themselves are stored, rather than just promises to an eventually-created comm. So I can return the actual comm, not just a promise to a comm.
} | ||
|
||
/** | ||
* Connect to a running kernel. | ||
* | ||
* TODO: why is this function async? |
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'm not sure, but that fact has made some of the kernel help links in the menus a pain to add.
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'm okay with making it sync, I think. I put the TODO there to come back to it after mulling over it/sleeping on it :).
Is the |
It's just good practice. I don't care if it is actually a promise, just that it is resolvable. For example, all the TS typings for resolve, etc., use PromiseLike. |
…eleting one from the list.
This is so that code waiting on, for example, a status message will still resolve, especially in the case that the messages were buffered into another session. Any message handling that may require the current session (such as creating comms) should check to see if the message being handled is current.
It’s still a bit puzzling what is happening - it appears that the notebook is dropping messages from the kernel. More details at jupyter/notebook#3705.
@ian-r-rose - I added a long test to test that all of the message handling is asynchronous and in order! |
* See [Update Display data](https://jupyter-client.readthedocs.io/en/latest/messaging.html#update-display-data). | ||
*/ | ||
export | ||
interface IUpdateDisplayDataMsg extends IDisplayDataMsg { |
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 something that can be simplified with conditional types?
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 suppose we could use a MakeRequired typing thing, but I think it's simpler to be more explicit here.
@@ -1451,7 +1451,7 @@ namespace Private { | |||
// We might want to move the handleRestart to after we get the response back | |||
|
|||
// Handle the restart on all of the kernels with the same id. | |||
each(runningKernels, k => { | |||
each(runningKernels.slice(), k => { |
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 don't see the disposal possibility here.
@@ -1463,7 +1463,7 @@ namespace Private { | |||
let data = await response.json(); | |||
validate.validateModel(data); | |||
// Reconnect the other kernels asynchronously, but don't wait for them. | |||
each(runningKernels, k => { | |||
each(runningKernels.slice(), k => { |
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.
Or here.
}); | ||
|
||
}); | ||
|
||
context('handles messages asynchronously', () => { |
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 is quite the test.
|
||
|
||
// TODO: Check tests for any kernels that are disposed before done | ||
// let status = []; |
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.
Looks like leftover console logs.
packages/services/test/src/utils.ts
Outdated
export | ||
async function testResolveOrder(promises: PromiseLike<any>[], resolutions: any[]): Promise<void> { | ||
// We construct all of the races synchronously so that the winner can truly be determined | ||
let subsequences = promises.map((value, index) => Promise.race(promises.slice(index))); |
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 learned about Promise.race
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.
:)
* false if the promise is still pending. | ||
*/ | ||
export | ||
async function isFulfilled<T>(p: PromiseLike<T>): Promise<boolean> { |
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.
Very clever. I had thought there was no way to do this with JS. At least it's not synchronous, or else my whole world view would be challenged.
packages/services/test/src/utils.ts
Outdated
} | ||
} catch (e) { | ||
done.reject(e); | ||
// throw e; |
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.
Dead code.
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.
deleted
done.resolve(options.value || undefined); | ||
} | ||
}, object); | ||
return done.promise; |
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.
Why does this function need to be asychronous?
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 added a comment. Basically, the thing that effected the signal may be asynchronous, so this allows that thing to happen.
this._futures.forEach(future => { future.dispose(); }); | ||
this._comms.forEach(comm => { comm.dispose(); }); | ||
this._kernelSession = ''; | ||
this._msgChain = null; |
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.
How close are we to enabling strict null checks? Should we type msgChain
as Promise<void> | null
?
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.
done.
@@ -17,19 +17,30 @@ import { | |||
KernelMessage | |||
} from './messages'; | |||
|
|||
// from Phosphor | |||
export |
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.
Does this need to be exported?
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.
no. Fixed in the latest commit I just pushed.
* | ||
* #### NOTES | ||
* We can't just use requestAnimationFrame since it is not available in our | ||
* testing setup. This implementation is from Phosphor: |
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 would argue it's not just for testing: we want services
to work in a node environment.
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.
Changing comment, then...you're right, it's really node that we're talking about here.
Okay, let's merge and move on to the follow ups. Thanks for the herculean effort @jasongrout! |
Fixes #4790 |
Fixes #4188