Skip to content

Commit ef997e9

Browse files
Enhance handleRenderRequest to support multiple new bundles and dependency timestamps
- Updated the `handleRenderRequest` function to accept an array of new bundles and dependency timestamps, improving flexibility in handling rendering requests. - Refactored related functions to manage multiple bundles effectively, including the creation of directories for new bundles. - Added tests to validate the handling of multiple bundles and scenarios where dependency timestamps are provided but not uploaded. - Introduced a new fixture for secondary bundles to support testing of the updated functionality.
1 parent 3385d6e commit ef997e9

File tree

5 files changed

+221
-52
lines changed

5 files changed

+221
-52
lines changed

packages/node-renderer/src/worker.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import fileExistsAsync from './shared/fileExistsAsync';
1515
import type { FastifyInstance, FastifyReply, FastifyRequest } from './worker/types';
1616
import checkProtocolVersion from './worker/checkProtocolVersionHandler';
1717
import authenticate from './worker/authHandler';
18-
import handleRenderRequest from './worker/handleRenderRequest';
18+
import { handleRenderRequest, type ProvidedNewBundle } from './worker/handleRenderRequest';
1919
import {
2020
errorResponseResult,
2121
formatExceptionMessage,
@@ -174,7 +174,10 @@ export default function run(config: Partial<Config>) {
174174
// the digest is part of the request URL. Yes, it's not used here, but the
175175
// server logs might show it to distinguish different requests.
176176
app.post<{
177-
Body: { renderingRequest: string } & Record<string, Asset>;
177+
Body: {
178+
renderingRequest: string;
179+
dependencyBundleTimestamps?: string[];
180+
};
178181
// Can't infer from the route like Express can
179182
Params: { bundleTimestamp: string; renderRequestDigest: string };
180183
}>('/bundles/:bundleTimestamp/render/:renderRequestDigest', async (req, res) => {
@@ -193,13 +196,15 @@ export default function run(config: Partial<Config>) {
193196
// await delay(100000);
194197
// }
195198

196-
const { renderingRequest } = req.body;
199+
const { renderingRequest, dependencyBundleTimestamps } = req.body;
197200
const { bundleTimestamp } = req.params;
198-
let providedNewBundle: Asset | undefined;
201+
const providedNewBundles: ProvidedNewBundle[] = [];
199202
const assetsToCopy: Asset[] = [];
200203
Object.entries(req.body).forEach(([key, value]) => {
201-
if (key === 'bundle') {
202-
providedNewBundle = value as Asset;
204+
if (key === 'bundle' && isAsset(value)) {
205+
providedNewBundles.push({ timestamp: bundleTimestamp, bundle: value });
206+
} else if (key.startsWith('bundle_') && isAsset(value)) {
207+
providedNewBundles.push({ timestamp: key.replace('bundle_', ''), bundle: value });
203208
} else if (isAsset(value)) {
204209
assetsToCopy.push(value);
205210
}
@@ -211,7 +216,8 @@ export default function run(config: Partial<Config>) {
211216
const result = await handleRenderRequest({
212217
renderingRequest,
213218
bundleTimestamp,
214-
providedNewBundle,
219+
dependencyBundleTimestamps,
220+
providedNewBundles,
215221
assetsToCopy,
216222
});
217223
await setResponse(result, res);

packages/node-renderer/src/worker/handleRenderRequest.ts

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import cluster from 'cluster';
99
import path from 'path';
10+
import { mkdir } from 'fs/promises';
1011
import { lock, unlock } from '../shared/locks';
1112
import fileExistsAsync from '../shared/fileExistsAsync';
1213
import log from '../shared/log';
@@ -26,6 +27,11 @@ import { getConfig } from '../shared/configBuilder';
2627
import * as errorReporter from '../shared/errorReporter';
2728
import { buildVM, hasVMContextForBundle, runInVM } from './vm';
2829

30+
export type ProvidedNewBundle = {
31+
timestamp: string | number;
32+
bundle: Asset;
33+
};
34+
2935
async function prepareResult(
3036
renderingRequest: string,
3137
bundleFilePathPerTimestamp: string,
@@ -70,7 +76,8 @@ async function prepareResult(
7076

7177
function 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
*/
8289
async 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} \
123134
to ${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+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
global.ReactOnRails = {
2+
dummy: { html: 'Dummy Object from secondary bundle' },
3+
};

0 commit comments

Comments
 (0)