Skip to content

Commit 31fa75c

Browse files
add gracefulWorkerRestartTimeout node renderer config
1 parent 4d8e807 commit 31fa75c

File tree

6 files changed

+36
-9
lines changed

6 files changed

+36
-9
lines changed

react_on_rails_pro/docs/installation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ const config = {
129129
// allWorkersRestartInterval: 15,
130130
// time in minutes between each worker restarting when restarting all workers
131131
// delayBetweenIndividualWorkerRestarts: 2,
132+
// Also, you can set he parameter gracefulWorkerRestartTimeout to force the worker to restart
133+
// If it's the time for the worker to restart, the worker waits until it serves all active requests before restarting
134+
// If a worker stuck because of a memory leakage or an infinite loop, you can set a timeout that master waits for it before killing the worker
132135
}
133136
134137
// Renderer detects a total number of CPUs on virtual hostings like Heroku

react_on_rails_pro/docs/node-renderer/js-configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Here are the options available for the JavaScript renderer configuration object,
2222
If no password is set, no authentication will be required.
2323
1. **allWorkersRestartInterval** (default: `env.RENDERER_ALL_WORKERS_RESTART_INTERVAL`) - Interval in minutes between scheduled restarts of all workers. By default restarts are not enabled. If restarts are enabled, `delayBetweenIndividualWorkerRestarts` should also be set.
2424
1. **delayBetweenIndividualWorkerRestarts** (default: `env.RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS`) - Interval in minutes between individual worker restarts (when cluster restart is triggered). By default restarts are not enabled. If restarts are enabled, `allWorkersRestartInterval` should also be set.
25+
1. **gracefulWorkerRestartTimeout**: (default: `env.GRACEFUL_WORKER_RESTART_TIMEOUT`) - Time in seconds that master waits for worker to gracefully restart (after serving all active requests) before killing it. You need to use this config to avoid situations where worker stucks at an inifite loop and doesn't restart. This config is only usable if worker restart is enabled. This time is counted starting from the time when should the worker restart, if that timeout value passed without restarting the worker, the worker is killed.
2526
1. **maxDebugSnippetLength** (default: 1000) - If the rendering request is longer than this, it will be truncated in exception and logging messages.
2627
1. **supportModules** - (default: `env.RENDERER_SUPPORT_MODULES || null`) - If set to true, `supportModules` enables the server-bundle code to call a default set of NodeJS global objects and functions that get added to the VM context:
2728
`{ Buffer, TextDecoder, TextEncoder, URLSearchParams, ReadableStream, process, setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate, queueMicrotask }`.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# Node Renderer Troubleshooting
22

