33const assert = require ( 'assert' ) ;
44const cluster = require ( 'cluster' ) ;
55const os = require ( 'os' ) ;
6-
7- const Worker = require ( './worker' ) ;
6+ const path = require ( 'path' ) ;
7+ const serialize = require ( './utils/serialization' ) . serialize ;
88
99class FastBootAppServer {
1010 constructor ( options ) {
@@ -34,37 +34,17 @@ class FastBootAppServer {
3434
3535 this . propagateUI ( ) ;
3636
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 ;
5340
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 ;
5942
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." ) ;
6345 }
6446
6547 start ( ) {
66- if ( cluster . isWorker ) { return ; }
67-
6848 return this . initializeApp ( )
6949 . then ( ( ) => this . subscribeToNotifier ( ) )
7050 . then ( ( ) => this . forkWorkers ( ) )
@@ -75,6 +55,9 @@ class FastBootAppServer {
7555 } )
7656 . catch ( err => {
7757 this . ui . writeLine ( err . stack ) ;
58+ } )
59+ . finally ( ( ) => {
60+ this . _clusterInitialized = true ;
7861 } ) ;
7962 }
8063
@@ -138,6 +121,12 @@ class FastBootAppServer {
138121 }
139122 }
140123
124+ /**
125+ * send message to worker
126+ *
127+ * @method broadcast
128+ * @param {Object } message
129+ */
141130 broadcast ( message ) {
142131 let workers = cluster . workers ;
143132
@@ -153,6 +142,10 @@ class FastBootAppServer {
153142 forkWorkers ( ) {
154143 let promises = [ ] ;
155144
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+
156149 for ( let i = 0 ; i < this . workerCount ; i ++ ) {
157150 promises . push ( this . forkWorker ( ) ) ;
158151 }
@@ -161,31 +154,53 @@ class FastBootAppServer {
161154 }
162155
163156 forkWorker ( ) {
164- let env = this . buildWorkerEnv ( ) ;
165- let worker = cluster . fork ( env ) ;
157+ let worker = cluster . fork ( this . buildWorkerEnv ( ) ) ;
166158
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+ } ) ;
168182
169183 worker . on ( 'exit' , ( code , signal ) => {
184+ let error ;
170185 if ( signal ) {
171- this . ui . writeLine ( `worker was killed by signal: ${ signal } `) ;
186+ error = new Error ( `Worker ${ worker . process . pid } killed by signal: ${ signal } `) ;
172187 } 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 } `) ;
174189 } 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. `) ;
176191 }
177192
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+ }
179201 } ) ;
180202
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 ;
189204 }
190205
191206 buildWorkerEnv ( ) {
@@ -198,6 +213,36 @@ class FastBootAppServer {
198213 return env ;
199214 }
200215
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+ }
201246}
202247
203248module . exports = FastBootAppServer ;
0 commit comments