Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stack size exceeded on M1/arm64 but not on x86 #41163

Open
ggascoigne opened this issue Dec 14, 2021 · 36 comments
Open

stack size exceeded on M1/arm64 but not on x86 #41163

ggascoigne opened this issue Dec 14, 2021 · 36 comments
Labels
arm Issues and PRs related to the ARM platform. v8 engine Issues and PRs related to the V8 dependency.

Comments

@ggascoigne
Copy link

Version

v16.13.1

Platform

Darwin USAGPM.local 21.2.0 Darwin Kernel Version 21.2.0: Sun Nov 28 20:28:41 PST 2021; root:xnu-8019.61.5~1/RELEASE_ARM64_T6000 arm64

Subsystem

No response

What steps will reproduce the bug?

I have a project with about 1000 typescript files (it's private so I can't point you at it, sorry). It's a react project that uses create-react app.

The arm version of v16 gives:

Failed to compile.

undefined
fork-ts-checker-webpack-plugin error in undefined(undefined,undefined):
Maximum call stack size exceeded  TSINTERNAL

The x86 version works fine. I've tried every arm version of v16 that I can find, all have the same issue, every x86 version works with no problem. Running yarn tsc explicitly works fine with both the arm and the x86 version.

How often does it reproduce? Is there a required condition?

With the affected project it happens 100% of the time, with other projects I can't reproduce this.

What is the expected behavior?

x86 and arm behavior should be identical.

What do you see instead?

No response

Additional information

No response

@marsonya marsonya added the arm Issues and PRs related to the ARM platform. label Dec 14, 2021
@aduh95 aduh95 added the v8 engine Issues and PRs related to the V8 dependency. label Dec 14, 2021
@ggascoigne
Copy link
Author

BTW I realise that without a project to test this on this will be difficult to do anything with, I'm more than happy to jump through hoops to try and get you any further information that can help pin this down if it comes to that.

@shauninman
Copy link

Building the open source Ace (Ajax.org Cloud9 Editor) also exhibits the problem. Steps to reproduce on an arm64 Mac:

git clone https://github.com/ajaxorg/ace
cd ace
npm install
make clean && node Makefile.dryice.js normal --m --nc

@bnoordhuis
Copy link
Member

I can't test locally but I know the cause: V8 on arm64 assumes there's at most 864 kb of stack space (984 kb on other architectures.)

Check what ulimit -s prints and start node with node --stack_size=<kb> app.js. Important: if you set it too high, you risk crashes.

@targos
Copy link
Member

targos commented Nov 8, 2022

@bnoordhuis can we close this issue or there's something Node.js can do?

@bnoordhuis
Copy link
Member

Node should probably check getrlimit(RLIMIT_STACK) at start-up.

@mirabilos
Copy link

864 kb (kilobits)? This is highly unusual, stack is normally 4096 or 8192 KiB, in most settings.

I do have a public reproducer for you, first reported in Debian. I can reproduce this on the Debian/arm64 porterbox:

Linux amdahl 5.10.0-21-arm64 #1 SMP Debian 5.10.162-1 (2023-01-21) aarch64 GNU/Linux
processor       : 0                                                                                              
BogoMIPS        : 100.00                                                                                         
Features        : fp asimd evtstrm cpuid                                                                         
CPU implementer : 0x50                                                                                           
CPU architecture: 8                                                                                              
CPU variant     : 0x0                                                                                            
CPU part        : 0x000                                                                                          
CPU revision    : 1                                                                                              

Here’s it using the latest nodejs release (though I’m still using Babel etc. from Debian because that’s quicker for me right now than using npm):

