-
Notifications
You must be signed in to change notification settings - Fork 299
/
Copy pathkernelLauncherDaemon.ts
96 lines (89 loc) · 4 KB
/
kernelLauncherDaemon.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import { ChildProcess } from 'child_process';
import * as fs from 'fs-extra';
import { inject, injectable } from 'inversify';
import { BaseError } from '../../common/errors/types';
import { traceInfo } from '../../common/logger';
import { ObservableExecutionResult } from '../../common/process/types';
import { IDisposable, Resource } from '../../common/types';
import { noop } from '../../common/utils/misc';
import { traceDecorators } from '../../logging';
import { PythonEnvironment } from '../../pythonEnvironments/info';
import { IJupyterKernelSpec } from '../types';
import { KernelDaemonPool } from './kernelDaemonPool';
import { KernelEnvironmentVariablesService } from './kernelEnvVarsService';
import { IPythonKernelDaemon } from './types';
export class UnsupportedKernelSpec extends BaseError {
constructor(args: string[]) {
super(
'unsupportedKernelSpec',
`Unsupported KernelSpec file. args must be [<pythonPath>, '-m', <moduleName>, arg1, arg2, ..]. Provied ${args.join(
' '
)}`
);
}
}
/**
* Launches a Python kernel in a daemon.
* We need a daemon for the sole purposes of being able to interrupt kernels in Windows.
* (Else we don't need a kernel).
*/
@injectable()
export class PythonKernelLauncherDaemon implements IDisposable {
private readonly processesToDispose: ChildProcess[] = [];
constructor(
@inject(KernelDaemonPool) private readonly daemonPool: KernelDaemonPool,
@inject(KernelEnvironmentVariablesService)
private readonly kernelEnvVarsService: KernelEnvironmentVariablesService
) {}
@traceDecorators.verbose('Launching kernel daemon')
public async launch(
resource: Resource,
workingDirectory: string,
kernelSpec: IJupyterKernelSpec,
interpreter?: PythonEnvironment
): Promise<{ observableOutput: ObservableExecutionResult<string>; daemon: IPythonKernelDaemon | undefined }> {
traceInfo(`Launching kernel daemon for ${kernelSpec.display_name} # ${interpreter?.path}`);
const [daemon, wdExists, env] = await Promise.all([
this.daemonPool.get(resource, kernelSpec, interpreter),
fs.pathExists(workingDirectory),
this.kernelEnvVarsService.getEnvironmentVariables(resource, interpreter, kernelSpec)
]);
// Check to see if we have the type of kernelspec that we expect
const args = kernelSpec.argv.slice();
const modulePrefixIndex = args.findIndex((item) => item === '-m');
if (modulePrefixIndex === -1) {
throw new UnsupportedKernelSpec(args);
}
const moduleName = args[modulePrefixIndex + 1];
const moduleArgs = args.slice(modulePrefixIndex + 2);
// The daemon pool can return back a non-IPythonKernelDaemon if daemon service is not supported or for Python 2.
// Use a check for the daemon.start function here before we call it.
if (!('start' in daemon)) {
// If we don't have a KernelDaemon here then we have an execution service and should use that to launch
const observableOutput = daemon.execModuleObservable(moduleName, moduleArgs, {
env,
cwd: wdExists ? workingDirectory : process.cwd()
});
return { observableOutput, daemon: undefined };
} else {
// In the case that we do have a kernel deamon, just return it
const observableOutput = await daemon.start(moduleName, moduleArgs, { env, cwd: workingDirectory });
if (observableOutput.proc) {
this.processesToDispose.push(observableOutput.proc);
}
return { observableOutput, daemon };
}
}
public dispose() {
while (this.processesToDispose.length) {
try {
this.processesToDispose.shift()!.kill();
} catch {
noop();
}
}
}
}