RuntimeError with WebAssembly in @react-pdf/yoga on Node.js Server #1572

Open
opened 2024-02-02 04:15:08 -08:00 by gino8080 · 9 comments
gino8080 commented 2024-02-02 04:15:08 -08:00 (Migrated from github.com)

Hello,

I've encountered a critical issue while using @react-pdf/renderer in a Node.js Express server environment. The server crashes with a RuntimeError related to WebAssembly memory allocation when attempting to generate PDFs. The error message is as follows:

RuntimeError: Aborted(RangeError: WebAssembly.instantiate(): Out of memory: wasm memory). Build with -sASSERTIONS for more info.
    at w (/usr/app/node_modules/yoga-layout/binaries/wasm-async-node.js:14:73)
    at /usr/app/node_modules/yoga-layout/binaries/wasm-async-node.js:62:60

This error suggests that the WebAssembly module within yoga-layout, which is a dependency of @react-pdf/renderer, is running out of memory.

To Reproduce

  1. Set up a basic Express server that uses @react-pdf/renderer to generate PDFs.
  2. Trigger the PDF generation endpoint with the necessary data.
  3. The server crashes with the above RuntimeError after a few successful PDF generations.
    Expected Behavior:

The PDF generation should complete successfully without causing a server crash or running out of WebAssembly memory.

Actual Behavior:

The server crashes with a RuntimeError indicating that WebAssembly ran out of memory.

Additional Context:

The issue seems to occur after generating multiple PDFs, suggesting a potential memory leak or insufficient memory allocation for WebAssembly in the yoga-layout module.
Reducing the complexity of the PDF layout temporarily mitigates the issue but does not solve it entirely.
I would appreciate any insights or suggestions on how to resolve this issue. If there are any known workarounds or if additional information is needed, please let me know.

Environment:

Node.js version: v16.18.1
Express version: ^4.18.2
@react-pdf/layout": "3.10.3",
@react-pdf/renderer": "3.3.4",
Operating System: docker image

Thank you for your time and assistance.

Hello, I've encountered a critical issue while using @react-pdf/renderer in a Node.js Express server environment. The server crashes with a RuntimeError related to WebAssembly memory allocation when attempting to generate PDFs. The error message is as follows: ```javascript RuntimeError: Aborted(RangeError: WebAssembly.instantiate(): Out of memory: wasm memory). Build with -sASSERTIONS for more info. at w (/usr/app/node_modules/yoga-layout/binaries/wasm-async-node.js:14:73) at /usr/app/node_modules/yoga-layout/binaries/wasm-async-node.js:62:60 ``` This error suggests that the WebAssembly module within yoga-layout, which is a dependency of @react-pdf/renderer, is running out of memory. **To Reproduce** 1. Set up a basic Express server that uses @react-pdf/renderer to generate PDFs. 2. Trigger the PDF generation endpoint with the necessary data. 3. The server crashes with the above RuntimeError after a few successful PDF generations. Expected Behavior: The PDF generation should complete successfully without causing a server crash or running out of WebAssembly memory. **Actual Behavior:** The server crashes with a RuntimeError indicating that WebAssembly ran out of memory. **Additional Context:** The issue seems to occur after generating multiple PDFs, suggesting a potential memory leak or insufficient memory allocation for WebAssembly in the yoga-layout module. Reducing the complexity of the PDF layout temporarily mitigates the issue but does not solve it entirely. I would appreciate any insights or suggestions on how to resolve this issue. If there are any known workarounds or if additional information is needed, please let me know. **Environment:** Node.js version: v16.18.1 Express version: ^4.18.2 @react-pdf/layout": "3.10.3", @react-pdf/renderer": "3.3.4", Operating System: docker image Thank you for your time and assistance.
NickGerleman commented 2024-02-02 12:48:05 -08:00 (Migrated from github.com)

This is going to be hard to diagnose without more information about the usage.

The Yoga wasm binary allows heap growth (58aa090774/javascript/CMakeLists.txt (L37)) and provides methods to free nodes/configs.

Without knowing more, out of memory seems like the consuming library might not be freeing resources, or that there is an irregularly large tree.

