Skip to content

Commit 6846e5b

Browse files
DonJayamannerchiodo
authored andcommitted
Add ability to retry starting a session when starting a session fails (#9227)
For #9187 When starting a new session fails (e.g. due to bunked kernel or the like), allow user to recover by starting a new session with a different kernel. * Add ability to start retry starting sessions with different kernel * Fix message * Fixes * Address code review comments * Fix tests * Update labels
1 parent 4ab0e26 commit 6846e5b

13 files changed

Lines changed: 160 additions & 43 deletions

File tree

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"onCommand:python.viewLanguageServerOutput",
5555
"onCommand:python.viewTestOutput",
5656
"onCommand:python.viewOutput",
57+
"onCommand:python.datascience.viewJupyterOutput",
5758
"onCommand:python.selectAndRunTestMethod",
5859
"onCommand:python.selectAndDebugTestMethod",
5960
"onCommand:python.selectAndRunTestFile",
@@ -238,6 +239,11 @@
238239
"dark": "resources/dark/repl.svg"
239240
}
240241
},
242+
{
243+
"command": "python.datascience.viewJupyterOutput",
244+
"title": "%python.command.python.datascience.viewJupyterOutput.title%",
245+
"category": "Python"
246+
},
241247
{
242248
"command": "python.viewLanguageServerOutput",
243249
"title": "%python.command.python.viewLanguageServerOutput.title%",

package.nls.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"python.command.python.refactorExtractMethod.title": "Extract Method",
1515
"python.command.python.viewOutput.title": "Show Output",
1616
"python.command.python.viewTestOutput.title": "Show Test Output",
17+
"python.command.python.datascience.viewJupyterOutput.title": "Show Jupyter Output",
1718
"python.command.python.viewLanguageServerOutput.title": "Show Language Server Output",
1819
"python.command.python.selectAndRunTestMethod.title": "Run Test Method ...",
1920
"python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...",
@@ -221,6 +222,7 @@
221222
"DataScience.exportingFormat": "Exporting {0}",
222223
"DataScience.exportCancel": "Cancel",
223224
"Common.canceled": "Canceled",
225+
"Common.cancel": "Cancel",
224226
"DataScience.importChangeDirectoryComment": "{0} Change working directory from the workspace root to the ipynb file location. Turn this addition off with the DataScience.changeDirOnImportExport setting",
225227
"DataScience.exportChangeDirectoryComment": "# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting",
226228
"DataScience.interruptKernelStatus": "Interrupting IPython Kernel",
@@ -397,6 +399,8 @@
397399
"DataScience.fallBackToRegisterAndUseActiveInterpeterAsKernel": "Couldn't find kernel '{0}' that the notebook was created with. Registering a new kernel using the current interpreter.",
398400
"DataScience.fallBackToPromptToUseActiveInterpreterOrSelectAKernel": "Couldn't find kernel '{0}' that the notebook was created with.",
399401
"DataScience.selectKernel": "Select a Kernel",
402+
"DataScience.selectDifferentKernel": "Select a different Kernel",
400403
"products.installingModule": "Installing {0}",
401-
"DataScience.switchingKernelProgress": "Switching kernel to '{0}'"
404+
"DataScience.switchingKernelProgress": "Switching kernel to '{0}'",
405+
"DataScience.sessionStartFailedWithKernel": "Failed to start a session for the Kernel '{0}'. \nView Jupyter [log](command:{1}) for further details."
402406
}

src/client/common/application/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,5 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
137137
[DSCommands.DebugContinue]: [];
138138
[DSCommands.RunCurrentCellAndAddBelow]: [string];
139139
[DSCommands.ScrollToCell]: [string, string];
140+
[DSCommands.ViewJupyterOutput]: [];
140141
}

