@@ -10,12 +10,12 @@ import {
10
10
} from 'child_process' ;
11
11
import { once } from 'events' ;
12
12
13
- export async function kill ( childProcess : ChildProcess , code : NodeJS . Signals | number = 'SIGTERM' ) {
13
+ export async function kill (
14
+ childProcess : ChildProcess ,
15
+ code : NodeJS . Signals | number = 'SIGTERM'
16
+ ) {
14
17
childProcess . kill ( code ) ;
15
- if (
16
- childProcess . exitCode === null &&
17
- childProcess . signalCode === null
18
- ) {
18
+ if ( childProcess . exitCode === null && childProcess . signalCode === null ) {
19
19
await once ( childProcess , 'exit' ) ;
20
20
}
21
21
}
@@ -25,7 +25,7 @@ export default function spawnChildFromSource(
25
25
spawnOptions : SpawnOptionsWithoutStdio = { } ,
26
26
timeoutMs ?: number ,
27
27
_stdout : StdioNull | StdioPipe = 'inherit' ,
28
- _stderr : StdioNull | StdioPipe = 'inherit' ,
28
+ _stderr : StdioNull | StdioPipe = 'inherit'
29
29
) : Promise < ChildProcess > {
30
30
return new Promise ( async ( resolve , reject ) => {
31
31
const readyToken = Date . now ( ) . toString ( 32 ) ;
@@ -37,6 +37,7 @@ export default function spawnChildFromSource(
37
37
38
38
if ( ! childProcess . stdin ) {
39
39
await kill ( childProcess ) ;
40
+
40
41
return reject (
41
42
new Error ( "Can't write src to the spawned process, missing stdin" )
42
43
) ;
@@ -45,25 +46,44 @@ export default function spawnChildFromSource(
45
46
// eslint-disable-next-line prefer-const
46
47
let timeoutId : NodeJS . Timeout | null ;
47
48
48
- const onExit = ( exitCode : number | null ) => {
49
+ function cleanupListeners ( ) {
50
+ if ( timeoutId ) {
51
+ clearTimeout ( timeoutId ) ;
52
+ }
53
+ if ( childProcess . stdin ) {
54
+ childProcess . stdin . off ( 'error' , onWriteError ) ;
55
+ // To swallow a possible error about writing into a closed pipe if we
56
+ // e.g., killed a process before our script finished writing
57
+ childProcess . stdin . on ( 'error' , ( ) => { } ) ;
58
+ }
59
+ childProcess . off ( 'message' , onMessage ) ;
60
+ childProcess . off ( 'exit' , onExit ) ;
61
+ }
62
+
63
+ async function onExit ( exitCode : number | null ) {
49
64
if ( exitCode && exitCode > 0 ) {
65
+ cleanupListeners ( ) ;
66
+ await kill ( childProcess ) ;
50
67
reject ( new Error ( 'Child process exited with error before starting' ) ) ;
51
68
}
52
- } ;
69
+ }
53
70
54
- const onMessage = ( data : Serializable ) => {
71
+ async function onWriteError ( error : Error ) {
72
+ cleanupListeners ( ) ;
73
+ await kill ( childProcess ) ;
74
+ reject ( error ) ;
75
+ }
76
+
77
+ function onMessage ( data : Serializable ) {
55
78
if ( data === readyToken ) {
56
- if ( timeoutId ) {
57
- clearTimeout ( timeoutId ) ;
58
- }
59
- childProcess . off ( 'exit' , onExit ) ;
79
+ cleanupListeners ( ) ;
60
80
resolve ( childProcess ) ;
61
81
}
62
- } ;
82
+ }
63
83
64
84
childProcess . on ( 'message' , onMessage ) ;
65
-
66
85
childProcess . on ( 'exit' , onExit ) ;
86
+ childProcess . stdin . on ( 'error' , onWriteError ) ;
67
87
68
88
childProcess . stdin . write ( src ) ;
69
89
childProcess . stdin . write ( `;process.send(${ JSON . stringify ( readyToken ) } )` ) ;
@@ -72,6 +92,7 @@ export default function spawnChildFromSource(
72
92
timeoutId =
73
93
timeoutMs !== undefined
74
94
? setTimeout ( async ( ) => {
95
+ cleanupListeners ( ) ;
75
96
await kill ( childProcess ) ;
76
97
reject (
77
98
new Error ( 'Timed out while waiting for child process to start' )
0 commit comments