@@ -10,6 +10,7 @@ import * as util from 'util';
10
10
import { MessageConnection , NotificationType , RequestType , RequestType0 } from 'vscode-jsonrpc' ;
11
11
import { traceError , traceWarning } from '../logger' ;
12
12
import { IDisposable } from '../types' ;
13
+ import { createDeferred , Deferred } from '../utils/async' ;
13
14
import { noop } from '../utils/misc' ;
14
15
import { Architecture } from '../utils/platform' ;
15
16
import { parsePythonVersion } from '../utils/version' ;
@@ -27,22 +28,37 @@ import {
27
28
28
29
type ErrorResponse = { error ?: string } ;
29
30
31
+ export class ConnectionClosedError extends Error {
32
+ constructor ( public readonly message : string ) {
33
+ super ( ) ;
34
+ }
35
+ }
30
36
export class PythonDaemonExecutionService implements IPythonDaemonExecutionService {
31
- private connectionClosedMessage ? : string ;
37
+ private connectionClosedMessage : string = '' ;
32
38
private outputObservale = new Subject < Output < string > > ( ) ;
39
+ // tslint:disable-next-line: no-any
40
+ private readonly connectionClosedDeferred : Deferred < any > ;
33
41
private disposables : IDisposable [ ] = [ ] ;
42
+ public get isAlive ( ) : boolean {
43
+ return this . connectionClosedMessage === '' ;
44
+ }
34
45
constructor (
35
46
protected readonly pythonExecutionService : IPythonExecutionService ,
36
47
protected readonly pythonPath : string ,
37
- protected readonly daemonProc : ChildProcess ,
38
- protected readonly connection : MessageConnection
48
+ public readonly proc : ChildProcess ,
49
+ public readonly connection : MessageConnection
39
50
) {
51
+ // tslint:disable-next-line: no-any
52
+ this . connectionClosedDeferred = createDeferred < any > ( ) ;
53
+ // This promise gets used conditionally, if it doesn't get used, and the promise is rejected,
54
+ // then node logs errors. We don't want that, hence add a dummy error handler.
55
+ this . connectionClosedDeferred . promise . catch ( noop ) ;
40
56
this . monitorConnection ( ) ;
41
57
}
42
58
public dispose ( ) {
43
59
try {
44
60
this . connection . dispose ( ) ;
45
- this . daemonProc . kill ( ) ;
61
+ this . proc . kill ( ) ;
46
62
} catch {
47
63
noop ( ) ;
48
64
}
@@ -53,7 +69,7 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
53
69
try {
54
70
type InterpreterInfoResponse = ErrorResponse & { versionInfo : PythonVersionInfo ; sysPrefix : string ; sysVersion : string ; is64Bit : boolean } ;
55
71
const request = new RequestType0 < InterpreterInfoResponse , void , void > ( 'get_interpreter_information' ) ;
56
- const response = await this . connection . sendRequest ( request ) ;
72
+ const response = await this . sendRequestWithoutArgs ( request ) ;
57
73
const versionValue = response . versionInfo . length === 4 ? `${ response . versionInfo . slice ( 0 , 3 ) . join ( '.' ) } -${ response . versionInfo [ 3 ] } ` : response . versionInfo . join ( '.' ) ;
58
74
return {
59
75
architecture : response . is64Bit ? Architecture . x64 : Architecture . x86 ,
@@ -71,7 +87,7 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
71
87
try {
72
88
type ExecutablePathResponse = ErrorResponse & { path : string } ;
73
89
const request = new RequestType0 < ExecutablePathResponse , void , void > ( 'get_executable' ) ;
74
- const response = await this . connection . sendRequest ( request ) ;
90
+ const response = await this . sendRequestWithoutArgs ( request ) ;
75
91
if ( response . error ) {
76
92
throw new Error ( response . error ) ;
77
93
}
@@ -85,7 +101,7 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
85
101
try {
86
102
type ModuleInstalledResponse = ErrorResponse & { exists : boolean } ;
87
103
const request = new RequestType < { module_name : string } , ModuleInstalledResponse , void , void > ( 'is_module_installed' ) ;
88
- const response = await this . connection . sendRequest ( request , { module_name : moduleName } ) ;
104
+ const response = await this . sendRequest ( request , { module_name : moduleName } ) ;
89
105
if ( response . error ) {
90
106
throw new Error ( response . error ) ;
91
107
}
@@ -112,31 +128,17 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
112
128
}
113
129
public async exec ( args : string [ ] , options : SpawnOptions ) : Promise < ExecutionResult < string > > {
114
130
this . throwIfRPCConnectionIsDead ( ) ;
115
- if ( ! this . canExecFileUsingDaemon ( args , options ) ) {
116
- return this . pythonExecutionService . exec ( args , options ) ;
117
- }
118
- try {
119
- return await this . execFileWithDaemon ( args [ 0 ] , args . slice ( 1 ) , options ) ;
120
- } catch ( ex ) {
121
- // This is a handled error (error from user code that must be bubbled up).
122
- if ( ex instanceof StdErrError ) {
123
- throw ex ;
124
- }
131
+ if ( this . canExecFileUsingDaemon ( args , options ) ) {
132
+ return this . execFileWithDaemon ( args [ 0 ] , args . slice ( 1 ) , options ) ;
133
+ } else {
125
134
return this . pythonExecutionService . exec ( args , options ) ;
126
135
}
127
136
}
128
137
public async execModule ( moduleName : string , args : string [ ] , options : SpawnOptions ) : Promise < ExecutionResult < string > > {
129
138
this . throwIfRPCConnectionIsDead ( ) ;
130
- if ( ! this . canExecModuleUsingDaemon ( moduleName , args , options ) ) {
131
- return this . pythonExecutionService . execModule ( moduleName , args , options ) ;
132
- }
133
- try {
134
- return await this . execModuleWithDaemon ( moduleName , args , options ) ;
135
- } catch ( ex ) {
136
- // This is a handled error (error from user code that must be bubbled up).
137
- if ( ex instanceof StdErrError ) {
138
- throw ex ;
139
- }
139
+ if ( this . canExecModuleUsingDaemon ( moduleName , args , options ) ) {
140
+ return this . execModuleWithDaemon ( moduleName , args , options ) ;
141
+ } else {
140
142
return this . pythonExecutionService . execModule ( moduleName , args , options ) ;
141
143
}
142
144
}
@@ -151,6 +153,13 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
151
153
// tslint:disable-next-line: no-any
152
154
return Object . keys ( options ) . every ( item => daemonSupportedSpawnOptions . indexOf ( item as any ) >= 0 ) ;
153
155
}
156
+ private sendRequestWithoutArgs < R , E , RO > ( type : RequestType0 < R , E , RO > ) : Thenable < R > {
157
+ return Promise . race ( [ this . connection . sendRequest ( type ) , this . connectionClosedDeferred . promise ] ) ;
158
+ }
159
+ private sendRequest < P , R , E , RO > ( type : RequestType < P , R , E , RO > , params ?: P ) : Thenable < R > {
160
+ // Throw an error if the connection has been closed.
161
+ return Promise . race ( [ this . connection . sendRequest ( type , params ) , this . connectionClosedDeferred . promise ] ) ;
162
+ }
154
163
/**
155
164
* Process the response.
156
165
*
@@ -161,7 +170,6 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
161
170
*/
162
171
private processResponse ( response : { error ?: string | undefined ; stdout : string ; stderr ?: string } , options : SpawnOptions ) {
163
172
if ( response . error ) {
164
- traceError ( 'Failed to execute file using the daemon' , response . error ) ;
165
173
throw new StdErrError ( `Failed to execute using the daemon, ${ response . error } ` ) ;
166
174
}
167
175
// Throw an error if configured to do so if there's any output in stderr.
@@ -177,7 +185,7 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
177
185
type ExecResponse = ErrorResponse & { stdout : string ; stderr ?: string } ;
178
186
// tslint:disable-next-line: no-any
179
187
const request = new RequestType < { file_name : string ; args : string [ ] ; cwd ?: string ; env ?: any } , ExecResponse , void , void > ( 'exec_file' ) ;
180
- const response = await this . connection . sendRequest ( request , { file_name : fileName , args, cwd : options . cwd , env : options . env } ) ;
188
+ const response = await this . sendRequest ( request , { file_name : fileName , args, cwd : options . cwd , env : options . env } ) ;
181
189
this . processResponse ( response , options ) ;
182
190
return response ;
183
191
}
@@ -188,7 +196,7 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
188
196
type ExecResponse = ErrorResponse & { stdout : string ; stderr ?: string } ;
189
197
// tslint:disable-next-line: no-any
190
198
const request = new RequestType < { module_name : string ; args : string [ ] ; cwd ?: string ; env ?: any } , ExecResponse , void , void > ( 'exec_module' ) ;
191
- const response = await this . connection . sendRequest ( request , { module_name : moduleName , args, cwd : options . cwd , env : options . env } ) ;
199
+ const response = await this . sendRequest ( request , { module_name : moduleName , args, cwd : options . cwd , env : options . env } ) ;
192
200
this . processResponse ( response , options ) ;
193
201
return response ;
194
202
}
@@ -203,19 +211,19 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
203
211
if ( 'fileName' in moduleOrFile ) {
204
212
// tslint:disable-next-line: no-any
205
213
const request = new RequestType < { file_name : string ; args : string [ ] ; cwd ?: string ; env ?: any } , ExecResponse , void , void > ( 'exec_file_observable' ) ;
206
- response = await this . connection . sendRequest ( request , { file_name : moduleOrFile . fileName , args, cwd : options . cwd , env : options . env } ) ;
214
+ response = await this . sendRequest ( request , { file_name : moduleOrFile . fileName , args, cwd : options . cwd , env : options . env } ) ;
207
215
} else {
208
216
// tslint:disable-next-line: no-any
209
217
const request = new RequestType < { module_name : string ; args : string [ ] ; cwd ?: string ; env ?: any } , ExecResponse , void , void > ( 'exec_module_observable' ) ;
210
- response = await this . connection . sendRequest ( request , { module_name : moduleOrFile . moduleName , args, cwd : options . cwd , env : options . env } ) ;
218
+ response = await this . sendRequest ( request , { module_name : moduleOrFile . moduleName , args, cwd : options . cwd , env : options . env } ) ;
211
219
}
212
220
// Might not get a response object back, as its observable.
213
221
if ( response && response . error ) {
214
222
throw new StdErrError ( response . error ) ;
215
223
}
216
224
} ;
217
225
let stdErr = '' ;
218
- this . daemonProc . stderr . on ( 'data' , ( output : string | Buffer ) => ( stdErr += output . toString ( ) ) ) ;
226
+ this . proc . stderr . on ( 'data' , ( output : string | Buffer ) => ( stdErr += output . toString ( ) ) ) ;
219
227
// Wire up stdout/stderr.
220
228
const subscription = this . outputObservale . subscribe ( out => {
221
229
if ( out . source === 'stderr' && options . throwOnStdErr ) {
@@ -243,15 +251,16 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
243
251
. ignoreErrors ( ) ;
244
252
245
253
return {
246
- proc : this . daemonProc ,
254
+ proc : this . proc ,
247
255
dispose : ( ) => this . dispose ( ) ,
248
256
out : subject
249
257
} ;
250
258
}
251
259
private monitorConnection ( ) {
252
260
// tslint:disable-next-line: no-any
253
261
const logConnectionStatus = ( msg : string , ex ?: any ) => {
254
- this . connectionClosedMessage = msg + ( ex ? `, With Error: ${ util . format ( ex ) } ` : '' ) ;
262
+ this . connectionClosedMessage += msg + ( ex ? `, With Error: ${ util . format ( ex ) } ` : '' ) ;
263
+ this . connectionClosedDeferred . reject ( new ConnectionClosedError ( this . connectionClosedMessage ) ) ;
255
264
traceWarning ( msg ) ;
256
265
if ( ex ) {
257
266
traceError ( 'Connection errored' , ex ) ;
@@ -260,6 +269,8 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
260
269
this . disposables . push ( this . connection . onClose ( ( ) => logConnectionStatus ( 'Daemon Connection Closed' ) ) ) ;
261
270
this . disposables . push ( this . connection . onDispose ( ( ) => logConnectionStatus ( 'Daemon Connection disposed' ) ) ) ;
262
271
this . disposables . push ( this . connection . onError ( ex => logConnectionStatus ( 'Daemon Connection errored' , ex ) ) ) ;
272
+ // this.proc.on('error', error => logConnectionStatus('Daemon Processed died with error', error));
273
+ this . proc . on ( 'exit' , code => logConnectionStatus ( 'Daemon Processed died with exit code' , code ) ) ;
263
274
// Wire up stdout/stderr.
264
275
const OuputNotification = new NotificationType < Output < string > , void > ( 'output' ) ;
265
276
this . connection . onNotification ( OuputNotification , output => this . outputObservale . next ( output ) ) ;
0 commit comments