Skip to content

Conversation

Asaf-Federman
Copy link
Contributor

These commits add support for specifying --max-old-space-size as a percentage of system memory, in addition to the existing MB format.

PR-URL: #59082
PR-URL: #59460

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. v22.x Issues that can be reproduced on v22.x or PRs targeting the v22.x-staging branch. labels Aug 26, 2025
Asaf-Federman and others added 2 commits September 4, 2025 12:59
This commit adds support for specifying --max-old-space-size as a
percentage of system memory, in addition to the existing MB format.
A new HandleMaxOldSpaceSizePercentage method parses percentage values,
validates that they are within the 0-100% range, and provides clear
error messages for invalid input. The heap size is now calculated
based on available system memory when a percentage is used.

Test coverage has been added for both valid and invalid cases.
Documentation and the JSON schema for CLI options have been updated
with examples for both formats.

Refs: nodejs#57447
PR-URL: nodejs#59082
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: theanarkh <theratliter@gmail.com>
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Add validation to ensure that --max-old-space-size-percentage cannot
be used when available memory cannot be calculated, preventing
undefined behavior when memory detection fails.

Also enhance test-process-constrained-memory.js to support testing
in constrained environments where memory calculation may fail.

PR-URL: nodejs#59460
Reviewed-By: theanarkh <theratliter@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
@Asaf-Federman Asaf-Federman force-pushed the backport-max-old-space-size-percentage-to-v22.x branch from 1520d5e to dac7895 Compare September 4, 2025 09:59
@Asaf-Federman
Copy link
Contributor Author

Asaf-Federman commented Sep 4, 2025

cc @aduh95

@nodejs-github-bot
Copy link
Collaborator

@richardlau
Copy link
Member

CI: https://ci.nodejs.org/job/node-test-pull-request/69270/

While some of the test failures are flakes, the test added in this backport PR is failing on 32-bit Linux on ARM and 32-bit Windows.
e.g.

