GeezerVison is showing github.com — Use daemon for starting jupyter notebooks (#8543) · joyni/vscode-python@77866a1
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +1Lines changed: 1 addition & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Improve startup performance of Jupyter by using a Python daemon. |
Collapse file Copy file name to clipboardExpand all lines: pythonFiles/datascience/jupyter_daemon.py +9Lines changed: 9 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -32,6 +32,8 @@ def m_exec_module(self, module_name, args=[], cwd=None, env=None): | 32 | 32 | return self._execute_and_capture_output(self._print_kernel_list) | 33 | 33 | elif module_name == "jupyter" and args == ["kernelspec", "--version"]: | 34 | 34 | return self._execute_and_capture_output(self._print_kernelspec_version) | | 35 | + elif module_name == "jupyter" and args[0] == "nbconvert" and args[-1] != "--version": | | 36 | + return self._execute_and_capture_output(lambda : self._convert(args)) | 35 | 37 | else: | 36 | 38 | log.info("check base class stuff") | 37 | 39 | return super().m_exec_module(module_name, args, cwd, env) | @@ -87,6 +89,13 @@ def _print_kernel_list(self): | 87 | 89 | ) | 88 | 90 | sys.stdout.flush() | 89 | 91 |
| | 92 | + def _convert(self, args): | | 93 | + log.info("nbconvert") | | 94 | + from nbconvert import nbconvertapp as app | | 95 | + | | 96 | + sys.argv = [""] + args | | 97 | + app.main() | | 98 | + | 90 | 99 | def _start_notebook(self, args): | 91 | 100 | from notebook import notebookapp as app | 92 | 101 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/client/common/process/pythonExecutionFactory.ts +6-3Lines changed: 6 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | import { inject, injectable } from 'inversify'; | 4 | 4 |
| 5 | 5 | import { IEnvironmentActivationService } from '../../interpreter/activation/types'; | | 6 | +import { IInterpreterService } from '../../interpreter/contracts'; | 6 | 7 | import { WindowsStoreInterpreter } from '../../interpreter/locators/services/windowsStoreInterpreter'; | 7 | 8 | import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; | 8 | 9 | import { IServiceContainer } from '../../ioc/types'; | @@ -52,14 +53,16 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { | 52 | 53 | const pythonPath = options.pythonPath ? options.pythonPath : this.configService.getSettings(options.resource).pythonPath; | 53 | 54 | const daemonPoolKey = `${pythonPath}#${options.daemonClass || ''}#${options.daemonModule || ''}`; | 54 | 55 | const disposables = this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry); | 55 | | - const activatedProcPromise = this.createActivatedEnvironment({ allowEnvironmentFetchExceptions: true, pythonPath: options.pythonPath, resource: options.resource }); | 56 | | - | | 56 | + const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService); | | 57 | + const activatedProcPromise = this.createActivatedEnvironment({ allowEnvironmentFetchExceptions: true, pythonPath: pythonPath, resource: options.resource }); | | 58 | + const interpreterInfoPromise = interpreterService.getInterpreterDetails(pythonPath); | 57 | 59 | // Ensure we do not start multiple daemons for the same interpreter. | 58 | 60 | // Cache the promise. | 59 | 61 | const start = async () => { | | 62 | + const interpreter = await interpreterInfoPromise; | 60 | 63 | const [activatedProc, activatedEnvVars] = await Promise.all([ | 61 | 64 | activatedProcPromise, | 62 | | - this.activationHelper.getActivatedEnvironmentVariables(options.resource, undefined, false) | | 65 | + this.activationHelper.getActivatedEnvironmentVariables(options.resource, interpreter, true) | 63 | 66 | ]); | 64 | 67 |
| 65 | 68 | const daemon = new PythonDaemonExecutionServicePool({...options, pythonPath}, activatedProc!, activatedEnvVars); | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/activation.ts +6-9Lines changed: 6 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 4 | 4 | 'use strict'; | 5 | 5 |
| 6 | 6 | import { inject, injectable } from 'inversify'; | 7 | | -import { TextDocument } from 'vscode'; | 8 | 7 | import { IExtensionSingleActivationService } from '../activation/types'; | 9 | | -import { IDocumentManager } from '../common/application/types'; | 10 | 8 | import '../common/extensions'; | 11 | 9 | import { IPythonExecutionFactory } from '../common/process/types'; | 12 | 10 | import { IDisposableRegistry } from '../common/types'; | 13 | 11 | import { debounceAsync, swallowExceptions } from '../common/utils/decorators'; | 14 | 12 | import { IInterpreterService } from '../interpreter/contracts'; | 15 | 13 | import { PythonDaemonModule } from './constants'; | | 14 | +import { INotebookEditor, INotebookEditorProvider } from './types'; | 16 | 15 |
| 17 | 16 | @injectable() | 18 | 17 | export class Activation implements IExtensionSingleActivationService { | 19 | 18 | private notebookOpened = false; | 20 | 19 | constructor( | 21 | | - @inject(IDocumentManager) private readonly documentManager: IDocumentManager, | | 20 | + @inject(INotebookEditorProvider) private readonly notebookProvider: INotebookEditorProvider, | 22 | 21 | @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, | 23 | 22 | @inject(IPythonExecutionFactory) private readonly factory: IPythonExecutionFactory, | 24 | 23 | @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry | 25 | 24 | ) {} | 26 | 25 | public async activate(): Promise<void> { | 27 | | - this.disposables.push(this.documentManager.onDidOpenTextDocument(this.onDidOpenTextDocument, this)); | | 26 | + this.disposables.push(this.notebookProvider.onDidOpenNotebookEditor(this.onDidOpenNotebookEditor, this)); | 28 | 27 | this.disposables.push(this.interpreterService.onDidChangeInterpreter(this.onDidChangeInterpreter, this)); | 29 | 28 | } | 30 | 29 |
| 31 | | - private onDidOpenTextDocument(e: TextDocument) { | 32 | | - if (e.fileName.toLowerCase().endsWith('.ipynb')) { | 33 | | - this.notebookOpened = true; | 34 | | - this.PreWarmDaemonPool().ignoreErrors(); | 35 | | - } | | 30 | + private onDidOpenNotebookEditor(_: INotebookEditor) { | | 31 | + this.notebookOpened = true; | | 32 | + this.PreWarmDaemonPool().ignoreErrors(); | 36 | 33 | } | 37 | 34 |
| 38 | 35 | private onDidChangeInterpreter() { | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/interactive-ipynb/nativeEditorProvider.ts +6-1Lines changed: 6 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | 'use strict'; | 4 | 4 | import { inject, injectable } from 'inversify'; | 5 | 5 | import * as path from 'path'; | 6 | | -import { TextDocument, TextEditor, Uri } from 'vscode'; | | 6 | +import { Event, EventEmitter, TextDocument, TextEditor, Uri } from 'vscode'; | 7 | 7 |
| 8 | 8 | import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; | 9 | 9 | import { JUPYTER_LANGUAGE } from '../../common/constants'; | @@ -20,9 +20,13 @@ export class NativeEditorProvider implements INotebookEditorProvider, IAsyncDisp | 20 | 20 |
| 21 | 21 | private activeEditors: Map<string, INotebookEditor> = new Map<string, INotebookEditor>(); | 22 | 22 | private executedEditors: Set<string> = new Set<string>(); | | 23 | + private _onDidOpenNotebookEditor = new EventEmitter<INotebookEditor>(); | 23 | 24 | private notebookCount: number = 0; | 24 | 25 | private openedNotebookCount: number = 0; | 25 | 26 | private nextNumber: number = 1; | | 27 | + public get onDidOpenNotebookEditor(): Event<INotebookEditor> { | | 28 | + return this._onDidOpenNotebookEditor.event; | | 29 | + } | 26 | 30 | constructor( | 27 | 31 | @inject(IServiceContainer) private serviceContainer: IServiceContainer, | 28 | 32 | @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, | @@ -170,6 +174,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, IAsyncDisp | 170 | 174 | this.activeEditors.set(e.file.fsPath, e); | 171 | 175 | this.disposables.push(e.saved(this.onSavedEditor.bind(this, e.file.fsPath))); | 172 | 176 | this.openedNotebookCount += 1; | | 177 | + this._onDidOpenNotebookEditor.fire(e); | 173 | 178 | } | 174 | 179 |
| 175 | 180 | private onSavedEditor(oldPath: string, e: INotebookEditor) { | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/jupyter/jupyterCommand.ts +29-14Lines changed: 29 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -14,7 +14,7 @@ import { | 14 | 14 | } from '../../common/process/types'; | 15 | 15 | import { IEnvironmentActivationService } from '../../interpreter/activation/types'; | 16 | 16 | import { IInterpreterService, PythonInterpreter } from '../../interpreter/contracts'; | 17 | | -import { JupyterCommands } from '../constants'; | | 17 | +import { JupyterCommands, PythonDaemonModule } from '../constants'; | 18 | 18 | import { IJupyterCommand, IJupyterCommandFactory } from '../types'; | 19 | 19 |
| 20 | 20 | // JupyterCommand objects represent some process that can be launched that should be guaranteed to work because it | @@ -68,9 +68,18 @@ class InterpreterJupyterCommand implements IJupyterCommand { | 68 | 68 | protected interpreterPromise: Promise<PythonInterpreter | undefined>; | 69 | 69 | private pythonLauncher: Promise<IPythonExecutionService>; | 70 | 70 |
| 71 | | - constructor(protected readonly moduleName: string, protected args: string[], pythonExecutionFactory: IPythonExecutionFactory, private readonly _interpreter: PythonInterpreter) { | | 71 | + constructor(protected readonly moduleName: string, protected args: string[], protected readonly pythonExecutionFactory: IPythonExecutionFactory, | | 72 | + private readonly _interpreter: PythonInterpreter, isActiveInterpreter: boolean) { | 72 | 73 | this.interpreterPromise = Promise.resolve(this._interpreter); | 73 | | - this.pythonLauncher = pythonExecutionFactory.createActivatedEnvironment({ resource: undefined, interpreter: _interpreter, allowEnvironmentFetchExceptions: true }); | | 74 | + this.pythonLauncher = this.interpreterPromise.then(interpreter => { | | 75 | + // Create a daemon only if the interpreter is the same as the current interpreter. | | 76 | + // We don't want too many daemons (one for each of the users interpreter on their machine). | | 77 | + if (isActiveInterpreter) { | | 78 | + return pythonExecutionFactory.createDaemon({ daemonModule: PythonDaemonModule, pythonPath: interpreter!.path }); | | 79 | + } else { | | 80 | + return pythonExecutionFactory.createActivatedEnvironment({interpreter: this._interpreter}); | | 81 | + } | | 82 | + }); | 74 | 83 | } | 75 | 84 | public interpreter() : Promise<PythonInterpreter | undefined> { | 76 | 85 | return this.interpreterPromise; | @@ -80,14 +89,20 @@ class InterpreterJupyterCommand implements IJupyterCommand { | 80 | 89 | const newOptions = { ...options }; | 81 | 90 | const launcher = await this.pythonLauncher; | 82 | 91 | const newArgs = [...this.args, ...args]; | 83 | | - return launcher.execObservable(newArgs, newOptions); | | 92 | + const moduleName = newArgs[1]; | | 93 | + newArgs.shift(); // Remove '-m' | | 94 | + newArgs.shift(); // Remove module name | | 95 | + return launcher.execModuleObservable(moduleName, newArgs, newOptions); | 84 | 96 | } | 85 | 97 |
| 86 | 98 | public async exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> { | 87 | 99 | const newOptions = { ...options }; | 88 | 100 | const launcher = await this.pythonLauncher; | 89 | 101 | const newArgs = [...this.args, ...args]; | 90 | | - return launcher.exec(newArgs, newOptions); | | 102 | + const moduleName = newArgs[1]; | | 103 | + newArgs.shift(); // Remove '-m' | | 104 | + newArgs.shift(); // Remove module name | | 105 | + return launcher.execModule(moduleName, newArgs, newOptions); | 91 | 106 | } | 92 | 107 | } | 93 | 108 |
| @@ -99,8 +114,8 @@ class InterpreterJupyterCommand implements IJupyterCommand { | 99 | 114 | * @implements {IJupyterCommand} | 100 | 115 | */ | 101 | 116 | class InterpreterJupyterNotebookCommand extends InterpreterJupyterCommand { | 102 | | - constructor(moduleName: string, args: string[], pythonExecutionFactory: IPythonExecutionFactory, interpreter: PythonInterpreter) { | 103 | | - super(moduleName, args, pythonExecutionFactory, interpreter); | | 117 | + constructor(moduleName: string, args: string[], pythonExecutionFactory: IPythonExecutionFactory, interpreter: PythonInterpreter, isActiveInterpreter: boolean) { | | 118 | + super(moduleName, args, pythonExecutionFactory, interpreter, isActiveInterpreter); | 104 | 119 | } | 105 | 120 | } | 106 | 121 |
| @@ -109,19 +124,19 @@ class InterpreterJupyterNotebookCommand extends InterpreterJupyterCommand { | 109 | 124 | export class JupyterCommandFactory implements IJupyterCommandFactory { | 110 | 125 |
| 111 | 126 | constructor( | 112 | | - @inject(IPythonExecutionFactory) private executionFactory: IPythonExecutionFactory, | 113 | | - @inject(IEnvironmentActivationService) private activationHelper : IEnvironmentActivationService, | 114 | | - @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, | 115 | | - @inject(IInterpreterService) private interpreterService: IInterpreterService | | 127 | + @inject(IPythonExecutionFactory) private readonly executionFactory : IPythonExecutionFactory, | | 128 | + @inject(IEnvironmentActivationService) private readonly activationHelper : IEnvironmentActivationService, | | 129 | + @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, | | 130 | + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService | 116 | 131 | ) { | 117 | 132 |
| 118 | 133 | } | 119 | 134 |
| 120 | | - public createInterpreterCommand(command: JupyterCommands, moduleName: string, args: string[], interpreter: PythonInterpreter): IJupyterCommand { | | 135 | + public createInterpreterCommand(command: JupyterCommands, moduleName: string, args: string[], interpreter: PythonInterpreter, isActiveInterpreter: boolean): IJupyterCommand { | 121 | 136 | if (command === JupyterCommands.NotebookCommand){ | 122 | | - return new InterpreterJupyterNotebookCommand(moduleName, args, this.executionFactory, interpreter); | | 137 | + return new InterpreterJupyterNotebookCommand(moduleName, args, this.executionFactory, interpreter, isActiveInterpreter); | 123 | 138 | } | 124 | | - return new InterpreterJupyterCommand(moduleName, args, this.executionFactory, interpreter); | | 139 | + return new InterpreterJupyterCommand(moduleName, args, this.executionFactory, interpreter, isActiveInterpreter); | 125 | 140 | } | 126 | 141 |
| 127 | 142 | public createProcessCommand(exe: string, args: string[]): IJupyterCommand { | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/jupyter/jupyterCommandFinder.ts +24-6Lines changed: 24 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -10,13 +10,13 @@ import { IApplicationShell, IWorkspaceService } from '../../common/application/t | 10 | 10 | import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../common/cancellation'; | 11 | 11 | import { traceError, traceInfo, traceWarning } from '../../common/logger'; | 12 | 12 | import { IFileSystem } from '../../common/platform/types'; | 13 | | -import { IProcessService, IProcessServiceFactory, IPythonExecutionFactory, SpawnOptions } from '../../common/process/types'; | | 13 | +import { IProcessService, IProcessServiceFactory, IPythonExecutionFactory, IPythonExecutionService, SpawnOptions } from '../../common/process/types'; | 14 | 14 | import { IConfigurationService, IDisposableRegistry, ILogger, IPersistentState, IPersistentStateFactory } from '../../common/types'; | 15 | 15 | import * as localize from '../../common/utils/localize'; | 16 | 16 | import { StopWatch } from '../../common/utils/stopWatch'; | 17 | 17 | import { IInterpreterService, IKnownSearchPathsForInterpreters, PythonInterpreter } from '../../interpreter/contracts'; | 18 | 18 | import { sendTelemetryEvent } from '../../telemetry'; | 19 | | -import { JupyterCommands, RegExpValues, Telemetry } from '../constants'; | | 19 | +import { JupyterCommands, PythonDaemonModule, RegExpValues, Telemetry } from '../constants'; | 20 | 20 | import { IJupyterCommand, IJupyterCommandFactory } from '../types'; | 21 | 21 |
| 22 | 22 | export enum ModuleExistsStatus { | @@ -141,11 +141,13 @@ export class JupyterCommandFinderImpl { | 141 | 141 |
| 142 | 142 | // If the module is found on this interpreter, then we found it. | 143 | 143 | if (interpreter && !Cancellation.isCanceled(cancelToken)) { | 144 | | - findResult = await this.doesModuleExist(command, interpreter, cancelToken); | | 144 | + const [result, activeInterpreter] = await Promise.all([this.doesModuleExist(command, interpreter, cancelToken), this.interpreterService.getActiveInterpreter(undefined)]); | | 145 | + findResult = result!; | | 146 | + const isActiveInterpreter = activeInterpreter ? activeInterpreter.path === interpreter.path : false; | 145 | 147 | if (findResult.status === ModuleExistsStatus.FoundJupyter) { | 146 | | - findResult.command = this.commandFactory.createInterpreterCommand(command, 'jupyter', ['-m', 'jupyter', command], interpreter); | | 148 | + findResult.command = this.commandFactory.createInterpreterCommand(command, 'jupyter', ['-m', 'jupyter', command], interpreter, isActiveInterpreter); | 147 | 149 | } else if (findResult.status === ModuleExistsStatus.Found) { | 148 | | - findResult.command = this.commandFactory.createInterpreterCommand(command, command, ['-m', command], interpreter); | | 150 | + findResult.command = this.commandFactory.createInterpreterCommand(command, command, ['-m', command], interpreter, isActiveInterpreter); | 149 | 151 | } | 150 | 152 | } | 151 | 153 | return findResult; | @@ -350,13 +352,29 @@ export class JupyterCommandFinderImpl { | 350 | 352 |
| 351 | 353 | return found; | 352 | 354 | } | | 355 | + private async createExecutionService(interpreter: PythonInterpreter): Promise<IPythonExecutionService> { | | 356 | + const [currentInterpreter, pythonService] = await Promise.all([ | | 357 | + this.interpreterService.getActiveInterpreter(undefined), | | 358 | + this.executionFactory.createActivatedEnvironment({ resource: undefined, interpreter, allowEnvironmentFetchExceptions: true }) | | 359 | + ]); | | 360 | + | | 361 | + // Use daemons for current interpreter, when using any other interpreter, do not use a daemon. | | 362 | + // Creating daemons for other interpreters might not be what we want. | | 363 | + // E.g. users can have dozens of pipenv or conda environments. | | 364 | + // In such cases, we'd end up creating n*3 python processes that are long lived. | | 365 | + if (!currentInterpreter || currentInterpreter.path !== interpreter.path){ | | 366 | + return pythonService!; | | 367 | + } | | 368 | + | | 369 | + return this.executionFactory.createDaemon({ daemonModule: PythonDaemonModule, pythonPath: interpreter.path }); | | 370 | + } | 353 | 371 | private async doesModuleExist(moduleName: string, interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<IModuleExistsResult> { | 354 | 372 | const result: IModuleExistsResult = { | 355 | 373 | status: ModuleExistsStatus.NotFound | 356 | 374 | }; | 357 | 375 | if (interpreter && interpreter !== null) { | 358 | 376 | const newOptions: SpawnOptions = { throwOnStdErr: false, encoding: 'utf8', token: cancelToken }; | 359 | | - const pythonService = await this.executionFactory.createActivatedEnvironment({ resource: undefined, interpreter, allowEnvironmentFetchExceptions: true }); | | 377 | + const pythonService = await this.createExecutionService(interpreter); | 360 | 378 |
| 361 | 379 | // For commands not 'ipykernel' first try them as jupyter commands | 362 | 380 | if (moduleName !== JupyterCommands.KernelCreateCommand) { | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/jupyter/jupyterExecution.ts +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -59,7 +59,7 @@ export class JupyterExecutionBase implements IJupyterExecution { | 59 | 59 | this.commandFinder = serviceContainer.get<JupyterCommandFinder>(JupyterCommandFinder); | 60 | 60 | this.kernelService = new KernelService(this, this.commandFinder, asyncRegistry, | 61 | 61 | processServiceFactory, interpreterService, fileSystem); | 62 | | - this.notebookStarter = new NotebookStarter(executionFactory, this, this.commandFinder, | | 62 | + this.notebookStarter = new NotebookStarter(executionFactory, this.commandFinder, | 63 | 63 | this.kernelService, fileSystem, serviceContainer); | 64 | 64 | this.disposableRegistry.push(this.interpreterService.onDidChangeInterpreter(() => this.onSettingsChanged())); | 65 | 65 | this.disposableRegistry.push(this); | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/jupyter/jupyterSession.ts +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -174,7 +174,7 @@ export class JupyterSession implements IJupyterSession { | 174 | 174 |
| 175 | 175 | private async waitForIdleOnSession(session: Session.ISession | undefined, timeout: number): Promise<void> { | 176 | 176 | if (session && session.kernel) { | 177 | | - traceInfo(`Waiting for idle on: ${session.kernel.id} -> ${session.kernel.status}`); | | 177 | + traceInfo(`Waiting for idle on (kernel): ${session.kernel.id} -> ${session.kernel.status}`); | 178 | 178 |
| 179 | 179 | const statusChangedPromise = new Promise(resolve => session.kernelChanged.connect((_, e) => e.newValue && e.newValue.status === 'idle' ? resolve() : undefined)); | 180 | 180 | const checkStatusPromise = new Promise(async resolve => { | @@ -191,7 +191,7 @@ export class JupyterSession implements IJupyterSession { | 191 | 191 | resolve(); | 192 | 192 | }); | 193 | 193 | await Promise.race([statusChangedPromise, checkStatusPromise]); | 194 | | - traceInfo(`Finished waiting for idle on: ${session.kernel.id} -> ${session.kernel.status}`); | | 194 | + traceInfo(`Finished waiting for idle on (kernel): ${session.kernel.id} -> ${session.kernel.status}`); | 195 | 195 |
| 196 | 196 | // If we didn't make it out in ten seconds, indicate an error | 197 | 197 | if (session.kernel && session.kernel.status === 'idle') { | |
Collapse file Copy file name to clipboardExpand all lines: src/client/datascience/jupyter/liveshare/hostJupyterServer.ts +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -178,7 +178,7 @@ export class HostJupyterServer | 178 | 178 | this.workspaceService); | 179 | 179 |
| 180 | 180 | // Wait for it to be ready | 181 | | - traceInfo(`Waiting for idle ${this.id}`); | | 181 | + traceInfo(`Waiting for idle (session) ${this.id}`); | 182 | 182 | const stopWatch = new StopWatch(); | 183 | 183 | const idleTimeout = configService.getSettings().datascience.jupyterLaunchTimeout; | 184 | 184 | await notebook.waitForIdle(idleTimeout); | |
|