-
Notifications
You must be signed in to change notification settings - Fork 305
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
execute hidden code with the executionQueue #7763
Conversation
const stopWatch = new StopWatch(); | ||
const notebookPromise = this.startNotebook(); | ||
const promise = notebookPromise.then((nb) => executeSilently(nb.session, code)); | ||
|
||
const promise = this.kernelExecution.executeHidden(notebookPromise, code, doc); |
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 do this for all the uses of executeSilently
?
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 we should not, this has the potential to break variable viewer and others.
I think we'd should have two methods.
And we might want to rename the executeCell to queueAndExecuteCell,
Similarly executeHidden and queueAndExeceuteHidden
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.
We'd should not because if we have 10 cells and we're thinking all, then variables will never refresh because those hidden requests will get queued and run only after all 10 cells finish. Similarly plenty of other pitfalls.
Never this new method should be banned to make it obvious how the code will be executed, ie the fact that it's queued.
@@ -147,7 +147,7 @@ export interface IKernel extends IAsyncDisposable { | |||
interrupt(): Promise<InterruptResult>; | |||
restart(): Promise<void>; | |||
executeCell(cell: NotebookCell): Promise<NotebookCellRunState>; | |||
executeHidden(code: string): Promise<nbformat.IOutput[]>; | |||
executeHidden(code: string, doc: NotebookDocument): Promise<nbformat.IOutput[]>; |
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.
We don't need to pass the document
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.
It's already available in the class
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.
We need it here to get the executionQueue
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 it in the class, there's documentExecutions
which uses documents as keys, but there's no document in the class
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.
Check line 113 of kernel.ts file
} | ||
const cellExecution = this.executionFactory.create(cell, this.metadata); | ||
this.queueOfCellsToExecute.push(cellExecution); | ||
public queueCell(cell?: NotebookCell, code?: string): void { |
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 api allows me to pass two empty arguments.
It should instead be code : NotebookCell | string
This way it's impossible to pass undefined and it's obvious what's expected without having to look at the 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.
I think it's better to return a promise from this method, then we won't need waitForHidden.
Else you have two methods to deal with.
interface Execution { | ||
cellExecution?: CellExecution; | ||
codeExecution?: CodeExecution; | ||
} |
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 means we can have a cake with both properties as empty, which is not correct.
We should change this to type Execution = CellExecution | CodeExecution
this.queueToExecute.push({ codeExecution }); | ||
|
||
traceInfo('Hidden cell queued for execution', codeExecution.code.substring(0, 50)); | ||
} | ||
|
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.
We have missed an else clause and that results in bugs, the suggestion I made will ensure typescript types will prevent such bugs
traceCellMessage(toExecute.cellExecution.cell, 'Before Execute individual cell'); | ||
} else if (toExecute.codeExecution) { | ||
traceInfo('Before Execute hidden code', toExecute.codeExecution.code.substring(0, 50)); | ||
} |
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.
We have missed an else clause and that results in bugs, the suggestion I made will ensure typescript types will prevent such bugs
Codecov Report
@@ Coverage Diff @@
## main #7763 +/- ##
======================================
- Coverage 68% 68% -1%
======================================
Files 363 364 +1
Lines 22593 22714 +121
Branches 3437 3456 +19
======================================
+ Hits 15530 15607 +77
- Misses 5725 5756 +31
- Partials 1338 1351 +13
|
if (item.cellExecution) { | ||
void item.cellExecution.cancel(forced); | ||
} else { | ||
void item.codeExecution?.cancel(forced); |
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.
We must await, that's why we have promise.all
|
||
return Promise.all(cellsToCheck.map((cell) => cell.result)); | ||
} | ||
public async waitForHiddenOutput(code: string): Promise<nbformat.IOutput[]> { |
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 won't work if we have multiple expositions with the same code, e.g. pass
or the like..
It will return the result from the first matching Execution with the same code
const execution = queue.find((exec) => exec.code === code); | ||
|
||
if (execution) { | ||
return Promise.resolve(execution.output); |
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.
Promise.resolve is unnecessary
} | ||
const cellExecution = this.executionFactory.create(cell, this.metadata); | ||
this.queueOfCellsToExecute.push(cellExecution); | ||
public queueCell(code: NotebookCell | string): Promise<nbformat.IOutput[]> { |
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 couldn't figure out how to return both types (Promise<NotebookCellRunState[] | nbformat.IOutput[]>
) here and then use instanceof nbformat.IOutput[]
on kernelExecution.ts
.
If you do, let me know @DonJayamanne
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'd created two methods
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.
Yeah, and as is the naming is quite awkward here at least in my view. It's called queueCell, but it takes a parameter called 'code' now not cell. And then the paths below are harder to read and easy to miss whens on. @DonJayamanne 's suggestion is good. If it was one method it would be better with a more explicit name like queueExecution, then casting early to either CodeExecution or CellExecution so that the code after that is easier to read, but two methods sounds like a legit idea.
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.
+1 on a separate method
@@ -7,7 +7,7 @@ import { IKernelDebugAdapterConfig, KernelDebugMode } from '../types'; | |||
|
|||
export async function isUsingIpykernel6OrLater(kernel: IKernel): Promise<boolean> { | |||
const code = 'import ipykernel\nprint(ipykernel.__version__)'; | |||
const output = await kernel.executeHidden(code); | |||
const output = await kernel.queueAndExeceuteHidden(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.
This should not be queued, only the debugger start method should be queued, all other (older) methods that used hidden Execution b should not have to be queued
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.
Not queuing that is the root of the issue
@@ -132,4 +171,28 @@ export class CellExecutionQueue { | |||
} | |||
} | |||
} | |||
|
|||
private getCellExecutions(executionList: Execution[]): CellExecution[] { |
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.
Not a perf difference I believe, but this could look cleaner just using .filter
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 agree, personally we don't need these two methods, i'd just move the filtering into the place where we call this method, else we just iterate throught the same set of list twice.
} | ||
|
||
/** | ||
* Responsible for execution of an individual cell and manages the state of the cell as it progresses through the execution phases. |
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 picky, but that's kinda how I am 😆 . The comments here look copied over from the other class, they should be updated for the new class (i.e. this is not responsible for execution of an individual cell).
* Further details here https://github.com/microsoft/vscode-jupyter/issues/232 & https://github.com/jupyter/jupyter_client/issues/297 | ||
* | ||
*/ | ||
export class CodeExecution implements IDisposable { |
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.
It seems weird that we'd need an entirely new class just to execute a string instead of a notebook cell. Could CellExecution not use this instead?
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.
Meaning derive from it or use this object internally to do execution?
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.
Valid request, however I think it gets way too messy (lots of if/else), i personally think this is cleaner than trying to get a single class to do both.
Also i don't think execute hidden will require execution of code that has widgets & other complexities.
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 not a single class. The other class would use this one. This one knows only about code.
The other one uses this class internally to actually do an execute. It just listens to the promise and updates the cell outputs on the cell.
Otherwise we end up with the same code doing iopub listening and the potential for having to fix things in two spots if something is broken.
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.
The other class would use this one. This one knows only about code.
I'm still not convinced, this is simple and small, it just gets messy. We don't need widgets, etc, hence this code is sufficient as is.
code doing iopub listening and the potential for having to fix things in two
I'd perfer to refactor when that happens, right now this feels much simpler to me, but thats just me.
Please add me as a reviewer on PRs like this so I get a notification. I don't quite follow this though, how does it actually queue RBL? |
Assume we have 10 cell and we're hit run all, then we hit RBL for the last cell, ideally what should happen (i plan on bringing this up in standup to discuss expected behavior) is we should start RBL after the 10 cells. The down side of this is, RBL could start 10 minutes after you clicked RBL, (i'll be bringing up this issue & a few others related to RBL in standup) |
As we discussed on standup, we'll wait for after release for this. And there's a chance we'll drop this and just not allow run by line/debugging when the kernel is running. |
@DonJayamanne I get it, you're describing how queueing should work, I don't see how this PR implements that. It seems like individual executions will be queued but not the operation of starting RBL itself. But anyway, like we discussed we can revisit it later |
I think we should re-vist this, I"m actually keen on taking some of this work, as it ensures hidden cells & others all go through the same queu, there are times when they must (& that's what this PR was trying to address) Will leave open for now, will create an issue so we track this as an engineering/debt task |
For #7740
package-lock.json
has been regenerated by runningnpm install
(if dependencies have changed).