77
88import cluster from 'cluster' ;
99import path from 'path' ;
10+ import { mkdir } from 'fs/promises' ;
1011import { lock , unlock } from '../shared/locks' ;
1112import fileExistsAsync from '../shared/fileExistsAsync' ;
1213import log from '../shared/log' ;
@@ -26,6 +27,11 @@ import { getConfig } from '../shared/configBuilder';
2627import * as errorReporter from '../shared/errorReporter' ;
2728import { buildVM , hasVMContextForBundle , runInVM } from './vm' ;
2829
30+ export type ProvidedNewBundle = {
31+ timestamp : string | number ;
32+ bundle : Asset ;
33+ } ;
34+
2935async function prepareResult (
3036 renderingRequest : string ,
3137 bundleFilePathPerTimestamp : string ,
@@ -70,7 +76,8 @@ async function prepareResult(
7076
7177function getRequestBundleFilePath ( bundleTimestamp : string | number ) {
7278 const { bundlePath } = getConfig ( ) ;
73- return path . join ( bundlePath , `${ bundleTimestamp } .js` ) ;
79+ const bundleDirectory = path . join ( bundlePath , `${ bundleTimestamp } ` ) ;
80+ return path . join ( bundleDirectory , `${ bundleTimestamp } .js` ) ;
7481}
7582
7683/**
@@ -80,11 +87,13 @@ function getRequestBundleFilePath(bundleTimestamp: string | number) {
8087 * @param assetsToCopy might be null
8188 */
8289async function handleNewBundleProvided (
83- bundleFilePathPerTimestamp : string ,
84- providedNewBundle : Asset ,
8590 renderingRequest : string ,
91+ providedNewBundle : ProvidedNewBundle ,
8692 assetsToCopy : Asset [ ] | null | undefined ,
87- ) : Promise < ResponseResult > {
93+ ) : Promise < ResponseResult | undefined > {
94+ const bundleFilePathPerTimestamp = getRequestBundleFilePath ( providedNewBundle . timestamp ) ;
95+ const bundleDirectory = path . dirname ( bundleFilePathPerTimestamp ) ;
96+ await mkdir ( bundleDirectory , { recursive : true } ) ;
8897 log . info ( 'Worker received new bundle: %s' , bundleFilePathPerTimestamp ) ;
8998
9099 let lockAcquired = false ;
@@ -104,22 +113,24 @@ async function handleNewBundleProvided(
104113 }
105114
106115 try {
107- log . info ( `Moving uploaded file ${ providedNewBundle . savedFilePath } to ${ bundleFilePathPerTimestamp } ` ) ;
108- await moveUploadedAsset ( providedNewBundle , bundleFilePathPerTimestamp ) ;
116+ log . info (
117+ `Moving uploaded file ${ providedNewBundle . bundle . savedFilePath } to ${ bundleFilePathPerTimestamp } ` ,
118+ ) ;
119+ await moveUploadedAsset ( providedNewBundle . bundle , bundleFilePathPerTimestamp ) ;
109120 if ( assetsToCopy ) {
110121 await moveUploadedAssets ( assetsToCopy ) ;
111122 }
112123
113124 log . info (
114- `Completed moving uploaded file ${ providedNewBundle . savedFilePath } to ${ bundleFilePathPerTimestamp } ` ,
125+ `Completed moving uploaded file ${ providedNewBundle . bundle . savedFilePath } to ${ bundleFilePathPerTimestamp } ` ,
115126 ) ;
116127 } catch ( error ) {
117128 const fileExists = await fileExistsAsync ( bundleFilePathPerTimestamp ) ;
118129 if ( ! fileExists ) {
119130 const msg = formatExceptionMessage (
120131 renderingRequest ,
121132 error ,
122- `Unexpected error when moving the bundle from ${ providedNewBundle . savedFilePath } \
133+ `Unexpected error when moving the bundle from ${ providedNewBundle . bundle . savedFilePath } \
123134to ${ bundleFilePathPerTimestamp } )` ,
124135 ) ;
125136 log . error ( msg ) ;
@@ -131,20 +142,7 @@ to ${bundleFilePathPerTimestamp})`,
131142 ) ;
132143 }
133144
134- try {
135- // Either this process or another process placed the file. Because the lock is acquired, the
136- // file must be fully written
137- log . info ( 'buildVM, bundleFilePathPerTimestamp' , bundleFilePathPerTimestamp ) ;
138- await buildVM ( bundleFilePathPerTimestamp ) ;
139- return prepareResult ( renderingRequest , bundleFilePathPerTimestamp ) ;
140- } catch ( error ) {
141- const msg = formatExceptionMessage (
142- renderingRequest ,
143- error ,
144- `Unexpected error when building the VM ${ bundleFilePathPerTimestamp } ` ,
145- ) ;
146- return Promise . resolve ( errorResponseResult ( msg ) ) ;
147- }
145+ return undefined ;
148146 } finally {
149147 if ( lockAcquired ) {
150148 log . info ( 'About to unlock %s from worker %i' , lockfileName , workerIdLabel ( ) ) ;
@@ -164,20 +162,44 @@ to ${bundleFilePathPerTimestamp})`,
164162 }
165163}
166164
165+ async function handleNewBundlesProvided (
166+ renderingRequest : string ,
167+ providedNewBundles : ProvidedNewBundle [ ] ,
168+ assetsToCopy : Asset [ ] | null | undefined ,
169+ ) : Promise < ResponseResult | undefined > {
170+ log . info ( 'Worker received new bundles: %s' , providedNewBundles ) ;
171+
172+ // const handlingPromises = providedNewBundles.map((providedNewBundle) => handleNewBundleProvided(renderingRequest, providedNewBundle, assetsToCopy));
173+ // const results = await Promise.all(handlingPromises);
174+ // const errorResults = results.filter((result) => result?.status !== 200);
175+ // if (errorResults.length > 0) {
176+ // return errorResults[0];
177+ // }
178+ // return undefined;
179+ const handlingPromises = providedNewBundles . map ( ( providedNewBundle ) =>
180+ handleNewBundleProvided ( renderingRequest , providedNewBundle , assetsToCopy ) ,
181+ ) ;
182+ const results = await Promise . all ( handlingPromises ) ;
183+ const errorResult = results . find ( ( result ) => result !== undefined ) ;
184+ return errorResult ;
185+ }
186+
167187/**
168188 * Creates the result for the Fastify server to use.
169189 * @returns Promise where the result contains { status, data, headers } to
170190 * send back to the browser.
171191 */
172- export = async function handleRenderRequest ( {
192+ export async function handleRenderRequest ( {
173193 renderingRequest,
174194 bundleTimestamp,
175- providedNewBundle,
195+ dependencyBundleTimestamps,
196+ providedNewBundles,
176197 assetsToCopy,
177198} : {
178199 renderingRequest : string ;
179200 bundleTimestamp : string | number ;
180- providedNewBundle ?: Asset | null ;
201+ dependencyBundleTimestamps ?: string [ ] | number [ ] ;
202+ providedNewBundles ?: ProvidedNewBundle [ ] | null ;
181203 assetsToCopy ?: Asset [ ] | null ;
182204} ) : Promise < ResponseResult > {
183205 try {
@@ -189,19 +211,25 @@ export = async function handleRenderRequest({
189211 }
190212
191213 // If gem has posted updated bundle:
192- if ( providedNewBundle ) {
193- return handleNewBundleProvided (
194- bundleFilePathPerTimestamp ,
195- providedNewBundle ,
196- renderingRequest ,
197- assetsToCopy ,
198- ) ;
214+ if ( providedNewBundles ) {
215+ const result = await handleNewBundlesProvided ( renderingRequest , providedNewBundles , assetsToCopy ) ;
216+ if ( result ) {
217+ return result ;
218+ }
199219 }
200220
201221 // Check if the bundle exists:
202- const fileExists = await fileExistsAsync ( bundleFilePathPerTimestamp ) ;
203- if ( ! fileExists ) {
204- log . info ( `No saved bundle ${ bundleFilePathPerTimestamp } . Requesting a new bundle.` ) ;
222+ const notExistingBundles = await Promise . all (
223+ [ ...( dependencyBundleTimestamps ?? [ ] ) , bundleTimestamp ] . map ( async ( timestamp ) => {
224+ const bundleFilePath = getRequestBundleFilePath ( timestamp ) ;
225+ const fileExists = await fileExistsAsync ( bundleFilePath ) ;
226+ return ! fileExists ;
227+ } ) ,
228+ ) ;
229+ if ( notExistingBundles . some ( ( bundle ) => bundle ) ) {
230+ log . info (
231+ `No saved bundle${ notExistingBundles . length > 1 ? 's' : '' } ${ notExistingBundles . join ( ', ' ) } . Requesting a new bundle${ notExistingBundles . length > 1 ? 's' : '' } .` ,
232+ ) ;
205233 return Promise . resolve ( {
206234 headers : { 'Cache-Control' : 'no-cache, no-store, max-age=0, must-revalidate' } ,
207235 status : 410 ,
@@ -224,4 +252,4 @@ export = async function handleRenderRequest({
224252 errorReporter . message ( msg ) ;
225253 return Promise . reject ( error as Error ) ;
226254 }
227- } ;
255+ }
0 commit comments