(sid_arm64-dchroot)tg@amdahl:~$ wget https://github.com/danvk/dygraphs/archive/refs/tags/v2.2.0.tar.gz           
(sid_arm64-dchroot)tg@amdahl:~$ tar xaf v2.2.0.tar.gz 
(sid_arm64-dchroot)tg@amdahl:~$ cd dygraphs-2.2.0
(sid_arm64-dchroot)tg@amdahl:~/dygraphs-2.2.0$ env NODE_PATH=/usr/share/node_modules ../node-v19.6.0-linux-arm64/bin/node /usr/bin/babeljs --config-file $PWD/babel.config.json --compact false --source-maps inline -d tests5 auto_tests                                                                                                          
RangeError: Maximum call stack size exceeded                                                                     
    at isBlockStatement (/usr/share/nodejs/@babel/types/lib/validators/generated/index.js:381:26)                
    at isScope (/usr/share/nodejs/@babel/types/lib/validators/isScope.js:9:39)                                   
    at NodePath.isScope (/usr/share/nodejs/@babel/traverse/lib/path/lib/virtual-types-validator.js:107:10)       
    at NodePath.getScope (/usr/share/nodejs/@babel/traverse/lib/path/index.js:85:17)                             
    at NodePath.setScope (/usr/share/nodejs/@babel/traverse/lib/path/context.js:115:21)                          
    at NodePath.setContext (/usr/share/nodejs/@babel/traverse/lib/path/context.js:128:8)                         
    at NodePath.pushContext (/usr/share/nodejs/@babel/traverse/lib/path/context.js:183:8)                        
    at TraversalContext.visitQueue (/usr/share/nodejs/@babel/traverse/lib/context.js:78:14)                      
    at TraversalContext.visitSingle (/usr/share/nodejs/@babel/traverse/lib/context.js:65:19)                     
    at TraversalContext.visit (/usr/share/nodejs/@babel/traverse/lib/context.js:109:19)                          
(sid_arm64-dchroot)tg@amdahl:~/dygraphs-2.2.0$ env NODE_PATH=/usr/share/node_modules ../node-v19.6.0-linux-arm64/bin/node --stack-size="$(ulimit -s)" /usr/bin/babeljs --config-file $PWD/babel.config.json --compact false --source-maps inline -d tests5 auto_tests
Successfully compiled 59 files with Babel (9209ms).

An npm i after the cd should do the trick for you as well.

This probably affects many; I’ve seen reports of people trying to use some äpps on Android on arm64 running into this bug. I found it during Debian reproducible-builds testing. Others run into this because they use arm64 machines for development.

@mirabilos
Copy link

mirabilos commented Feb 15, 2023 via email

@mirabilos
Copy link

ooooh and I think I get why “risk crashes” is mentioned here: setting --stack_size="$(ulimit -s)" omits the subtraction of the per-arch CFrustFrust overhead, so that’s not a consideration for the proposed way to fix this

@bnoordhuis
Copy link
Member

“if you set it too high, you risk crashes”

That can’t be right.

--stack_size=... doesn't set the stack size, it just tells V8 to assume it's this big. Perennial source of confusion.

@mirabilos
Copy link

mirabilos commented Feb 15, 2023 via email

@mirabilos
Copy link

mirabilos commented Feb 27, 2023 via email

@mirabilos
Copy link

mirabilos commented Feb 27, 2023 via email

@jayaddison
Copy link
Contributor

That comment was written and sent by me. I didn't realize that the GitHub reply-to email address is user-specific and that messages sent to it will appear with them as the author.

@jayaddison
Copy link
Contributor

Patches are welcomed downstream by the Debian maintainer (see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1030284), and there is a plan to re-build and re-test affected Debian packages with any selected patch(es) before the hard freeze for the upcoming bookworm release (that freeze is likely to occur on or around 2023-03-12).

@jayaddison
Copy link
Contributor

I've offered an untested and not-yet-accepted patch -- a mitigation, not a fix -- to Debian, that removes the preprocessor-defined smaller stack size for ARM-based architectures.

That patch is also available as pull request #46896 for this repository (currently opened against the main branch of Node).

The patch maintains the existing static-constant definition of stack size limits (that is to say: it does not introduce dynamic rlimit-based querying), since I'm not particularly familiar with the details of C, V8, NodeJS, or stack limits.

