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