Skip to content

Commit 66a4ffb

Browse files
authored
Refactor cell execution using IKernel (microsoft#13402)
For #12189 * The kernel information returned to VSC from kernel provider now returns a stateless class * No more validation is performed when a kernel is changed (this happens when executing code) * Cleaned up IKernel interface * Move all execution of code & managing state of cells and notebook into IKernel class Hence deleted the old ExecutionService
1 parent 032ebce commit 66a4ffb

22 files changed

+770
-690
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { CancellationToken, NotebookCell, NotebookCellRunState } from 'vscode';
7+
import { createDeferred } from '../../../common/utils/async';
8+
import { StopWatch } from '../../../common/utils/stopWatch';
9+
import { sendTelemetryEvent } from '../../../telemetry';
10+
import { Telemetry } from '../../constants';
11+
import { updateCellWithErrorStatus } from '../../notebook/helpers/executionHelpers';
12+
import {
13+
clearCellForExecution,
14+
getCellStatusMessageBasedOnFirstCellErrorOutput,
15+
updateCellExecutionTimes
16+
} from '../../notebook/helpers/helpers';
17+
import { MultiCancellationTokenSource } from '../../notebook/helpers/multiCancellationToken';
18+
import { NotebookEditor } from '../../notebook/notebookEditor';
19+
import { INotebookContentProvider } from '../../notebook/types';
20+
import { IDataScienceErrorHandler, INotebookEditorProvider } from '../../types';
21+
// tslint:disable-next-line: no-var-requires no-require-imports
22+
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
23+
24+
export class CellExecutionFactory {
25+
constructor(
26+
private readonly contentProvider: INotebookContentProvider,
27+
private readonly errorHandler: IDataScienceErrorHandler,
28+
private readonly editorProvider: INotebookEditorProvider
29+
) {}
30+
31+
public create(cell: NotebookCell) {
32+
// tslint:disable-next-line: no-use-before-declare
33+
return CellExecution.fromCell(cell, this.contentProvider, this.errorHandler, this.editorProvider);
34+
}
35+
}
36+
/**
37+
* Responsible for execution of an individual cell and manages the state of the cell as it progresses through the execution phases.
38+
* Execution phases include - enqueue for execution (done in ctor), start execution, completed execution with/without errors, cancel execution or dequeue.
39+
*/
40+
export class CellExecution {
41+
public get result(): Promise<NotebookCellRunState | undefined> {
42+
return this._result.promise;
43+
}
44+
45+
public get token(): CancellationToken {
46+
return this.source.token;
47+
}
48+
49+
public get completed() {
50+
return this._completed;
51+
}
52+
53+
private static sentExecuteCellTelemetry?: boolean;
54+
55+
private readonly oldCellRunState?: NotebookCellRunState;
56+
57+
private stopWatch = new StopWatch();
58+
59+
private readonly source = new MultiCancellationTokenSource();
60+
61+
private readonly _result = createDeferred<NotebookCellRunState | undefined>();
62+
63+
private started?: boolean;
64+
65+
private _completed?: boolean;
66+
67+
private constructor(
68+
public readonly cell: NotebookCell,
69+
private readonly contentProvider: INotebookContentProvider,
70+
private readonly errorHandler: IDataScienceErrorHandler,
71+
private readonly editorProvider: INotebookEditorProvider
72+
) {
73+
this.oldCellRunState = cell.metadata.runState;
74+
this.enqueue();
75+
}
76+
77+
public static fromCell(
78+
cell: NotebookCell,
79+
contentProvider: INotebookContentProvider,
80+
errorHandler: IDataScienceErrorHandler,
81+
editorProvider: INotebookEditorProvider
82+
) {
83+
return new CellExecution(cell, contentProvider, errorHandler, editorProvider);
84+
}
85+
86+
public start() {
87+
this.started = true;
88+
// Ensure we clear the cell state and trigger a change.
89+
clearCellForExecution(this.cell);
90+
this.cell.metadata.runStartTime = new Date().getTime();
91+
this.stopWatch.reset();
92+
// Changes to metadata must be saved in ipynb, hence mark doc has dirty.
93+
this.contentProvider.notifyChangesToDocument(this.cell.notebook);
94+
this.notifyCellExecution();
95+
}
96+
97+
/**
98+
* Cancel execution.
99+
* If execution has commenced, then interrupt (via cancellation token) else dequeue from execution.
100+
*/
101+
public cancel() {
102+
// We need to notify cancellation only if execution is in progress,
103+
// coz if not, we can safely reset the states.
104+
if (this.started && !this._completed) {
105+
this.source.cancel();
106+
}
107+
108+
if (!this.started) {
109+
this.dequeue();
110+
}
111+
this._result.resolve(this.cell.metadata.runState);
112+
}
113+
114+
public completedWithErrors(error: Partial<Error>) {
115+
this.sendPerceivedCellExecute();
116+
this.cell.metadata.lastRunDuration = this.stopWatch.elapsedTime;
117+
updateCellWithErrorStatus(this.cell, error);
118+
this.contentProvider.notifyChangesToDocument(this.cell.notebook);
119+
this.errorHandler.handleError((error as unknown) as Error).ignoreErrors();
120+
121+
this._completed = true;
122+
this._result.resolve(this.cell.metadata.runState);
123+
// Changes to metadata must be saved in ipynb, hence mark doc has dirty.
124+
this.contentProvider.notifyChangesToDocument(this.cell.notebook);
125+
}
126+
127+
public completedSuccessfully() {
128+
this.sendPerceivedCellExecute();
129+
// If we requested a cancellation, then assume it did not even run.
130+
// If it did, then we'd get an interrupt error in the output.
131+
this.cell.metadata.runState = this.token.isCancellationRequested
132+
? vscodeNotebookEnums.NotebookCellRunState.Idle
133+
: vscodeNotebookEnums.NotebookCellRunState.Success;
134+
135+
this.cell.metadata.statusMessage = '';
136+
this.cell.metadata.lastRunDuration = this.stopWatch.elapsedTime;
137+
updateCellExecutionTimes(this.cell, {
138+
startTime: this.cell.metadata.runStartTime,
139+
duration: this.cell.metadata.lastRunDuration
140+
});
141+
// If there are any errors in the cell, then change status to error.
142+
if (this.cell.outputs.some((output) => output.outputKind === vscodeNotebookEnums.CellOutputKind.Error)) {
143+
this.cell.metadata.runState = vscodeNotebookEnums.NotebookCellRunState.Error;
144+
this.cell.metadata.statusMessage = getCellStatusMessageBasedOnFirstCellErrorOutput(this.cell.outputs);
145+
}
146+
147+
this._completed = true;
148+
this._result.resolve(this.cell.metadata.runState);
149+
// Changes to metadata must be saved in ipynb, hence mark doc has dirty.
150+
this.contentProvider.notifyChangesToDocument(this.cell.notebook);
151+
}
152+
153+
/**
154+
* Notify other parts of extension about the cell execution.
155+
*/
156+
private notifyCellExecution() {
157+
const editor = this.editorProvider.editors.find((e) => e.file.toString() === this.cell.notebook.uri.toString());
158+
if (!editor) {
159+
throw new Error('No editor for Model');
160+
}
161+
if (editor && !(editor instanceof NotebookEditor)) {
162+
throw new Error('Executing Notebook with another Editor');
163+
}
164+
editor.notifyExecution(this.cell.document.getText());
165+
}
166+
167+
/**
168+
* This cell will no longer be processed for execution (even though it was meant to be).
169+
* At this point we revert cell state & indicate that it has nto started & it is not busy.
170+
*/
171+
private dequeue() {
172+
if (this.oldCellRunState === vscodeNotebookEnums.NotebookCellRunState.Running) {
173+
this.cell.metadata.runState = vscodeNotebookEnums.NotebookCellRunState.Idle;
174+
} else {
175+
this.cell.metadata.runState = this.oldCellRunState;
176+
}
177+
this.cell.metadata.runStartTime = undefined;
178+
this._completed = true;
179+
this._result.resolve(this.cell.metadata.runState);
180+
// Changes to metadata must be saved in ipynb, hence mark doc has dirty.
181+
this.contentProvider.notifyChangesToDocument(this.cell.notebook);
182+
}
183+
184+
/**
185+
* Place in queue for execution with kernel.
186+
* (mark it as busy).
187+
*/
188+
private enqueue() {
189+
this.cell.metadata.runState = vscodeNotebookEnums.NotebookCellRunState.Running;
190+
this.contentProvider.notifyChangesToDocument(this.cell.notebook);
191+
}
192+
193+
private sendPerceivedCellExecute() {
194+
const props = { notebook: true };
195+
if (!CellExecution.sentExecuteCellTelemetry) {
196+
CellExecution.sentExecuteCellTelemetry = true;
197+
sendTelemetryEvent(Telemetry.ExecuteCellPerceivedCold, this.stopWatch.elapsedTime, props);
198+
} else {
199+
sendTelemetryEvent(Telemetry.ExecuteCellPerceivedWarm, this.stopWatch.elapsedTime, props);
200+
}
201+
}
202+
}

0 commit comments

Comments
 (0)