forked from microsoft/vscode-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpythonDaemonFactory.ts
More file actions
134 lines (127 loc) · 6.05 KB
/
pythonDaemonFactory.ts
File metadata and controls
134 lines (127 loc) · 6.05 KB
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { ChildProcess } from 'child_process';
import * as path from 'path';
import {
createMessageConnection,
MessageConnection,
RequestType,
StreamMessageReader,
StreamMessageWriter
} from 'vscode-jsonrpc/node';
import { EXTENSION_ROOT_DIR } from '../../constants';
import { PYTHON_WARNINGS } from '../constants';
import { traceDecorators, traceError } from '../logger';
import { IPlatformService } from '../platform/types';
import { IDisposable, IDisposableRegistry } from '../types';
import { createDeferred } from '../utils/async';
import { BasePythonDaemon } from './baseDaemon';
import { PythonDaemonExecutionService } from './pythonDaemon';
import { DaemonExecutionFactoryCreationOptions, IPythonDaemonExecutionService, IPythonExecutionService } from './types';
export class PythonDaemonFactory {
protected readonly envVariables: NodeJS.ProcessEnv;
protected readonly pythonPath: string;
constructor(
protected readonly disposables: IDisposableRegistry,
protected readonly options: DaemonExecutionFactoryCreationOptions,
protected readonly pythonExecutionService: IPythonExecutionService,
protected readonly platformService: IPlatformService,
protected readonly activatedEnvVariables?: NodeJS.ProcessEnv
) {
if (!options.pythonPath) {
throw new Error('options.pythonPath is empty when it shoud not be');
}
this.pythonPath = options.pythonPath;
// Setup environment variables for the daemon.
// The daemon must have access to the Python Module that'll run the daemon
// & also access to a Python package used for the JSON rpc comms.
const envPythonPath = `${path.join(EXTENSION_ROOT_DIR, 'pythonFiles')}${path.delimiter}${path.join(
EXTENSION_ROOT_DIR,
'pythonFiles',
'lib',
'python'
)}`;
this.envVariables = this.activatedEnvVariables ? { ...this.activatedEnvVariables } : { ...process.env };
this.envVariables.PYTHONPATH = this.envVariables.PYTHONPATH
? `${this.envVariables.PYTHONPATH}${path.delimiter}${envPythonPath}`
: envPythonPath;
this.envVariables.PYTHONUNBUFFERED = '1';
// Always ignore warnings as the user should never see the output of the daemon running
this.envVariables[PYTHON_WARNINGS] = 'ignore';
}
@traceDecorators.error('Failed to create daemon')
public async createDaemonService<T extends IPythonDaemonExecutionService | IDisposable>(): Promise<T> {
// Add '--log-file=/Users/donjayamanne/Desktop/Development/vsc/pythonVSCode/daaemon.log' to log to a file.
const loggingArgs: string[] = ['-v']; // Log information messages or greater (see daemon.__main__.py for options).
const args = (this.options.daemonModule ? [`--daemon-module=${this.options.daemonModule}`] : []).concat(
loggingArgs
);
const env = this.envVariables;
const daemonProc = this.pythonExecutionService!.execModuleObservable(
'vscode_datascience_helpers.daemon',
args,
{ env }
);
if (!daemonProc.proc) {
throw new Error('Failed to create Daemon Proc');
}
const connection = this.createConnection(daemonProc.proc);
connection.listen();
let stdError = '';
let procEndEx: Error | undefined;
daemonProc.proc.stderr.on('data', (data: string | Buffer) => {
data = typeof data === 'string' ? data : data.toString('utf8');
stdError += data;
});
daemonProc.proc.on('error', (ex) => (procEndEx = ex));
try {
await this.testDaemon(connection);
const cls = this.options.daemonClass ?? PythonDaemonExecutionService;
const instance = new cls(
this.pythonExecutionService,
this.platformService,
this.pythonPath,
daemonProc.proc,
connection
);
if (instance instanceof BasePythonDaemon) {
this.disposables.push(instance);
return (instance as unknown) as T;
}
throw new Error(`Daemon class ${cls.name} must inherit BasePythonDaemon.`);
} catch (ex) {
traceError('Failed to start the Daemon, StdErr: ', stdError);
traceError('Failed to start the Daemon, ProcEndEx', procEndEx || ex);
traceError('Failed to start the Daemon, Ex', ex);
throw ex;
}
}
/**
* Protected so we can override for testing purposes.
*/
protected createConnection(proc: ChildProcess) {
return createMessageConnection(new StreamMessageReader(proc.stdout), new StreamMessageWriter(proc.stdin));
}
/**
* Tests whether a daemon is usable or not by checking whether it responds to a simple ping.
* If a daemon doesn't reply to a ping in 5s, then its deemed to be dead/not usable.
*
* @private
* @param {MessageConnection} connection
* @memberof PythonDaemonExecutionServicePool
*/
@traceDecorators.error('Pinging Daemon Failed')
protected async testDaemon(connection: MessageConnection) {
// If we don't get a reply to the ping in 5 seconds assume it will never work. Bomb out.
// At this point there should be some information logged in stderr of the daemon process.
const fail = createDeferred<{ pong: string }>();
const timer = setTimeout(() => fail.reject(new Error('Timeout waiting for daemon to start')), 5_000);
const request = new RequestType<{ data: string }, { pong: string }, void, void>('ping');
// Check whether the daemon has started correctly, by sending a ping.
const result = await Promise.race([fail.promise, connection.sendRequest(request, { data: 'hello' })]);
clearTimeout(timer);
if (result.pong !== 'hello') {
throw new Error(`Daemon did not reply to the ping, received: ${result.pong}`);
}
}
}