3
3
const assert = require ( 'assert' ) ;
4
4
const cluster = require ( 'cluster' ) ;
5
5
const os = require ( 'os' ) ;
6
-
7
- const Worker = require ( './worker' ) ;
6
+ const path = require ( 'path' ) ;
7
+ const serialize = require ( './utils/serialization' ) . serialize ;
8
8
9
9
class FastBootAppServer {
10
10
constructor ( options ) {
@@ -33,37 +33,17 @@ class FastBootAppServer {
33
33
34
34
this . propagateUI ( ) ;
35
35
36
- if ( cluster . isWorker ) {
37
- this . worker = new Worker ( {
38
- ui : this . ui ,
39
- distPath : this . distPath || process . env . FASTBOOT_DIST_PATH ,
40
- cache : this . cache ,
41
- gzip : this . gzip ,
42
- host : this . host ,
43
- port : this . port ,
44
- username : this . username ,
45
- password : this . password ,
46
- httpServer : this . httpServer ,
47
- beforeMiddleware : this . beforeMiddleware ,
48
- afterMiddleware : this . afterMiddleware ,
49
- buildSandboxGlobals : this . buildSandboxGlobals ,
50
- chunkedResponse : this . chunkedResponse ,
51
- } ) ;
36
+ this . workerCount = options . workerCount ||
37
+ ( process . env . NODE_ENV === 'test' ? 1 : null ) ||
38
+ os . cpus ( ) . length ;
52
39
53
- this . worker . start ( ) ;
54
- } else {
55
- this . workerCount = options . workerCount ||
56
- ( process . env . NODE_ENV === 'test' ? 1 : null ) ||
57
- os . cpus ( ) . length ;
40
+ this . _clusterInitialized = false ;
58
41
59
- assert ( this . distPath || this . downloader , "FastBootAppServer must be provided with either a distPath or a downloader option." ) ;
60
- assert ( ! ( this . distPath && this . downloader ) , "FastBootAppServer must be provided with either a distPath or a downloader option, but not both." ) ;
61
- }
42
+ assert ( this . distPath || this . downloader , "FastBootAppServer must be provided with either a distPath or a downloader option." ) ;
43
+ assert ( ! ( this . distPath && this . downloader ) , "FastBootAppServer must be provided with either a distPath or a downloader option, but not both." ) ;
62
44
}
63
45
64
46
start ( ) {
65
- if ( cluster . isWorker ) { return ; }
66
-
67
47
return this . initializeApp ( )
68
48
. then ( ( ) => this . subscribeToNotifier ( ) )
69
49
. then ( ( ) => this . forkWorkers ( ) )
@@ -74,6 +54,9 @@ class FastBootAppServer {
74
54
} )
75
55
. catch ( err => {
76
56
this . ui . writeLine ( err . stack ) ;
57
+ } )
58
+ . finally ( ( ) => {
59
+ this . _clusterInitialized = true ;
77
60
} ) ;
78
61
}
79
62
@@ -137,6 +120,12 @@ class FastBootAppServer {
137
120
}
138
121
}
139
122
123
+ /**
124
+ * send message to worker
125
+ *
126
+ * @method broadcast
127
+ * @param {Object } message
128
+ */
140
129
broadcast ( message ) {
141
130
let workers = cluster . workers ;
142
131
@@ -152,6 +141,10 @@ class FastBootAppServer {
152
141
forkWorkers ( ) {
153
142
let promises = [ ] ;
154
143
144
+ // https://nodejs.org/api/cluster.html#cluster_cluster_setupprimary_settings
145
+ // Note: cluster.setupPrimary in v16.0.0
146
+ cluster . setupMaster ( this . clusterSetupPrimary ( ) ) ;
147
+
155
148
for ( let i = 0 ; i < this . workerCount ; i ++ ) {
156
149
promises . push ( this . forkWorker ( ) ) ;
157
150
}
@@ -160,31 +153,53 @@ class FastBootAppServer {
160
153
}
161
154
162
155
forkWorker ( ) {
163
- let env = this . buildWorkerEnv ( ) ;
164
- let worker = cluster . fork ( env ) ;
156
+ let worker = cluster . fork ( this . buildWorkerEnv ( ) ) ;
165
157
166
- this . ui . writeLine ( `forked worker ${ worker . process . pid } ` ) ;
158
+ this . ui . writeLine ( `Worker ${ worker . process . pid } forked` ) ;
159
+
160
+ let firstBootResolve ;
161
+ let firstBootReject ;
162
+ const firstBootPromise = new Promise ( ( resolve , reject ) => {
163
+ firstBootResolve = resolve ;
164
+ firstBootReject = reject ;
165
+ } ) ;
166
+
167
+ if ( this . _clusterInitialized ) {
168
+ firstBootResolve ( ) ;
169
+ }
170
+
171
+ worker . on ( 'online' , ( ) => {
172
+ this . ui . writeLine ( `Worker ${ worker . process . pid } online.` ) ;
173
+ } ) ;
174
+
175
+ worker . on ( 'message' , ( message ) => {
176
+ if ( message . event === 'http-online' ) {
177
+ this . ui . writeLine ( `Worker ${ worker . process . pid } healthy.` ) ;
178
+ firstBootResolve ( ) ;
179
+ }
180
+ } ) ;
167
181
168
182
worker . on ( 'exit' , ( code , signal ) => {
183
+ let error ;
169
184
if ( signal ) {
170
- this . ui . writeLine ( `worker was killed by signal: ${ signal } `) ;
185
+ error = new Error ( `Worker ${ worker . process . pid } killed by signal: ${ signal } `) ;
171
186
} else if ( code !== 0 ) {
172
- this . ui . writeLine ( ` worker exited with error code: ${ code } `) ;
187
+ error = new Error ( `Worker ${ worker . process . pid } exited with error code: ${ code } `) ;
173
188
} else {
174
- this . ui . writeLine ( ` worker exited`) ;
189
+ error = new Error ( `Worker ${ worker . process . pid } exited gracefully. It should only exit when told to do so. `) ;
175
190
}
176
191
177
- this . forkWorker ( ) ;
192
+ if ( ! this . _clusterInitialized ) {
193
+ // Do not respawn for a failed first launch.
194
+ firstBootReject ( error ) ;
195
+ } else {
196
+ // Do respawn if you've ever successfully been initialized.
197
+ this . ui . writeLine ( error ) ;
198
+ this . forkWorker ( ) ;
199
+ }
178
200
} ) ;
179
201
180
- return new Promise ( resolve => {
181
- this . ui . writeLine ( 'worker online' ) ;
182
- worker . on ( 'message' , message => {
183
- if ( message . event === 'http-online' ) {
184
- resolve ( ) ;
185
- }
186
- } ) ;
187
- } ) ;
202
+ return firstBootPromise ;
188
203
}
189
204
190
205
buildWorkerEnv ( ) {
@@ -197,6 +212,36 @@ class FastBootAppServer {
197
212
return env ;
198
213
}
199
214
215
+ /**
216
+ * Extension point to allow configuring the default fork configuration.
217
+ *
218
+ * @method clusterSetupPrimary
219
+ * @returns {Object }
220
+ * @public
221
+ */
222
+ clusterSetupPrimary ( ) {
223
+ const workerOptions = {
224
+ ui : this . ui ,
225
+ distPath : this . distPath || process . env . FASTBOOT_DIST_PATH ,
226
+ cache : this . cache ,
227
+ gzip : this . gzip ,
228
+ host : this . host ,
229
+ port : this . port ,
230
+ username : this . username ,
231
+ password : this . password ,
232
+ httpServer : this . httpServer ,
233
+ beforeMiddleware : this . beforeMiddleware ,
234
+ afterMiddleware : this . afterMiddleware ,
235
+ buildSandboxGlobals : this . buildSandboxGlobals ,
236
+ chunkedResponse : this . chunkedResponse ,
237
+ } ;
238
+
239
+ const workerPath = this . workerPath || path . join ( __dirname , './worker-start.js' ) ;
240
+ return {
241
+ exec : workerPath ,
242
+ args : [ serialize ( workerOptions ) ]
243
+ } ;
244
+ }
200
245
}
201
246
202
247
module . exports = FastBootAppServer ;
0 commit comments