33
- If you enabled restarts (having `allWorkersRestartInterval` and `delayBetweenIndividualWorkerRestarts`), you should set it with a high number to avoid the app from crashing because all Node renderer workers are stopped/killed.
4+
5+
- If your app contains streamed pages that take too much time to be streamed to the client, ensure to not set the `gracefulWorkerRestartTimeout` parameter or set to a high number, so the worker is not killed while it's still serving an active request.

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ export = function masterRun(runningConfig?: Partial<Config>) {
1919

2020
// Store config in app state. From now it can be loaded by any module using getConfig():
2121
const config = buildConfig(runningConfig);
22-
const { workersCount, allWorkersRestartInterval, delayBetweenIndividualWorkerRestarts } = config;
22+
const {
23+
workersCount,
24+
allWorkersRestartInterval,
25+
delayBetweenIndividualWorkerRestarts,
26+
gracefulWorkerRestartTimeout,
27+
} = config;
2328

2429
logSanitizedConfig();
2530

@@ -51,7 +56,7 @@ export = function masterRun(runningConfig?: Partial<Config>) {
5156

5257
const allWorkersRestartIntervalMS = allWorkersRestartInterval * MILLISECONDS_IN_MINUTE;
5358
const scheduleWorkersRestart = () => {
54-
void restartWorkers(delayBetweenIndividualWorkerRestarts).finally(() => {
59+
void restartWorkers(delayBetweenIndividualWorkerRestarts, gracefulWorkerRestartTimeout).finally(() => {
5560
setTimeout(scheduleWorkersRestart, allWorkersRestartIntervalMS);
5661
});
5762
};

react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ declare module 'cluster' {
1515
}
1616
}
1717

18-
export = async function restartWorkers(delayBetweenIndividualWorkerRestarts: number) {
18+
export = async function restartWorkers(
19+
delayBetweenIndividualWorkerRestarts: number,
20+
gracefulWorkerRestartTimeout: number | undefined,
21+
) {
1922
log.info('Started scheduled restart of workers');
2023

2124
if (!cluster.workers) {
@@ -39,12 +42,15 @@ export = async function restartWorkers(delayBetweenIndividualWorkerRestarts: num
3942
};
4043
worker.on('exit', onExit);
4144

42-
timeout = setTimeout(() => {
43-
log.debug('Worker #%d timed out, forcing kill it', worker.id);
44-
worker.destroy();
45-
worker.off('exit', onExit);
46-
resolve();
47-
}, 100_000);
45+
// Zero means no timeout
46+
if (gracefulWorkerRestartTimeout) {
47+
timeout = setTimeout(() => {
48+
log.debug('Worker #%d timed out, forcing kill it', worker.id);
49+
worker.destroy();
50+
worker.off('exit', onExit);
51+
resolve();
52+
}, gracefulWorkerRestartTimeout);
53+
}
4854
});
4955
// eslint-disable-next-line no-await-in-loop
5056
await new Promise((resolve) => {

react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ export interface Config {
5858
allWorkersRestartInterval: number | undefined;
5959
// Time in minutes between each worker restarting when restarting all workers
6060
delayBetweenIndividualWorkerRestarts: number | undefined;
61+
// Time in seconds to wait for worker to restart before killing it
62+
// Set it to 0 or undefined to never kill the worker
63+
gracefulWorkerRestartTimeout: number | undefined;
6164
// If the rendering request is longer than this, it will be truncated in exception and logging messages
6265
maxDebugSnippetLength: number;
6366
// @deprecated See https://www.shakacode.com/react-on-rails-pro/docs/node-renderer/error-reporting-and-tracing.
@@ -165,6 +168,10 @@ const defaultConfig: Config = {
165168
? parseInt(env.RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS, 10)
166169
: undefined,
167170

171+
gracefulWorkerRestartTimeout: env.GRACEFUL_WORKER_RESTART_TIMEOUT
172+
? parseInt(env.GRACEFUL_WORKER_RESTART_TIMEOUT, 10)
173+
: undefined,
174+
168175
maxDebugSnippetLength: MAX_DEBUG_SNIPPET_LENGTH,
169176

170177
// default to true if empty, otherwise it is set to false
@@ -195,6 +202,8 @@ function envValuesUsed() {
195202
RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS:
196203
!userConfig.delayBetweenIndividualWorkerRestarts &&
197204
env.RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS,
205+
GRACEFUL_WORKER_RESTART_TIMEOUT:
206+
!userConfig.gracefulWorkerRestartTimeout && env.GRACEFUL_WORKER_RESTART_TIMEOUT,
198207
INCLUDE_TIMER_POLYFILLS: !('includeTimerPolyfills' in userConfig) && env.INCLUDE_TIMER_POLYFILLS,
199208
REPLAY_SERVER_ASYNC_OPERATION_LOGS:
200209
!userConfig.replayServerAsyncOperationLogs && env.REPLAY_SERVER_ASYNC_OPERATION_LOGS,
@@ -209,6 +218,7 @@ function sanitizedSettings(aConfig: Partial<Config> | undefined, defaultValue?:
209218
password: aConfig.password != null ? '<MASKED>' : defaultValue,
210219
allWorkersRestartInterval: aConfig.allWorkersRestartInterval || defaultValue,
211220
delayBetweenIndividualWorkerRestarts: aConfig.delayBetweenIndividualWorkerRestarts || defaultValue,
221+
gracefulWorkerRestartTimeout: aConfig.gracefulWorkerRestartTimeout || defaultValue,
212222
}
213223
: {};
214224
}

0 commit comments

Comments
 (0)