src/client/common/utils/localize.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export namespace Diagnostics {
2626

2727
export namespace Common {
2828
export const canceled = localize('Common.canceled', 'Canceled');
29+
export const cancel = localize('Common.cancel', 'Cancel');
2930
export const gotIt = localize('Common.gotIt', 'Got it!');
3031
export const loadingExtension = localize('Common.loadingPythonExtension', 'Python extension loading...');
3132
export const openOutputPanel = localize('Common.openOutputPanel', 'Show output');
@@ -164,7 +165,7 @@ export namespace DataScience {
164165
export const restartingKernelStatus = localize('DataScience.restartingKernelStatus', 'Restarting IPython Kernel');
165166
export const restartingKernelFailed = localize('DataScience.restartingKernelFailed', 'Kernel restart failed. Jupyter server is hung. Please reload VS code.');
166167
export const interruptingKernelFailed = localize('DataScience.interruptingKernelFailed', 'Kernel interrupt failed. Jupyter server is hung. Please reload VS code.');
167-
168+
export const sessionStartFailedWithKernel = localize('DataScience.sessionStartFailedWithKernel', 'Failed to start a session for the Kernel \'{0}\'. \nView Jupyter [log](command:{1}) for further details.');
168169
export const executingCode = localize('DataScience.executingCode', 'Executing Cell');
169170
export const collapseAll = localize('DataScience.collapseAll', 'Collapse all cell inputs');
170171
export const expandAll = localize('DataScience.expandAll', 'Expand all cell inputs');
@@ -228,6 +229,7 @@ export namespace DataScience {
228229
export const jupyterServer = localize('DataScience.jupyterServer', 'Jupyter Server');
229230
export const noKernel = localize('DataScience.noKernel', 'No Kernel');
230231
export const selectKernel = localize('DataScience.selectKernel', 'Select a Kernel');
232+
export const selectDifferentKernel = localize('DataScience.selectDifferentKernel', 'Select a different Kernel');
231233
export const localJupyterServer = localize('DataScience.localJupyterServer', 'local');
232234
export const pandasTooOldForViewingFormat = localize('DataScience.pandasTooOldForViewingFormat', 'Python package \'pandas\' is version {0}. Version 0.20 or greater is required for viewing data.');
233235
export const pandasRequiredForViewing = localize('DataScience.pandasRequiredForViewing', 'Python package \'pandas\' is required for viewing data.');

src/client/datascience/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export namespace Commands {
5858
export const RunCurrentCellAndAddBelow = 'python.datascience.runcurrentcellandaddbelow';
5959
export const ScrollToCell = 'python.datascience.scrolltocell';
6060
export const CreateNewNotebook = 'python.datascience.createnewnotebook';
61+
export const ViewJupyterOutput = 'python.datascience.viewJupyterOutput';
6162
}
6263

6364
export namespace CodeLensCommands {
@@ -107,7 +108,6 @@ export namespace RegExpValues {
107108
export const SvgWidthRegex = /(\<svg.*width=\")(.*?)\"/;
108109
export const SvgSizeTagRegex = /\<svg.*tag=\"sizeTag=\{(.*),\s*(.*)\}\"/;
109110
export const StyleTagRegex = /\<style[\s\S]*\<\/style\>/m;
110-
111111
}
112112

113113
export enum Telemetry {
@@ -154,6 +154,7 @@ export enum Telemetry {
154154
ConnectRemoteJupyter = 'DATASCIENCE.CONNECTREMOTEJUPYTER',
155155
ConnectFailedJupyter = 'DATASCIENCE.CONNECTFAILEDJUPYTER',
156156
ConnectRemoteFailedJupyter = 'DATASCIENCE.CONNECTREMOTEFAILEDJUPYTER',
157+
StartSessionFailedJupyter = 'DATASCIENCE.START_SESSION_FAILED_JUPYTER',
157158
ConnectRemoteSelfCertFailedJupyter = 'DATASCIENCE.CONNECTREMOTESELFCERTFAILEDJUPYTER',
158159
RegisterAndUseInterpreterAsKernel = 'DATASCIENCE.REGISTER_AND_USE_INTERPRETER_AS_KERNEL',
159160
UseInterpreterAsKernel = 'DATASCIENCE.USE_INTERPRETER_AS_KERNEL',

src/client/datascience/datascience.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
IDisposableRegistry,
1818
IExtensionContext,
1919
IMemento,
20+
IOutputChannel,
2021
IPythonExtensionBanner
2122
} from '../common/types';
2223
import { debounceAsync, swallowExceptions } from '../common/utils/decorators';
@@ -26,7 +27,7 @@ import { IServiceContainer } from '../ioc/types';
2627
import { captureTelemetry, sendTelemetryEvent } from '../telemetry';
2728
import { hasCells } from './cellFactory';
2829
import { getSavedUriList } from './common';
29-
import { Commands, EditorContexts, Settings, Telemetry } from './constants';
30+
import { Commands, EditorContexts, JUPYTER_OUTPUT_CHANNEL, Settings, Telemetry } from './constants';
3031
import { KernelSelector, KernelSpecInterpreter } from './jupyter/kernels/kernelSelector';
3132
import { LiveKernelModel } from './jupyter/kernels/types';
3233
import {
@@ -67,7 +68,8 @@ export class DataScience implements IDataScience {
6768
@inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: vscode.Memento,
6869
@inject(IJupyterSessionManagerFactory) private jupyterSessionManagerFactory: IJupyterSessionManagerFactory,
6970
@inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory,
70-
@inject(KernelSelector) private kernelSelector: KernelSelector
71+
@inject(KernelSelector) private kernelSelector: KernelSelector,
72+
@inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) private jupyterOutput: IOutputChannel
7173
) {
7274
this.dataScienceSurveyBanner = this.serviceContainer.get<IPythonExtensionBanner>(IPythonExtensionBanner, BANNER_NAME_DS_SURVEY);
7375
}
@@ -514,6 +516,7 @@ export class DataScience implements IDataScience {
514516
disposable = this.commandManager.registerCommand(Commands.DebugCurrentCellPalette, this.debugCurrentCellFromCursor, this);
515517
this.disposableRegistry.push(disposable);
516518
disposable = this.commandManager.registerCommand(Commands.CreateNewNotebook, this.createNewNotebook, this);
519+
disposable = this.commandManager.registerCommand(Commands.ViewJupyterOutput, this.viewJupyterOutput, this);
517520
this.disposableRegistry.push(disposable);
518521
if (this.commandListeners) {
519522
this.commandListeners.forEach((listener: IDataScienceCommandListener) => {
@@ -568,4 +571,7 @@ export class DataScience implements IDataScience {
568571
private async createNewNotebook(): Promise<void> {
569572
await this.notebookProvider.createNew();
570573
}
574+
private viewJupyterOutput() {
575+
this.jupyterOutput.show(true);
576+
}
571577
}

src/client/datascience/interactive-common/interactiveBase.ts

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
3333
import { generateCellRangesFromDocument } from '../cellFactory';
3434
import { CellMatcher } from '../cellMatcher';
3535
import { addToUriList } from '../common';
36-
import { Identifiers, Settings, Telemetry } from '../constants';
36+
import { Commands, Identifiers, Settings, Telemetry } from '../constants';
3737
import { ColumnWarningSize } from '../data-viewing/types';
3838
import { DataScience } from '../datascience';
3939
import {
@@ -50,6 +50,7 @@ import {
5050
} from '../interactive-common/interactiveWindowTypes';
5151
import { JupyterInstallError } from '../jupyter/jupyterInstallError';
5252
import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
53+
import { JupyterSessionStartError } from '../jupyter/jupyterSession';
5354
import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError';
5455
import { KernelSpecInterpreter } from '../jupyter/kernels/kernelSelector';
5556
import { CssMessages } from '../messages';
@@ -1307,8 +1308,8 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
13071308
const settings = this.configuration.getSettings();
13081309

13091310
let kernel: KernelSpecInterpreter | undefined;
1310-
1311-
if (settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch) {
1311+
const isLocalConnection = this.notebook?.server.getConnectionInfo()?.localLaunch ?? settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch;
1312+
if (isLocalConnection) {
13121313
kernel = await this.dataScience.selectLocalJupyterKernel(this.notebook?.getKernelSpec());
13131314
} else if (this.notebook) {
13141315
const connInfo = this.notebook.server.getConnectionInfo();
@@ -1319,28 +1320,66 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
13191320
}
13201321

13211322
if (kernel && (kernel.kernelSpec || kernel.kernelModel) && this.notebook) {
1322-
const switchKernel = async (newKernel: KernelSpecInterpreter) => {
1323-
if (newKernel.interpreter) {
1324-
this.notebook!.setInterpreter(newKernel.interpreter);
1323+
await this.switchKernelWithRetry(kernel);
1324+
}
1325+
}
1326+
private async switchKernelWithRetry(kernel: KernelSpecInterpreter){
1327+
const settings = this.configuration.getSettings();
1328+
const isLocalConnection = this.notebook?.server.getConnectionInfo()?.localLaunch ?? settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch;
1329+
if (!isLocalConnection){
1330+
return this.switchKernel(kernel);
1331+
}
1332+
1333+
// Keep retrying, until it works or user cancels.
1334+
// Sometimes if a bad kernel is selected, starting a session can fail.
1335+
// In such cases we need to let the user know about this and prompt them to select another kernel.
1336+
// tslint:disable-next-line: no-constant-condition
1337+
while (true) {
1338+
try {
1339+
await this.switchKernel(kernel);
1340+
} catch (ex) {
1341+
if (ex instanceof JupyterSessionStartError && isLocalConnection) {
1342+
// Looks like we were unable to start a session for the local connection.
1343+
// Possibly something wrong with the kernel.
1344+
// At this point we have a valid jupyter server.
1345+
const displayName = kernel.kernelSpec?.display_name || kernel.kernelModel?.display_name || kernel.kernelSpec?.name || kernel.kernelModel?.name || '';
1346+
const message = localize.DataScience.sessionStartFailedWithKernel().format(displayName, Commands.ViewJupyterOutput);
1347+
const selectKernel = localize.DataScience.selectDifferentKernel();
1348+
const cancel = localize.Common.cancel();
1349+
const selection = await this.applicationShell.showErrorMessage(message, selectKernel, cancel);
1350+
if (selection === selectKernel) {
1351+
kernel = await this.dataScience.selectLocalJupyterKernel(kernel.kernelSpec || kernel.kernelModel);
1352+
if (Object.keys(kernel).length > 0){
1353+
continue;
1354+
}
1355+
}
13251356
}
1357+
throw ex;
1358+
}
1359+
}
1360+
}
1361+
private async switchKernel(kernel: KernelSpecInterpreter): Promise<void> {
1362+
const switchKernel = async (newKernel: KernelSpecInterpreter) => {
1363+
// Change the kernel. A status update should fire that changes our display
1364+
await this.notebook!.setKernelSpec(newKernel.kernelSpec || newKernel.kernelModel!, this.configService.getSettings().datascience.jupyterLaunchTimeout);
13261365

1327-
// Change the kernel. A status update should fire that changes our display
1328-
await this.notebook!.setKernelSpec(newKernel.kernelSpec || newKernel.kernelModel!, this.configService.getSettings().datascience.jupyterLaunchTimeout);
1366+
if (newKernel.interpreter) {
1367+
this.notebook!.setInterpreter(newKernel.interpreter);
1368+
}
13291369

1330-
// Add in a new sys info
1331-
await this.addSysInfo(SysInfoReason.New);
1332-
};
1370+
// Add in a new sys info
1371+
await this.addSysInfo(SysInfoReason.New);
1372+
};
13331373

1334-
const kernelDisplayName = kernel.kernelSpec?.display_name || kernel.kernelModel?.display_name;
1335-
const kernelName = kernel.kernelSpec?.name || kernel.kernelModel?.name;
1336-
// One of them is bound to be non-empty.
1337-
const displayName = kernelDisplayName || kernelName || '';
1338-
const options: ProgressOptions = {
1339-
location: ProgressLocation.Notification,
1340-
cancellable: false,
1341-
title: localize.DataScience.switchingKernelProgress().format(displayName)
1342-
};
1343-
await this.applicationShell.withProgress(options, async (_, __) => switchKernel(kernel!));
1344-
}
1374+
const kernelDisplayName = kernel.kernelSpec?.display_name || kernel.kernelModel?.display_name;
1375+
const kernelName = kernel.kernelSpec?.name || kernel.kernelModel?.name;
1376+
// One of them is bound to be non-empty.
1377+
const displayName = kernelDisplayName || kernelName || '';
1378+
const options: ProgressOptions = {
1379+
location: ProgressLocation.Notification,
1380+
cancellable: false,
1381+
title: localize.DataScience.switchingKernelProgress().format(displayName)
1382+
};
1383+
await this.applicationShell.withProgress(options, async (_, __) => switchKernel(kernel!));
13451384
}
13461385
}

0 commit comments

Comments
 (0)