Skip to content

Commit bf55456

Browse files
Revert "Refactor incremental rendering logic and enhance bundle validation"
This reverts commit 26bac50ae9742e25ff75e5d1b27225a4495ef3dc.
1 parent 8b10a30 commit bf55456

File tree

6 files changed

+417
-445
lines changed

6 files changed

+417
-445
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ import {
3434
getAssetPath,
3535
getBundleDirectory,
3636
deleteUploadedAssets,
37+
validateBundlesExist,
3738
} from './shared/utils';
3839
import * as errorReporter from './shared/errorReporter';
3940
import { lock, unlock } from './shared/locks';
4041
import { startSsrRequestOptions, trace } from './shared/tracing';
41-
import { validateAndGetBundlePaths } from './worker/sharedRenderUtils';
4242

4343
// Uncomment the below for testing timeouts:
4444
// import { delay } from './shared/utils';
@@ -323,16 +323,15 @@ export default function run(config: Partial<Config>) {
323323
};
324324
}
325325

326-
// Bundle validation using shared utility
326+
// Bundle validation
327327
const dependencyBundleTimestamps = extractBodyArrayField(
328328
tempReqBody as WithBodyArrayField<Record<string, unknown>, 'dependencyBundleTimestamps'>,
329329
'dependencyBundleTimestamps',
330330
);
331-
332-
const validationResult = await validateAndGetBundlePaths(bundleTimestamp, dependencyBundleTimestamps);
333-
if (!validationResult.success) {
331+
const missingBundleError = await validateBundlesExist(bundleTimestamp, dependencyBundleTimestamps);
332+
if (missingBundleError) {
334333
return {
335-
response: validationResult.error!,
334+
response: missingBundleError,
336335
shouldContinue: false,
337336
};
338337
}
Lines changed: 20 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { Readable } from 'stream';
2-
import { EventEmitter } from 'events';
32
import type { ResponseResult } from '../shared/utils';
4-
import { validateAndGetBundlePaths, buildVMsForBundles, executeRenderInVM } from './sharedRenderUtils';
53

64
export type IncrementalRenderSink = {
75
/** Called for every subsequent NDJSON object after the first one */
@@ -24,156 +22,36 @@ export type IncrementalRenderResult = {
2422
};
2523

2624
/**
27-
* Starts handling an incremental render request. This function:
28-
* - Creates an EventEmitter for handling updates
29-
* - Builds the VM if needed
30-
* - Executes the initial render request
31-
* - Returns both a stream that will be sent to the client and a sink for incoming chunks
25+
* Starts handling an incremental render request. This function is intended to:
26+
* - Initialize any resources needed to process the render
27+
* - Return both a stream that will be sent to the client and a sink for incoming chunks
28+
*
29+
* NOTE: This is intentionally left unimplemented. Tests should mock this.
3230
*/
33-
export async function handleIncrementalRenderRequest(
34-
initial: IncrementalRenderInitialRequest,
35-
): Promise<IncrementalRenderResult> {
36-
const { renderingRequest, bundleTimestamp, dependencyBundleTimestamps } = initial;
37-
38-
// Create event emitter for this specific request
39-
const updateEmitter = new EventEmitter();
40-
41-
// Validate bundles and get paths
42-
const validationResult = await validateAndGetBundlePaths(bundleTimestamp, dependencyBundleTimestamps);
43-
if (!validationResult.success || !validationResult.bundleFilePath) {
44-
return {
45-
response: validationResult.error || {
46-
status: 500,
47-
headers: { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate' },
48-
data: 'Bundle validation failed',
49-
},
50-
sink: {
51-
add: () => {
52-
/* no-op */
53-
},
54-
end: () => {
55-
/* no-op */
56-
},
57-
abort: () => {
58-
/* no-op */
59-
},
60-
},
61-
};
62-
}
63-
64-
// Build VMs
65-
const vmBuildResult = await buildVMsForBundles(
66-
validationResult.bundleFilePath,
67-
validationResult.dependencyBundleFilePaths || [],
68-
);
69-
if (!vmBuildResult.success) {
70-
return {
71-
response: vmBuildResult.error || {
72-
status: 500,
73-
headers: { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate' },
74-
data: 'VM building failed',
75-
},
76-
sink: {
77-
add: () => {
78-
/* no-op */
79-
},
80-
end: () => {
81-
/* no-op */
82-
},
83-
abort: () => {
84-
/* no-op */
85-
},
86-
},
87-
};
88-
}
89-
90-
// Create the response stream
91-
const responseStream = new Readable({
92-
read() {
93-
// No-op - data will be pushed via events
94-
},
95-
});
96-
97-
// Set up event listeners for the response stream
98-
updateEmitter.on('update', (data: unknown) => {
99-
// Push update data to the response stream
100-
responseStream.push(`${JSON.stringify(data)}\n`);
101-
});
102-
103-
updateEmitter.on('end', () => {
104-
// End the response stream
105-
responseStream.push(null);
106-
});
107-
108-
updateEmitter.on('error', (error: unknown) => {
109-
// Handle error and end stream
110-
const errorMessage = error instanceof Error ? error.message : String(error);
111-
responseStream.push(`{"error":"${errorMessage}"}\n`);
112-
responseStream.push(null);
113-
});
114-
115-
// Execute the initial render request with the update emitter
116-
const executionResult = await executeRenderInVM(
117-
renderingRequest,
118-
validationResult.bundleFilePath,
119-
updateEmitter,
120-
);
121-
122-
// Handle the render result
123-
if (executionResult.success && executionResult.result) {
124-
// Initial render completed successfully
125-
if (executionResult.result.data) {
126-
const dataString =
127-
typeof executionResult.result.data === 'string'
128-
? executionResult.result.data
129-
: JSON.stringify(executionResult.result.data);
130-
responseStream.push(`${dataString}\n`);
131-
}
132-
} else {
133-
// Render failed
134-
const errorMessage =
135-
typeof executionResult.error?.data === 'string' ? executionResult.error.data : 'Unknown render error';
136-
responseStream.push(`{"error":"${errorMessage}"}\n`);
137-
responseStream.push(null);
138-
return {
139-
response: executionResult.error || {
140-
status: 500,
141-
headers: { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate' },
142-
data: 'Render execution failed',
143-
},
144-
sink: {
145-
add: () => {
146-
/* no-op */
147-
},
148-
end: () => {
149-
/* no-op */
150-
},
151-
abort: () => {
152-
/* no-op */
153-
},
154-
},
155-
};
156-
}
157-
158-
return {
31+
export function handleIncrementalRenderRequest(initial: IncrementalRenderInitialRequest): Promise<IncrementalRenderResult> {
32+
// Empty placeholder implementation. Real logic will be added later.
33+
return Promise.resolve({
15934
response: {
16035
status: 200,
16136
headers: { 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate' },
162-
stream: responseStream,
163-
},
37+
stream: new Readable({
38+
read() {
39+
// No-op for now
40+
},
41+
}),
42+
} as ResponseResult,
16443
sink: {
165-
add: (chunk: unknown) => {
166-
// Emit event when chunk arrives
167-
updateEmitter.emit('update', chunk);
44+
add: () => {
45+
/* no-op */
16846
},
16947
end: () => {
170-
updateEmitter.emit('end');
48+
/* no-op */
17149
},
172-
abort: (error: unknown) => {
173-
updateEmitter.emit('error', error);
50+
abort: () => {
51+
/* no-op */
17452
},
17553
},
176-
};
54+
});
17755
}
17856

17957
export type { ResponseResult };

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

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @module worker/handleRenderRequest
66
*/
77

8+
import cluster from 'cluster';
89
import path from 'path';
910
import { mkdir } from 'fs/promises';
1011
import { lock, unlock } from '../shared/locks';
@@ -18,17 +19,15 @@ import {
1819
copyUploadedAssets,
1920
ResponseResult,
2021
moveUploadedAsset,
22+
isReadableStream,
23+
isErrorRenderResult,
2124
getRequestBundleFilePath,
2225
deleteUploadedAssets,
26+
validateBundlesExist,
2327
} from '../shared/utils';
2428
import { getConfig } from '../shared/configBuilder';
25-
import { hasVMContextForBundle } from './vm';
26-
import {
27-
validateAndGetBundlePaths,
28-
buildVMsForBundles,
29-
executeRenderInVM,
30-
createRenderErrorResponse,
31-
} from './sharedRenderUtils';
29+
import * as errorReporter from '../shared/errorReporter';
30+
import { buildVM, hasVMContextForBundle, runInVM } from './vm';
3231

3332
export type ProvidedNewBundle = {
3433
timestamp: string | number;
@@ -40,15 +39,36 @@ async function prepareResult(
4039
bundleFilePathPerTimestamp: string,
4140
): Promise<ResponseResult> {
4241
try {
43-
const executionResult = await executeRenderInVM(renderingRequest, bundleFilePathPerTimestamp);
42+
const result = await runInVM(renderingRequest, bundleFilePathPerTimestamp, cluster);
43+
44+
let exceptionMessage = null;
45+
if (!result) {
46+
const error = new Error('INVALID NIL or NULL result for rendering');
47+
exceptionMessage = formatExceptionMessage(renderingRequest, error, 'INVALID result for prepareResult');
48+
} else if (isErrorRenderResult(result)) {
49+
({ exceptionMessage } = result);
50+
}
51+
52+
if (exceptionMessage) {
53+
return errorResponseResult(exceptionMessage);
54+
}
4455

45-
if (!executionResult.success || !executionResult.result) {
46-
return executionResult.error || errorResponseResult('Unknown error during render execution');
56+
if (isReadableStream(result)) {
57+
return {
58+
headers: { 'Cache-Control': 'public, max-age=31536000' },
59+
status: 200,
60+
stream: result,
61+
};
4762
}
4863

49-
return executionResult.result;
64+
return {
65+
headers: { 'Cache-Control': 'public, max-age=31536000' },
66+
status: 200,
67+
data: result,
68+
};
5069
} catch (err) {
51-
return createRenderErrorResponse(renderingRequest, err, 'Unknown error calling runInVM');
70+
const exceptionMessage = formatExceptionMessage(renderingRequest, err, 'Unknown error calling runInVM');
71+
return errorResponseResult(exceptionMessage);
5272
}
5373
}
5474

@@ -173,6 +193,7 @@ export async function handleRenderRequest({
173193
assetsToCopy?: Asset[] | null;
174194
}): Promise<ResponseResult> {
175195
try {
196+
// const bundleFilePathPerTimestamp = getRequestBundleFilePath(bundleTimestamp);
176197
const allBundleFilePaths = Array.from(
177198
new Set([...(dependencyBundleTimestamps ?? []), bundleTimestamp].map(getRequestBundleFilePath)),
178199
);
@@ -201,27 +222,25 @@ export async function handleRenderRequest({
201222
}
202223
}
203224

204-
// Validate bundles and get paths
205-
const validationResult = await validateAndGetBundlePaths(bundleTimestamp, dependencyBundleTimestamps);
206-
if (!validationResult.success || !validationResult.bundleFilePath) {
207-
return validationResult.error || errorResponseResult('Bundle validation failed');
225+
// Check if the bundle exists:
226+
const missingBundleError = await validateBundlesExist(bundleTimestamp, dependencyBundleTimestamps);
227+
if (missingBundleError) {
228+
return missingBundleError;
208229
}
209230

210-
// Build VMs
211-
const vmBuildResult = await buildVMsForBundles(
212-
validationResult.bundleFilePath,
213-
validationResult.dependencyBundleFilePaths || [],
214-
);
215-
if (!vmBuildResult.success) {
216-
return vmBuildResult.error || errorResponseResult('VM building failed');
217-
}
231+
// The bundle exists, but the VM has not yet been created.
232+
// Another worker must have written it or it was saved during deployment.
233+
log.info('Bundle %s exists. Building VM for worker %s.', entryBundleFilePath, workerIdLabel());
234+
await Promise.all(allBundleFilePaths.map((bundleFilePath) => buildVM(bundleFilePath)));
218235

219236
return await prepareResult(renderingRequest, entryBundleFilePath);
220237
} catch (error) {
221-
return createRenderErrorResponse(
238+
const msg = formatExceptionMessage(
222239
renderingRequest,
223240
error,
224241
'Caught top level error in handleRenderRequest',
225242
);
243+
errorReporter.message(msg);
244+
return Promise.reject(error as Error);
226245
}
227246
}

0 commit comments

Comments
 (0)