feat: N8N_RUNNERS_MAX_OLD_SPACE_SIZE configuration (no-changelog) (#11419)

This commit is contained in:
Tomi Turtiainen 2024-10-28 16:14:38 +02:00 committed by GitHub
parent 3f30a08c8a
commit c56f30ce15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 77 additions and 21 deletions

View file

@ -11,7 +11,8 @@
"N8N_RUNNERS_N8N_URI", "N8N_RUNNERS_N8N_URI",
"N8N_RUNNERS_MAX_PAYLOAD", "N8N_RUNNERS_MAX_PAYLOAD",
"NODE_FUNCTION_ALLOW_BUILTIN", "NODE_FUNCTION_ALLOW_BUILTIN",
"NODE_FUNCTION_ALLOW_EXTERNAL" "NODE_FUNCTION_ALLOW_EXTERNAL",
"NODE_OPTIONS"
], ],
"uid": 2000, "uid": 2000,
"gid": 2000 "gid": 2000

View file

@ -42,4 +42,8 @@ export class TaskRunnersConfig {
/** Which task runner to launch from the config */ /** Which task runner to launch from the config */
@Env('N8N_RUNNERS_LAUNCHER_RUNNER') @Env('N8N_RUNNERS_LAUNCHER_RUNNER')
launcherRunner: string = 'javascript'; launcherRunner: string = 'javascript';
/** The --max-old-space-size option to use for the runner (in MB). Default means node.js will determine it based on the available memory. */
@Env('N8N_RUNNERS_MAX_OLD_SPACE_SIZE')
maxOldSpaceSize: string = '';
} }

View file