The following bugs are referenced from comments in the section of code that the patch removes:

  • https://crbug.com/405338 (I haven't read the contents of this bug yet)
  • https://crbug.com/v8/10575 (this appears to affect webviews, particularly under single-process conditions, and so I don't think that it's relevant to NodeJS, but I could be mistaken)

@kapouer
Copy link
Contributor

kapouer commented Mar 2, 2023

@jayaddison

(sid_arm64-dchroot)kapouer@amdahl:~/test/dygraphs-2.2.0$ NODE_PATH=/usr/share/node_modules ../../nodejs-18.13.0+dfsg1/node /usr/bin/babeljs --config-file $PWD/babel.config.json --compact false --source-maps inline -d tests5 auto_tests
Successfully compiled 59 files with Babel (10442ms).

your patch works, thank you.

@jayaddison
Copy link
Contributor

@kapouer you're welcome, thanks for testing it

@jayaddison
Copy link
Contributor

https://crbug.com/405338 (I haven't read the contents of this bug yet)

A request for help: has anyone else been able to read the contents of this bug and to determine whether they remain relevant for ARM and Node? (I don't seem to be able to read it)

@bnoordhuis
Copy link
Member

FWIW, I have (mildly) elevated privileges but I can't read it either.

@jayaddison
Copy link
Contributor

Thanks for checking @bnoordhuis.

Although information from that bug might be useful, it turns out that there is some relevant public changeset and code review information available.

https://codereview.chromium.org/555943003 - a V8 changeset for ARM - was initially proposed as a change to reduce the size of some allocated buffers.

It was closed with a preference for the easier and safer https://codereview.chromium.org/583163002/ approach instead -- introducing the reduced stack size on ARM.

Increasing the stack size again (as #46896 proposes) may fix the issue described in this thread, but would also be likely to re-introduce whatever bug those two changesets were aiming to resolve.

I'll try to formulate a suitable question to post to a V8 contributors mailing list with a summary of the situation.

@mirabilos
Copy link

mirabilos commented Mar 6, 2023 via email

@jayaddison
Copy link
Contributor

jayaddison commented Mar 7, 2023

I'm having difficulty posting to the v8-dev mailing list at the moment; if anyone's able to post a message there on my behalf (or suggest changes to this one), here's what I'd like to send:

Subject: Question: stack size on ARM systems

Hi folks,

Debian bug #1030284[1] and the related NodeJS GitHub issue #41163[2]
report ARM-specific RangeError exceptions from the vendored V8 library
in NodeJS.  The bug(s) are reproducible with v18.13 of NodeJS.

The cause of the difference-in-behaviour appears to be that V8 sets[3]
a lower stack size for ARM: 864K as compared to 984K.

Would it be safe to increase the stack size on ARM to 984K to restore
consistency with most other architectures?

(I've offered a patch to make that change, and others have confirmed
that it allows a provided repro case to pass; I'm worried about any
potential unsafe side-effects of the change, though)

Thanks,
James

[1] - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1030284

[2] - https://github.com/nodejs/node/issues/41163

[3] - https://github.com/nodejs/node/blob/2bb4b59fa5529569ad38d3bf7d36666c926d8e47/deps/v8/src/common/globals.h#L74-L86

(an earlier draft included references the codereview change history, bug reports, and so on -- after a while, though, it seemed like a more concise message might be more respectful of people's time)

Edit: add missing third footnote

@bnoordhuis
Copy link
Member

v8-dev is moderated. If you're a first-time poster, it can take up to a day for your post to get accepted. Its moderators predominantly follow CET working hours.

@jayaddison
Copy link
Contributor

In short summary from a helpful response on the v8-dev mailing list: no, it isn't currently considered safe to restore the 984KB stack size on ARM64 (it may be OK on 32-bit ARM, but not 64-bit). Also: if the RangeError condition is caused by recursive code, then it could be worthwhile to refactor that code into a non-recursive implementation.

@mirabilos
Copy link

mirabilos commented Mar 11, 2023 via email

@jayaddison
Copy link
Contributor

Thanks @mirabilos - I'm not likely to look at this issue again for at least a week; please see (and/or join, if you like) the v8-dev mailing list for the recent context.

@bnoordhuis
Copy link
Member

The suggestion made in https://groups.google.com/g/v8-dev/c/7ZI3vxtabcU/m/c9qvHkOBBAAJ was to float a patch in node but I don't think we want to be in the business of second-guessing V8. As a general rule, we don't float patches that haven't been accepted upstream.

If downstream Debian wants to float a patch, that's fine, of course.

@bnoordhuis
Copy link
Member

@laverdet is isolated-vm affected when node sets the --stack_size=... flag to some at-runtime-determined value? Effectively V8 will assume a 2-4 MB system stack instead of the 800-900k it defaults to today.

Node's own worker_threads module uses 4 MB thread stacks but I couldn't quite divine what isolated-vm does.

@laverdet
Copy link
Contributor

isolated-vm instantiates threads with the built-in C++11 std::thread constructor, so pthreads on macOS. I get the stack base using platform-specific invocations: https://github.com/laverdet/isolated-vm/blob/main/src/isolate/environment.cc#L27-L48 and then feed that to v8 minus 24kb of padding [experimentally derived] https://github.com/laverdet/isolated-vm/blob/main/src/isolate/environment.cc#L239-L247 [the comment here says 512kb but I just tested again and they increased it to 524kb at some point]. The implementation for GetStackBase in isolated-vm doesn't do anything in Windows because the stack sizes on that platform are more than enough to support v8's defaults.

@bnoordhuis
Copy link
Member

Ah okay, so isolated-vm uses v8::Isolate::SetStackLimit() and default thread stacks. Understood, thanks.

That means runtime tuning of --stack_size=... has to be conservative, so < 2 MB on linux and macos and $deity knows how little with musl libc.

Honestly, tweaking the flag seems fraught with peril. I'm kind of loath to make the change.

@jayaddison
Copy link
Contributor

Looping back some discussion from downstream in Debian - particularly some comments by @mirabilos around the fact that V8 accepts stack-size options on the command-line:

Would it make sense to make a call to getrlimit(RLIMIT_STACK) from somewhere around (probably just after) this code block:

node/src/node.cc

Lines 781 to 786 in 2bb4b59

#if defined(NODE_V8_OPTIONS)
// Should come before the call to V8::SetFlagsFromCommandLine()
// so the user can disable a flag --foo at run-time by passing
// --no_foo from the command line.
V8::SetFlagsFromString(NODE_V8_OPTIONS, sizeof(NODE_V8_OPTIONS) - 1);
#endif

... and then to set the V8 --stack-size option when a non-infinite limit is successfully found?

(I have written a sketchy draft of a patch to do that, but not yet built or tested it yet, and would welcome any feedback on whether it's a sensible approach in the first place)

@jayaddison
Copy link
Contributor

I've had a change of heart and do not wish to participate further with this bug, and will unsubscribe. Please feel free to make use of any of the patches I've suggested downstream in Debian, but do not merge them as-is (apply corrections first). Thank you.

@bnoordhuis
Copy link
Member

Would it make sense to make a call to getrlimit(RLIMIT_STACK)

#41163 (comment) - i.e., that won't work because of threads. RLIMIT_STACK only applies to the main thread.

@mirabilos
Copy link

mirabilos commented May 14, 2023 via email

@mirabilos
Copy link

mirabilos commented May 14, 2023 via email

@bnoordhuis
Copy link
Member

So, there are a couple of things node can do here.

For threads that node manages itself (including the main thread), it knows approximately how big the stack is and it can inform V8 about that through the v8::ResourceConstraints::set_stack_limit() API.

For threads that are not under node's control, we either do nothing or do something very conservative. I mean, you can try to probe the stack but that turns into a combinatorial explosion of platform- and architecture-specific code quickly.

Apropos the --stack_size switch, that's a per process setting, not per thread. I'd just leave it untouched. If people want to override the defaults, they presumably / hopefully know what they're doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arm Issues and PRs related to the ARM platform. v8 engine Issues and PRs related to the V8 dependency.
Projects
None yet
Development

No branches or pull requests

10 participants