18:32:17 not ok 2205 parallel/test-max-old-space-size-percentage
18:32:17   ---
18:32:17   duration_ms: 806.95900
18:32:17   severity: fail
18:32:17   exitcode: 1
18:32:17   stack: |-
18:32:17     node:internal/assert/utils:273
18:32:17         throw err;
18:32:17         ^
18:32:17     
18:32:17     AssertionError [ERR_ASSERTION]: 50% heap size should be roughly half of 100% (got 1.06, expected ~0.5)
18:32:17         at Object.<anonymous> (/home/iojs/build/workspace/node-test-commit-arm/test/parallel/test-max-old-space-size-percentage.js:101:1)
18:32:17         at Module._compile (node:internal/modules/cjs/loader:1706:14)
18:32:17         at Object..js (node:internal/modules/cjs/loader:1839:10)
18:32:17         at Module.load (node:internal/modules/cjs/loader:1441:32)
18:32:17         at Function._load (node:internal/modules/cjs/loader:1263:12)
18:32:17         at TracingChannel.traceSync (node:diagnostics_channel:322:14)
18:32:17         at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
18:32:17         at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
18:32:17         at node:internal/main/run_main_module:36:49 {
18:32:17       generatedMessage: false,
18:32:17       code: 'ERR_ASSERTION',
18:32:17       actual: false,
18:32:17       expected: true,
18:32:17       operator: '==',
18:32:17       diff: 'simple'
18:32:17     }
18:32:17     
18:32:17     Node.js v22.19.1-pre
18:32:17   ...
18:46:07 not ok 2053 parallel/test-max-old-space-size-percentage
18:46:07   ---
18:46:07   duration_ms: 2813.46400
18:46:07   severity: fail
18:46:07   exitcode: 1
18:46:07   stack: |-
18:46:07     node:internal/assert/utils:273
18:46:07         throw err;
18:46:07         ^
18:46:07     
18:46:07     AssertionError [ERR_ASSERTION]: 50% heap size should be roughly half of 100% (got 1.00, expected ~0.5)
18:46:07         at Object.<anonymous> (/home/iojs/build/workspace/node-test-binary-armv7l/test/parallel/test-max-old-space-size-percentage.js:101:1)
18:46:07         at Module._compile (node:internal/modules/cjs/loader:1706:14)
18:46:07         at Module._extensions..js (node:internal/modules/cjs/loader:1839:10)
18:46:07         at Module.load (node:internal/modules/cjs/loader:1441:32)
18:46:07         at Module._load (node:internal/modules/cjs/loader:1263:12)
18:46:07         at TracingChannel.traceSync (node:diagnostics_channel:322:14)
18:46:07         at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
18:46:07         at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
18:46:07         at node:internal/main/run_main_module:36:49 {
18:46:07       generatedMessage: false,
18:46:07       code: 'ERR_ASSERTION',
18:46:07       actual: false,
18:46:07       expected: true,
18:46:07       operator: '==',
18:46:07       diff: 'simple'
18:46:07     }
18:46:07     
18:46:07     Node.js v22.19.1-pre
18:46:07   ...
18:06:37 not ok 626 parallel/test-max-old-space-size-percentage
18:06:37   ---
18:06:37   duration_ms: 3154.98900
18:06:37   severity: fail
18:06:37   exitcode: 1
18:06:37   stack: |-
18:06:37     node:internal/assert/utils:273
18:06:37         throw err;
18:06:37         ^
18:06:37     
18:06:37     AssertionError [ERR_ASSERTION]: 50% heap size should be roughly half of 100% (got 1.11, expected ~0.5)
18:06:37         at Object.<anonymous> (C:\workspace\node-test-binary-windows-js-suites\node\test\parallel\test-max-old-space-size-percentage.js:101:1)
18:06:37         at Module._compile (node:internal/modules/cjs/loader:1706:14)
18:06:37         at Object..js (node:internal/modules/cjs/loader:1839:10)
18:06:37         at Module.load (node:internal/modules/cjs/loader:1441:32)
18:06:37         at Function._load (node:internal/modules/cjs/loader:1263:12)
18:06:37         at TracingChannel.traceSync (node:diagnostics_channel:322:14)
18:06:37         at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
18:06:37         at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
18:06:37         at node:internal/main/run_main_module:36:49 {
18:06:37       generatedMessage: false,
18:06:37       code: 'ERR_ASSERTION',
18:06:37       actual: false,
18:06:37       expected: true,
18:06:37       operator: '==',
18:06:37       diff: 'simple'
18:06:37     }
18:06:37     
18:06:37     Node.js v22.19.1-pre
18:06:37   ...

@Asaf-Federman
Copy link
Contributor Author

CI: https://ci.nodejs.org/job/node-test-pull-request/69270/

While some of the test failures are flakes, the test added in this backport PR is failing on 32-bit Linux on ARM and 32-bit Windows. e.g.

