@@ -4,22 +4,40 @@ import type {
4
4
TaskCallback ,
5
5
WorkerFactory ,
6
6
WorkerResultInternal ,
7
- WorkerTask ,
7
+ WorkerTaskInformation ,
8
8
WorkerTaskInput ,
9
9
} from './types.js' ;
10
+ import { AsyncResource } from 'node:async_hooks' ;
10
11
import { Subject } from 'rxjs' ;
11
12
import { WorkerFunction , WorkerResult } from './types.js' ;
12
13
import * as errors from './errors.js' ;
13
14
15
+ class WorkerTask extends AsyncResource {
16
+ protected callback : TaskCallback ;
17
+
18
+ constructor ( callback : TaskCallback , triggerAsyncId ?: number ) {
19
+ super ( 'WorkerTask' , triggerAsyncId ) ;
20
+ this . callback = callback ;
21
+ }
22
+
23
+ public done ( result , error : Error ) {
24
+ this . runInAsyncScope ( this . callback , null , result , error ) ;
25
+ this . emitDestroy ( ) ;
26
+ }
27
+ }
28
+
14
29
const taskInfoSymbol = Symbol ( 'Task Info Symbol' ) ;
15
- type PoolStatus = 'idle' | 'working' | 'queued' ;
30
+ type PoolStatus = 'terminated' | ' idle' | 'working' | 'queued' ;
16
31
17
32
class WorkerPool {
18
33
protected workerFactory : WorkerFactory ;
19
34
protected workers : Set < Worker > = new Set ( ) ;
20
35
protected freeWorkers : Array < Worker > = [ ] ;
21
- protected queue : Array < { task : WorkerTask ; callback : TaskCallback } > = [ ] ;
22
- protected terminating : boolean = false ;
36
+ protected queue : Array < {
37
+ task : WorkerTaskInformation ;
38
+ callback : TaskCallback ;
39
+ } > = [ ] ;
40
+ protected terminatedError : Error | undefined = undefined ;
23
41
protected handleDestroySubscription : Subscription ;
24
42
25
43
public $workerCreated = new Subject < void > ( ) ;
@@ -57,15 +75,31 @@ class WorkerPool {
57
75
58
76
protected addWorker ( ) {
59
77
const worker = this . workerFactory ( ) ;
78
+ let workerError : Error ;
79
+ let initializing = true ;
60
80
const messageHandler = ( result : WorkerResultInternal ) => {
61
- if ( result . error != null ) worker [ taskInfoSymbol ] ( undefined , result . error ) ;
62
- else worker [ taskInfoSymbol ] ( result . data , undefined ) ;
81
+ if ( initializing ) {
82
+ // @ts -ignore: ignoring type here
83
+ if ( result !== 'initialized' ) {
84
+ throw Error ( 'TMP IMP failed to initialize properly' ) ;
85
+ }
86
+ initializing = false ;
87
+ this . freeWorkers . push ( worker ) ;
88
+ this . $workerFreed . next ( ) ;
89
+ return ;
90
+ }
91
+ if ( result . error != null ) {
92
+ worker [ taskInfoSymbol ] . done ( undefined , result . error ) ;
93
+ } else {
94
+ worker [ taskInfoSymbol ] . done ( result . data , undefined ) ;
95
+ }
63
96
worker [ taskInfoSymbol ] = undefined ;
64
97
this . freeWorkers . push ( worker ) ;
65
98
this . $workerFreed . next ( ) ;
66
99
} ;
67
100
const errorHandler = ( e ) => {
68
- if ( worker [ taskInfoSymbol ] ) worker [ taskInfoSymbol ] ( undefined , e ) ;
101
+ workerError = e ;
102
+ if ( worker [ taskInfoSymbol ] ) worker [ taskInfoSymbol ] . done ( undefined , e ) ;
69
103
else this . $workerError . next ( e ) ;
70
104
} ;
71
105
worker . on ( 'message' , messageHandler ) ;
@@ -74,24 +108,37 @@ class WorkerPool {
74
108
worker . off ( 'message' , messageHandler ) ;
75
109
worker . off ( 'error' , errorHandler ) ;
76
110
this . workers . delete ( worker ) ;
111
+ if (
112
+ workerError != null &&
113
+ `${ workerError . message } ` . includes ( 'Cannot find module' )
114
+ ) {
115
+ // If the worker errored then we want to check if it was a failure to load error
116
+ if ( this . workers . size === 0 && this . terminatedError == null ) {
117
+ this . terminatedError = new Error (
118
+ 'TMP IMP Workers failed to load modules' ,
119
+ ) ;
120
+ this . cleanUp ( this . terminatedError ) ;
121
+ }
122
+ return ;
123
+ }
77
124
this . $workerDestroyed . next ( ) ;
78
125
} ) ;
126
+ worker . once ( 'online' , async ( ) => {
127
+ worker . postMessage ( { type : 'initialize' , data : undefined } ) ;
128
+ } ) ;
79
129
80
130
// TODO: debugging
81
131
// worker.on('message', (...args) => console.log('DEBUG message: ', args));
82
132
// worker.on('messageerror', (...args) => console.log('DEBUG messageerror: ', args));
83
133
// worker.on('error', (...args) => console.log('DEBUG error: ', args));
84
134
// worker.on('exit', (...args) => console.log('DEBUG exit: ', args));
85
135
// worker.on('online', (...args) => console.log('DEBUG online: ', args));
86
-
87
136
this . workers . add ( worker ) ;
88
137
this . $workerCreated . next ( ) ;
89
- this . freeWorkers . push ( worker ) ;
90
- this . $workerFreed . next ( ) ;
91
138
}
92
139
93
140
public runTask ( task : WorkerTaskInput , callback : TaskCallback ) {
94
- if ( this . terminating ) throw Error ( 'TMP IMP terminating' ) ;
141
+ if ( this . terminatedError != null ) throw this . terminatedError ;
95
142
if ( this . freeWorkers . length === 0 ) {
96
143
this . queue . push ( { task, callback } ) ;
97
144
if ( this . queue . length === 1 ) this . $poolStatus . next ( 'queued' ) ;
@@ -101,12 +148,13 @@ class WorkerPool {
101
148
const wasIdle = this . freeWorkers . length === this . workers . size ;
102
149
const worker = this . freeWorkers . pop ( ) ! ;
103
150
if ( wasIdle ) this . $poolStatus . next ( 'working' ) ;
104
- worker [ taskInfoSymbol ] = callback ;
151
+ worker [ taskInfoSymbol ] = new WorkerTask ( callback ) ;
105
152
worker . postMessage ( { type : task . type , data : task . data } , task . transferList ) ;
106
153
}
107
154
108
155
public async terminate ( force : boolean ) {
109
- this . terminating = true ;
156
+ if ( this . terminatedError != null ) return ;
157
+ this . terminatedError = Error ( 'TMP IMP terminating' ) ;
110
158
// Prevent new tasks and wait for exising queue to drain
111
159
if ( ! force ) await this . settled ( ) ;
112
160
// Prevent terminations from creating new workers
@@ -116,9 +164,18 @@ class WorkerPool {
116
164
workerTerminatePs . push ( worker . terminate ( ) ) ;
117
165
}
118
166
await Promise . all ( workerTerminatePs ) ;
119
- // Console.log('workers', this.workers.size);
120
- // console.log('queue', this.queue.length);
121
- // console.log('freeWorkers', this.freeWorkers.length);
167
+ this . cleanUp ( this . terminatedError ) ;
168
+ }
169
+
170
+ protected cleanUp ( terminatedError : Error ) : void {
171
+ // Cleaning up remaining queue and observables
172
+ let task = this . queue . pop ( ) ;
173
+ while ( task != null ) {
174
+ const workerTask = new WorkerTask ( task . callback ) ;
175
+ workerTask . done ( undefined , terminatedError ) ;
176
+ task = this . queue . pop ( ) ;
177
+ }
178
+ this . $poolStatus . next ( 'terminated' ) ;
122
179
// Cleaning up subjects
123
180
this . $workerCreated . complete ( ) ;
124
181
this . $workerDestroyed . complete ( ) ;
@@ -133,14 +190,16 @@ class WorkerPool {
133
190
*/
134
191
public completed ( ) : Promise < void > {
135
192
return new Promise < void > ( ( resolve , reject ) => {
136
- if ( this . poolStatus === 'idle' ) return resolve ( ) ;
193
+ if ( this . poolStatus === 'idle' || this . poolStatus === 'terminated' ) {
194
+ return resolve ( ) ;
195
+ }
137
196
const errorSubscription = this . $workerError . subscribe ( ( e ) => {
138
197
errorSubscription . unsubscribe ( ) ;
139
198
stateSubscription . unsubscribe ( ) ;
140
199
reject ( e ) ;
141
200
} ) ;
142
201
const stateSubscription = this . $poolStatus . subscribe ( ( v ) => {
143
- if ( v === 'idle' ) {
202
+ if ( v === 'idle' || this . poolStatus === 'terminated' ) {
144
203
errorSubscription . unsubscribe ( ) ;
145
204
stateSubscription . unsubscribe ( ) ;
146
205
return resolve ( ) ;
@@ -154,9 +213,11 @@ class WorkerPool {
154
213
*/
155
214
public settled ( ) : Promise < void > {
156
215
return new Promise < void > ( ( resolve ) => {
157
- if ( this . poolStatus === 'idle' ) return resolve ( ) ;
216
+ if ( this . poolStatus === 'idle' || this . poolStatus === 'terminated' ) {
217
+ return resolve ( ) ;
218
+ }
158
219
const subscription = this . $poolStatus . subscribe ( ( v ) => {
159
- if ( v === 'idle' ) {
220
+ if ( v === 'idle' || this . poolStatus === 'terminated' ) {
160
221
subscription . unsubscribe ( ) ;
161
222
return resolve ( ) ;
162
223
}
0 commit comments