This is going to be hard to diagnose without more information about the usage. The Yoga wasm binary allows heap growth (https://github.com/facebook/yoga/blob/58aa0907746204e151eac38f05827322bc4e6f63/javascript/CMakeLists.txt#L37) and provides methods to free nodes/configs. Without knowing more, out of memory seems like the consuming library might not be freeing resources, or that there is an irregularly large tree.
nicoburns commented 2024-02-04 14:37:13 -08:00 (Migrated from github.com)

Not sure if this is relevant, but in my (browser based) benchmarking scripts where I was constructing, measuring and then destructing Yoga trees in a loop, I was hitting a similar crash. It took quite high iteration count (>10000) to hit it, but it was with a script that in theory shouldn't have increased heap size at all. This also occurred with a similar Taffy-based benchmark at similar iteration counts.

It could perhaps be due to heap fragmentation? Or immaturity of WASM implementations? Although I must admit that I haven't investigated this thoroughly enough to rule out user error.

As a workaround, if this is only occurring after repeated PDF generation, then you could try the following steps (in order):

  • Catch the exception
  • Deinitialise the WASM module
  • Reinitialise the WASM module
  • Retry your call into Yoga
Not sure if this is relevant, but in my (browser based) benchmarking scripts where I was constructing, measuring and then destructing Yoga trees in a loop, I was hitting a similar crash. It took quite high iteration count (>10000) to hit it, but it was with a script that in theory shouldn't have increased heap size at all. This also occurred with a similar Taffy-based benchmark at similar iteration counts. It could perhaps be due to heap fragmentation? Or immaturity of WASM implementations? Although I must admit that I haven't investigated this thoroughly enough to rule out user error. As a workaround, if this is only occurring after _repeated_ PDF generation, then you could try the following steps (in order): - Catch the exception - Deinitialise the WASM module - Reinitialise the WASM module - Retry your call into Yoga
NickGerleman commented 2024-03-13 19:54:20 -07:00 (Migrated from github.com)

I took a quick look at this, and other issues running into it: https://github.com/vercel/next.js/issues/51870

I missed that this error happens during WASM environment instantiation. So, this error indicates that the WASM is being loaded into a new environment, and Node doesn't have enough memory to do that.

I am wondering if this scenario is somehow doing this loading more than once, or if maybe multiple processes are doing this, and running out of memory.

I went to double check if we have documented guarantees from Emscripten module that it is only loaded once on load call, but couldn't quickly verify. We are pushing out Yoga 3.0 now, which will cache the instance regardless.

I took a quick look at this, and other issues running into it: https://github.com/vercel/next.js/issues/51870 I missed that this error happens during WASM environment instantiation. So, this error indicates that the WASM is being loaded into a new environment, and Node doesn't have enough memory to do that. I am wondering if this scenario is somehow doing this loading more than once, or if maybe multiple processes are doing this, and running out of memory. I went to double check if we have documented guarantees from Emscripten module that it is only loaded once on load call, but couldn't quickly verify. We are pushing out Yoga 3.0 now, which will cache the instance regardless.
nnaku commented 2025-02-12 13:23:37 -08:00 (Migrated from github.com)

It seem that for node.js yoga-layout leaks memory after 3.1 because downgrading to 3.0.4 does not accumulate memory.
https://github.com/diegomura/react-pdf/issues/3051#issuecomment-2654825847

heapdump points to minifyed yoga-wasm-base64-esm.js but tbh I hit the wall there with my debugging skills 😅

It seem that for node.js yoga-layout leaks memory after 3.1 because downgrading to 3.0.4 does not accumulate memory. https://github.com/diegomura/react-pdf/issues/3051#issuecomment-2654825847 heapdump points to minifyed `yoga-wasm-base64-esm.js` but tbh I hit the wall there with my debugging skills 😅
NickGerleman commented 2025-02-14 12:08:08 -08:00 (Migrated from github.com)

That's really fascinating. There was not much in the way of significant changes to JS layer between these versions.

bbdd1afe59 is the most interesting thing I can find, where we added a new entrypoint.

If we are repeatedly instantiating wasm, I wonder if somehow environment is losing track of cached loaded module or something. Could be interesting to try to revert locally, or bisect (yarn prepack should be enough to create local npm package of any commit).

That's really fascinating. There was not much in the way of significant changes to JS layer between these versions. https://github.com/facebook/yoga/commit/bbdd1afe596b3f78fa5887410513d9a98b944715 is the most interesting thing I can find, where we added a new entrypoint. If we are repeatedly instantiating wasm, I wonder if somehow environment is losing track of cached loaded module or something. Could be interesting to try to revert locally, or bisect (`yarn prepack` should be enough to create local npm package of any commit).
Ledzz commented 2025-06-13 00:10:54 -07:00 (Migrated from github.com)

Hello!
I don't know if it's the same issue, but I'll put it here in case it helps.

I've got a quite complicated setup (react-three/uikit, custom layouting for text) and there's definitely something wrong happening when updating measure function via node.setMeasureFunc. I'm not very familiar with WASM debugging, but as far as I could tell, something wrong with calling the destructor for the old measure function. I get either this error, or something like "null function or invalid function signature" error. I don't know yet how to reproduce it outside of my setup, but it happens not every time and the error sometimes changes, so I think there's something going wrong with the heap.

Sorry for erratic wording, I hope this can help. I think I will continue to investigate it on my side. Hope this helps a bit.

UPD:
Sometimes the error is "memory access out of bounds"

Hello! I don't know if it's the same issue, but I'll put it here in case it helps. I've got a quite complicated setup (react-three/uikit, custom layouting for text) and there's definitely something wrong happening when updating measure function via node.setMeasureFunc. I'm not very familiar with WASM debugging, but as far as I could tell, something wrong with calling the destructor for the old measure function. I get either this error, or something like "null function or invalid function signature" error. I don't know yet how to reproduce it outside of my setup, but it happens not every time and the error sometimes changes, so I think there's something going wrong with the heap. Sorry for erratic wording, I hope this can help. I think I will continue to investigate it on my side. Hope this helps a bit. UPD: Sometimes the error is "memory access out of bounds"
Ledzz commented 2025-06-16 05:44:42 -07:00 (Migrated from github.com)

I've managed to reproduce the bug, please see #1818

I've managed to reproduce the bug, please see #1818
Obi-Dann commented 2025-08-21 05:43:55 -07:00 (Migrated from github.com)

Hello, we are facing this error and quite a significant memory leak when using with @react-pdf/renderer.
We use version 3.2.1. Rolled back to 3.0.4 and there was memory leak.
I was bisected the commits between 3.0.4 and 3.1.0, the memory leak was introduced in this commit

Commenting out the yoga::log fixes the memory leak in 3.2.1
dc2581f229/yoga/node/Node.cpp (L68-L73)

I am not too familiar with the codebase and C++. Perhaps there's something wrong with log in wasm that it captures references from the current context?

I can reproduce the issue when using @react-pdf/renderer and boilerplate from this commit f3ccddf442
The reproduction is somewhat unclean as I added react and @react-pdf/renderer dependencies to the project to showcase the issue. However, it should help someone familiar with the codebase to figure out the root cause.
The commit currently has the log line commented out

Steps:

  • Ensure the log line is commented out
  • Run cd javascript
  • Run yarn
  • Run yarn memory-test
  • Observe memory diff
    console.log
      memory {
        deltaMemory: 1.780517578125,
        memoryBefore: 49.78370666503906,
        memoryAfter: 51.56422424316406
      }
    
  • Uncomment the log line
  • Run yarn memory-test
  • Observe memory diff
      memory {
        deltaMemory: 16.605636596679688,
        memoryBefore: 49.90489196777344,
        memoryAfter: 66.51052856445312
      }
    

Having the log line significantly increases memory allocations that never free up by NodeJS Garbage Collector

Hello, we are facing this error and quite a significant memory leak when using with `@react-pdf/renderer`. We use version 3.2.1. Rolled back to 3.0.4 and there was memory leak. I was bisected the commits between 3.0.4 and 3.1.0, the memory leak was introduced in [this commit](https://github.com/facebook/yoga/commit/fb53cb7443edd286c7d26c0760d5d8967c2809ca) Commenting out the `yoga::log` fixes the memory leak in 3.2.1 https://github.com/facebook/yoga/blob/dc2581f229cb05c7d2af8dee37b2ee0b59fd5326/yoga/node/Node.cpp#L68-L73 I am not too familiar with the codebase and C++. Perhaps there's something wrong with `log` in wasm that it captures references from the current context? I can reproduce the issue when using `@react-pdf/renderer` and boilerplate from this commit https://github.com/Obi-Dann/yoga/commit/f3ccddf442b68023e883ade042946a1618a1a35b The reproduction is somewhat unclean as I added react and @react-pdf/renderer dependencies to the project to showcase the issue. However, it should help someone familiar with the codebase to figure out the root cause. The commit currently has the log line commented out Steps: - Ensure [the log line](https://github.com/facebook/yoga/blob/dc2581f229cb05c7d2af8dee37b2ee0b59fd5326/yoga/node/Node.cpp#L68-L73) is commented out - Run `cd javascript` - Run `yarn` - Run `yarn memory-test` - Observe memory diff ``` console.log memory { deltaMemory: 1.780517578125, memoryBefore: 49.78370666503906, memoryAfter: 51.56422424316406 } ``` - Uncomment [the log line](https://github.com/facebook/yoga/blob/dc2581f229cb05c7d2af8dee37b2ee0b59fd5326/yoga/node/Node.cpp#L68-L73) - Run `yarn memory-test` - Observe memory diff ``` memory { deltaMemory: 16.605636596679688, memoryBefore: 49.90489196777344, memoryAfter: 66.51052856445312 } ``` Having the log line significantly increases memory allocations that never free up by NodeJS Garbage Collector
Obi-Dann commented 2025-08-21 14:07:51 -07:00 (Migrated from github.com)

Hi @NickGerleman, I created a new reproduction example of this issue that does not rely on @react-pdf to reduce investigation scope.
https://github.com/Obi-Dann/yoga/pull/1
There's clearly something wrong with the way logging is setup in WASM/JS, even though yoga::log tries to output warnings they don't show up in the console. Perhaps that's the issue as they get captured somewhere?

Hi @NickGerleman, I created a new reproduction example of this issue that does not rely on `@react-pdf` to reduce investigation scope. https://github.com/Obi-Dann/yoga/pull/1 There's clearly something wrong with the way logging is setup in WASM/JS, even though `yoga::log` tries to output warnings they don't show up in the console. Perhaps that's the issue as they get captured somewhere?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DaddyFrosty/yoga#1572
No description provided.