18:32:17 not ok 2205 parallel/test-max-old-space-size-percentage
18:32:17   ---
18:32:17   duration_ms: 806.95900
18:32:17   severity: fail
18:32:17   exitcode: 1
18:32:17   stack: |-
18:32:17     node:internal/assert/utils:273
18:32:17         throw err;
18:32:17         ^
18:32:17     
18:32:17     AssertionError [ERR_ASSERTION]: 50% heap size should be roughly half of 100% (got 1.06, expected ~0.5)
18:32:17         at Object.<anonymous> (/home/iojs/build/workspace/node-test-commit-arm/test/parallel/test-max-old-space-size-percentage.js:101:1)
18:32:17         at Module._compile (node:internal/modules/cjs/loader:1706:14)
18:32:17         at Object..js (node:internal/modules/cjs/loader:1839:10)
18:32:17         at Module.load (node:internal/modules/cjs/loader:1441:32)
18:32:17         at Function._load (node:internal/modules/cjs/loader:1263:12)
18:32:17         at TracingChannel.traceSync (node:diagnostics_channel:322:14)
18:32:17         at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
18:32:17         at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
18:32:17         at node:internal/main/run_main_module:36:49 {
18:32:17       generatedMessage: false,
18:32:17       code: 'ERR_ASSERTION',
18:32:17       actual: false,
18:32:17       expected: true,
18:32:17       operator: '==',
18:32:17       diff: 'simple'
18:32:17     }
18:32:17     
18:32:17     Node.js v22.19.1-pre
18:32:17   ...
18:46:07 not ok 2053 parallel/test-max-old-space-size-percentage
18:46:07   ---
18:46:07   duration_ms: 2813.46400
18:46:07   severity: fail
18:46:07   exitcode: 1
18:46:07   stack: |-
18:46:07     node:internal/assert/utils:273
18:46:07         throw err;
18:46:07         ^
18:46:07     
18:46:07     AssertionError [ERR_ASSERTION]: 50% heap size should be roughly half of 100% (got 1.00, expected ~0.5)
18:46:07         at Object.<anonymous> (/home/iojs/build/workspace/node-test-binary-armv7l/test/parallel/test-max-old-space-size-percentage.js:101:1)
18:46:07         at Module._compile (node:internal/modules/cjs/loader:1706:14)
18:46:07         at Module._extensions..js (node:internal/modules/cjs/loader:1839:10)
18:46:07         at Module.load (node:internal/modules/cjs/loader:1441:32)
18:46:07         at Module._load (node:internal/modules/cjs/loader:1263:12)
18:46:07         at TracingChannel.traceSync (node:diagnostics_channel:322:14)
18:46:07         at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
18:46:07         at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
18:46:07         at node:internal/main/run_main_module:36:49 {
18:46:07       generatedMessage: false,
18:46:07       code: 'ERR_ASSERTION',
18:46:07       actual: false,
18:46:07       expected: true,
18:46:07       operator: '==',
18:46:07       diff: 'simple'
18:46:07     }
18:46:07     
18:46:07     Node.js v22.19.1-pre
18:46:07   ...
18:06:37 not ok 626 parallel/test-max-old-space-size-percentage
18:06:37   ---
18:06:37   duration_ms: 3154.98900
18:06:37   severity: fail
18:06:37   exitcode: 1
18:06:37   stack: |-
18:06:37     node:internal/assert/utils:273
18:06:37         throw err;
18:06:37         ^
18:06:37     
18:06:37     AssertionError [ERR_ASSERTION]: 50% heap size should be roughly half of 100% (got 1.11, expected ~0.5)
18:06:37         at Object.<anonymous> (C:\workspace\node-test-binary-windows-js-suites\node\test\parallel\test-max-old-space-size-percentage.js:101:1)
18:06:37         at Module._compile (node:internal/modules/cjs/loader:1706:14)
18:06:37         at Object..js (node:internal/modules/cjs/loader:1839:10)
18:06:37         at Module.load (node:internal/modules/cjs/loader:1441:32)
18:06:37         at Function._load (node:internal/modules/cjs/loader:1263:12)
18:06:37         at TracingChannel.traceSync (node:diagnostics_channel:322:14)
18:06:37         at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
18:06:37         at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
18:06:37         at node:internal/main/run_main_module:36:49 {
18:06:37       generatedMessage: false,
18:06:37       code: 'ERR_ASSERTION',
18:06:37       actual: false,
18:06:37       expected: true,
18:06:37       operator: '==',
18:06:37       diff: 'simple'
18:06:37     }
18:06:37     
18:06:37     Node.js v22.19.1-pre
18:06:37   ...

Since this flag's behavior depends on V8's complex and platform-specific heap sizing logic, creating a test that perfectly models these heuristics is likely to be brittle. Therefore, I think the most pragmatic solution is to skip these assertions.

What are your thoughts?

@richardlau
Copy link
Member

Since this flag's behavior depends on V8's complex and platform-specific heap sizing logic, creating a test that perfectly models these heuristics is likely to be brittle. Therefore, I think the most pragmatic solution is to skip these assertions.

What are your thoughts?