@ -231,6 +231,7 @@ describe('GlobalConfig', () => {
port: 5679, port: 5679,
launcherPath: '', launcherPath: '',
launcherRunner: 'javascript', launcherRunner: 'javascript',
maxOldSpaceSize: '',
}, },
sentry: { sentry: {
backendDsn: '', backendDsn: '',

View file

@ -23,7 +23,7 @@ describe('TaskRunnerProcess', () => {
runnerConfig.disabled = false; runnerConfig.disabled = false;
runnerConfig.mode = 'internal_childprocess'; runnerConfig.mode = 'internal_childprocess';
const authService = mock<TaskRunnerAuthService>(); const authService = mock<TaskRunnerAuthService>();
const taskRunnerProcess = new TaskRunnerProcess(runnerConfig, authService); let taskRunnerProcess = new TaskRunnerProcess(runnerConfig, authService);
afterEach(async () => { afterEach(async () => {
spawnMock.mockClear(); spawnMock.mockClear();
@ -40,10 +40,15 @@ describe('TaskRunnerProcess', () => {
}); });
describe('start', () => { describe('start', () => {
it('should propagate NODE_FUNCTION_ALLOW_BUILTIN and NODE_FUNCTION_ALLOW_EXTERNAL from env', async () => { beforeEach(() => {
taskRunnerProcess = new TaskRunnerProcess(runnerConfig, authService);
});
test.each(['PATH', 'NODE_FUNCTION_ALLOW_BUILTIN', 'NODE_FUNCTION_ALLOW_EXTERNAL'])(
'should propagate %s from env as is',
async (envVar) => {
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken'); jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
process.env.NODE_FUNCTION_ALLOW_BUILTIN = '*'; process.env[envVar] = 'custom value';
process.env.NODE_FUNCTION_ALLOW_EXTERNAL = '*';
await taskRunnerProcess.start(); await taskRunnerProcess.start();
@ -51,10 +56,36 @@ describe('TaskRunnerProcess', () => {
const options = spawnMock.mock.calls[0][2] as SpawnOptions; const options = spawnMock.mock.calls[0][2] as SpawnOptions;
expect(options.env).toEqual( expect(options.env).toEqual(
expect.objectContaining({ expect.objectContaining({
NODE_FUNCTION_ALLOW_BUILTIN: '*', [envVar]: 'custom value',
NODE_FUNCTION_ALLOW_EXTERNAL: '*', }),
);
},
);
it('should pass NODE_OPTIONS env if maxOldSpaceSize is configured', async () => {
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
runnerConfig.maxOldSpaceSize = '1024';
await taskRunnerProcess.start();
// @ts-expect-error The type is not correct
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
expect(options.env).toEqual(
expect.objectContaining({
NODE_OPTIONS: '--max-old-space-size=1024',
}), }),
); );
}); });
it('should not pass NODE_OPTIONS env if maxOldSpaceSize is not configured', async () => {
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
runnerConfig.maxOldSpaceSize = '';
await taskRunnerProcess.start();
// @ts-expect-error The type is not correct
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
expect(options.env).not.toHaveProperty('NODE_OPTIONS');
});
}); });
}); });

View file

@ -38,6 +38,12 @@ export class TaskRunnerProcess {
private isShuttingDown = false; private isShuttingDown = false;
private readonly passthroughEnvVars = [
'PATH',
'NODE_FUNCTION_ALLOW_BUILTIN',
'NODE_FUNCTION_ALLOW_EXTERNAL',
] as const;
constructor( constructor(
private readonly runnerConfig: TaskRunnersConfig, private readonly runnerConfig: TaskRunnersConfig,
private readonly authService: TaskRunnerAuthService, private readonly authService: TaskRunnerAuthService,
@ -68,26 +74,14 @@ export class TaskRunnerProcess {
const startScript = require.resolve('@n8n/task-runner'); const startScript = require.resolve('@n8n/task-runner');
return spawn('node', [startScript], { return spawn('node', [startScript], {
env: { env: this.getProcessEnvVars(grantToken, n8nUri),
PATH: process.env.PATH,
N8N_RUNNERS_GRANT_TOKEN: grantToken,
N8N_RUNNERS_N8N_URI: n8nUri,
N8N_RUNNERS_MAX_PAYLOAD: this.runnerConfig.maxPayload.toString(),
NODE_FUNCTION_ALLOW_BUILTIN: process.env.NODE_FUNCTION_ALLOW_BUILTIN,
NODE_FUNCTION_ALLOW_EXTERNAL: process.env.NODE_FUNCTION_ALLOW_EXTERNAL,
},
}); });
} }
startLauncher(grantToken: string, n8nUri: string) { startLauncher(grantToken: string, n8nUri: string) {
return spawn(this.runnerConfig.launcherPath, ['launch', this.runnerConfig.launcherRunner], { return spawn(this.runnerConfig.launcherPath, ['launch', this.runnerConfig.launcherRunner], {
env: { env: {
PATH: process.env.PATH, ...this.getProcessEnvVars(grantToken, n8nUri),
N8N_RUNNERS_GRANT_TOKEN: grantToken,
N8N_RUNNERS_N8N_URI: n8nUri,
N8N_RUNNERS_MAX_PAYLOAD: this.runnerConfig.maxPayload.toString(),
NODE_FUNCTION_ALLOW_BUILTIN: process.env.NODE_FUNCTION_ALLOW_BUILTIN,
NODE_FUNCTION_ALLOW_EXTERNAL: process.env.NODE_FUNCTION_ALLOW_EXTERNAL,
// For debug logging if enabled // For debug logging if enabled
RUST_LOG: process.env.RUST_LOG, RUST_LOG: process.env.RUST_LOG,
}, },
@ -155,4 +149,29 @@ export class TaskRunnerProcess {
setImmediate(async () => await this.start()); setImmediate(async () => await this.start());
} }
} }
private getProcessEnvVars(grantToken: string, n8nUri: string) {
const envVars: Record<string, string> = {
N8N_RUNNERS_GRANT_TOKEN: grantToken,
N8N_RUNNERS_N8N_URI: n8nUri,
N8N_RUNNERS_MAX_PAYLOAD: this.runnerConfig.maxPayload.toString(),
...this.getPassthroughEnvVars(),
};
if (this.runnerConfig.maxOldSpaceSize) {
envVars.NODE_OPTIONS = `--max-old-space-size=${this.runnerConfig.maxOldSpaceSize}`;
}
return envVars;
}
private getPassthroughEnvVars() {
return this.passthroughEnvVars.reduce<Record<string, string>>((env, key) => {
if (process.env[key]) {
env[key] = process.env[key];
}
return env;
}, {});
}
} }