My concern is that we don't run tests on 32-bit platforms on main/Node.js 24.x, so this is the first time we've tried this feature on those platforms. And while allowances might be made for the comparison in these tests to not be exactly half, the tests would seem to indicate that a 50% value is leading in some cases to a larger heap being allocated than 100% in some cases which indicates the opposite of the desired effect?

@Asaf-Federman
Copy link
Contributor Author

Asaf-Federman commented Sep 23, 2025

Hi @richardlau,

Following up on the discussion, I've run some tests on the behavior of --max-old-space-size on a 32-bit ARM (armv7l) system. The results show some significant inconsistencies that likely explain why --max-old-space-size-percentage would also be unreliable on this architecture.

Test Environment

  • Node.js Version: v22.19.0
  • Architecture: armv7l (32-bit ARM)
  • Script Used:
    // heap_check.js
    const v8 = require('v8');
    
    // Get heap statistics from the V8 engine.
    const stats = v8.getHeapStatistics();
    
    // A simple function to format bytes into megabytes.
    const toMB = (bytes) => (bytes / 1024 / 1024).toFixed(2);
    
    console.log('V8 Heap Statistics:');
    console.log(`- Used Heap Size: ${toMB(stats.used_heap_size)} MB`);
    console.log(`- Total Heap Available: ${toMB(stats.total_available_size)} MB`);
    console.log(`- Heap Size Limit: ${toMB(stats.heap_size_limit)} MB`);
    
    // The --max-old-space-size flag primarily affects this limit.
    // The total limit is slightly higher to account for other memory spaces.

Results

Here is a summary of the requested heap size versus the actual limit reported by V8.

  root@e0332c781f29:/# node --version
  v22.19.0
  root@e0332c781f29:/# uname -m
  armv7l
  root@e0332c781f29:/# node --max-old-space-size=2000 heap_check.js
  V8 Heap Statistics:
  - Used Heap Size: 2.00 MB
  - Total Heap Available: 2009.87 MB
  - Heap Size Limit: 2012.00 MB
  root@e0332c781f29:/# node --max-old-space-size=5000 heap_check.js
  V8 Heap Statistics:
  - Used Heap Size: 1.99 MB
  - Total Heap Available: 913.87 MB
  - Heap Size Limit: 916.00 MB
  root@e0332c781f29:/# node --max-old-space-size=13000 heap_check.js
  V8 Heap Statistics:
  - Used Heap Size: 1.99 MB
  - Total Heap Available: 721.87 MB
  - Heap Size Limit: 724.00 MB
  root@e0332c781f29:/# node --max-old-space-size=14000 heap_check.js
  V8 Heap Statistics:
  - Used Heap Size: 1.99 MB
  - Total Heap Available: 1721.87 MB
  - Heap Size Limit: 1724.00 MB

Analysis & Conclusion

The data shows that V8's memory allocation on 32-bit ARM is not just capped, but behaves erratically when given large values. The most telling result is that requesting 13000 MB results in a smaller heap limit than requesting 5000 MB.

This underlying instability confirms that the heap size is not handled predictably on this architecture. It strongly suggests that the --max-old-space-size-percentage flag would be subject to the same unreliable behavior, as it's ultimately bound by this same allocation logic.

I strongly believe we should skip those tests for 32bit architecture.
Maybe it's worth adding the lack of support for 32bit architecture to the cli documentation as well.

WDYT?

@richardlau richardlau added the request-ci Add this label to start a Jenkins CI on a PR. label Sep 24, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Sep 24, 2025
@nodejs-github-bot
Copy link
Collaborator

@Asaf-Federman
Copy link
Contributor Author

The tests seem to fail due to flakiness

@nodejs-github-bot
Copy link
Collaborator

@Asaf-Federman
Copy link
Contributor Author

Hi @richardlau,
the CI is 🆗, can we please move this effort forward?

Thanks in advance :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. v22.x Issues that can be reproduced on v22.x or PRs targeting the v22.x-staging branch.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants