emscripten icon indicating copy to clipboard operation
emscripten copied to clipboard

Out-of-memory error in Safari (iOS/macOS) when reloading a page

Open MathisRaibaud opened this issue 2 years ago β€’ 27 comments
trafficstars

Hello, I encountered an issue which seems to be systematic when building WASM code with Emscripten.

When building with -pthread enabled, the resulting WASM code make Safari crash with an error RangeError: Out-of-memory error on iOS and macOS after several reloads of the page. The number of reloads before getting the error seems to depend on the MAXIMUM_MEMORY value (or INITIAL_MEMORY value if ALLOW_MEMORY_GROWTH is disabled) and the device.

I created a simple project to reproduce the error and add my first investigations. See https://github.com/MathisRaibaud/wasm-memory-error-safari-repro

To summarize:

  • The problem seems to occur only on Safari (iOS and macOS).
  • The problem seems to occur only if the memory is shared (using -pthread flag)
  • The error seems to be related to the MAXIMUM_MEMORY value (or to the INITIAL_MEMORY value if ALLOW_MEMORY_GROWTH is disabled).
  • From what I experimented, the memory seems to be never deallocated when the page is reloaded and therefore creates an out-of-memory after several reloads.

What do you think? Is there anything I misunderstood or do you think it may come from an Emscripten or Safari bug?

Thank you so much for your help and your amazing work πŸ™

EDIT: The problem seems to be related to the auto-generated Emscripten script (and not Safari), as it does not occur when shared memory is allocated manually (see https://github.com/emscripten-core/emscripten/issues/19374#issuecomment-1550945358).

MathisRaibaud avatar May 16 '23 16:05 MathisRaibaud

How many threads are you trying to create? Perhaps limit you threads to just a few (2 or 4 perhaps?).

sbc100 avatar May 16 '23 19:05 sbc100

Otherwise I guess it could be a safari bug?

sbc100 avatar May 16 '23 19:05 sbc100

This one maybe: https://bugs.webkit.org/show_bug.cgi?id=255103

stknob avatar May 16 '23 21:05 stknob

@sbc100 The bug does not depend on the number of threads created. In the simple project I wrote to reproduce the bug, I don't even open a thread and yet I have the bug.

I also thought it might be a Safari bug, so I tested to run the line that crashes in the code generated by Emscripten directly in an HTML page:

  var INITIAL_MEMORY = 16777216;
  wasmMemory = new WebAssembly.Memory({
   "initial": INITIAL_MEMORY / 65536,
   "maximum": 536870912 / 65536,
   "shared": true
  });

but this time, I don't get the error after reloading the page. I only have the error when I use the code generated by Emscripten.

@stknob Yes I saw that issue and also https://github.com/ffmpegwasm/ffmpeg.wasm/issues/299 which describe a similar problem. In these two issues, users report out-of-memory error on iOS only at the first access to the page. The two issues suggested reducing the MAXIMUM_MEMORY to get rid of the out-of-memory error on iOS.

And indeed, the first access to the page works. But I found that if I reloaded the page, I still got the same error. I have the impression that the number of reloads to get the error depends on the value of MAXIMUM_MEMORY. The lower it is, the more reloads you have to do before getting the error.

I then tested on Safari macOS to identify if the problem came only from iOS. On macOS, the error also occurs, but after a larger number of reloads, probably because the memory allowed by the browser is larger on Desktop than on Mobile.

MathisRaibaud avatar May 17 '23 08:05 MathisRaibaud

It seems this can't really be an emscripten issue.. since the whole is discarded each time you reload. I guess its good to have the discussion here so others can find it, but it doesn't seem like something we can fix on our end, right?

sbc100 avatar May 17 '23 15:05 sbc100

I understand and I was surprised myself. For me, if the page is reloaded, all resources should be deallocated. But from my experiments, it seems that this is not the case when the memory is shared. And I had this problem only with code generated by emscripten, I didn't manage to reproduce it by instantiating shared memory myself without going through emscripten, hence the fact that I opened the issue here and not in Safari/WebKit.

Just out of curiosity and if you have time, do you reproduce the bug on your side using the repo I linked in my first comment?

Thanks a lot in any case πŸ™‚

MathisRaibaud avatar May 17 '23 16:05 MathisRaibaud

I'm afraid I don't have a iOS or macOS device myself. But perhaps others can confirm?

sbc100 avatar May 17 '23 16:05 sbc100

@sbc100 I'm getting the same "RangeError: Out of memory" after about 15 reloads on a Version 16.4 (18615.1.26.110.1) Desktop Safari (M1 MAX, 32GiB RAM), using @MathisRaibaud's repro.

And still happens with the latest macOS update applied, Safari Version 16.5 (18615.2.9.11.4), after about 30 reloads.

px-stkn avatar May 22 '23 18:05 px-stkn

@sbc100

  • Same here, on various iOS (iphones and ipads) and macOS (Intel and ARM) devices with up-to-date OS and Safari.
  • This behaves the same for me, pointing to an Emscripten issue.

Personally not a big fan of Safari, but it is quite widely used and we have to support it πŸ˜‰

It would awesome to have some update on this as it makes emscripten-generated modules quite unreliable for many users πŸ™

ThomasHezard avatar May 26 '23 14:05 ThomasHezard

As this sounds like a Safari-specific issue, it might help for more people affected by the issue to comment with details on the WebKit issue that's already been filed, that is linked here:

https://github.com/emscripten-core/emscripten/issues/19374#issuecomment-1550371248

More information there might help them investigate, and could also affect prioritization.

Otherwise, if someone can find a workaround we could do on the Emscripten side we'd be happy to do that, but if it's just Safari limiting the amount of memory (my best guess from the above) then I'm not sure we can do much for such a WebKit bug/limitation.

kripken avatar May 31 '23 21:05 kripken

I am facing a similar issue. In my case, I do not set "shared": true, but the problem arises when I use WASM inside an AudioWorklet.

The "Out of memory" error occurs after repeatedly reloading a web page that simply generates a WebAssembly.Memory in an AudioWorklet, regardless of Emscripten. This issue occurs on iOS Safari and iOS Chrome. I created a bug report since I think this is a bug of WebKit. https://bugs.webkit.org/show_bug.cgi?id=256023

Interestingly, there is a workaround that prevents the "Out of memory" error. If you just refer to the buffer of memory generated in AudioWorklet as

const mem = new WebAssembly.Memory({
  'initial': MEMORY_SIZE / 65536,
  'maximum': MEMORY_SIZE / 65536,
});
mem.buffer;

, the error goes away even though the mem.buffer line is not supposed to do anything special to the buffer.

Demo page: https://wasm-memory-in-audioworklet-test.netlify.app/

The "Out of memory" error also occurs when instantiating WASM using Emscripten, but the code generated by Emscripten is too complex for me to analyze and I have not been able to solve the problem using the workaround successfully.

Hiroki-Tamaru avatar Jun 05 '23 04:06 Hiroki-Tamaru

@Hiroki-Tamaru Thanks for your message, I came across your issue yesterday and wanted to reply, but you beat me to it! πŸ˜‰

I tested your demo and had the same problem: on iOS, after ~7 reloads, I get the "Out of memory" error. And if I call mem.buffer, I no longer get the error in your demo. I also tested your demo on Safari macOS (Safari 16.4) and I also have the same problem but after a large number of reloads (~66 on my machine, it was long to test!).

In this case, Emscripten is not involved, so the problem may actually be with Safari/WebKit.

In the repository I made to reproduce the bug, I've tried your workaround, i.e refer to buffer after the init of the WebAssembly.Memory in the code generated by Emscripten:

  wasmMemory = new WebAssembly.Memory({
   "initial": INITIAL_MEMORY / 65536,
   "maximum": 536870912 / 65536,
   "shared": true
  });
  wasmMemory.buffer; // <-- added this line manually

but I still got the Out-of-memory error in my case...

MathisRaibaud avatar Jun 05 '23 13:06 MathisRaibaud

@kripken Thank you for your reply πŸ™‚ . The problem you reported is not really the same as the one described here. In the problem you mentioned, the insufficient memory error occurs when the page first loads, and that I can understand. On Safari iOS, there's less memory available, so if you don't reduce the MAXIMUM_MEMORY value, you'll exceed the memory limit. The problem here is a little different, we manage to load the page on Safari, but after a few reloads, the page crashes with an out-of-memory error, as if the available memory is decreasing reload after reload... which seems strange.

After your message, I've checked if there are others issues opened in WebKit that may be related to the problem described here and have found two issues:

  • https://bugs.webkit.org/show_bug.cgi?id=256023 (@Hiroki-Tamaru)
  • https://bugs.webkit.org/show_bug.cgi?id=222097

In these two issues, an out-of-memory error is reported when reloading the page and Emscripten is not involved in either of these issues, so it might be a Safari-specific issue at the end, as you said.

MathisRaibaud avatar Jun 05 '23 13:06 MathisRaibaud

Hi everyone. This issue is biting me as it creates serious problems for the users of my barcode scanning library STRICH. I've created a simplified reproducer repro: https://github.com/pixelverse-llc/safari-wasm-oom-reproducer

It consists of just an index.c file containing:

#include <stdio.h>

int main() {
  printf("Hello from Safari iOS WASM OOM reproducer!\n");
  return 0;
}

and an accompanying build script:

#!/bin/sh
emcc \
    -sENVIRONMENT=web \
    -sINITIAL_MEMORY=67108864 \
    -sALLOW_MEMORY_GROWTH=1 \
    -sABORTING_MALLOC=1 \
    index.c -o index.html

Repeatedly loading the resulting index.html in Safari on iOS 17.5.1 triggers the issue after a couple of refreshes, and crucially persists across reloads, necessitating a TAB REOPEN (edit: previously though browser needed to be restarted). I will submit this reproducer to the Webkit issues mentioned in the previous post.

Edit: I can not reproduce this in the Safari iOS 18 Developer Beta (22A5282m). Can anyone confirm?

suzukieng avatar Jun 18 '24 09:06 suzukieng

I've mitigated a related issue (OOM during growth) by forcing a larger memory allocation (500mb) on Safari, and disabling memory growth. OOM is triggered during growth, no matter the initial size, and it became worse after iOS 17.4.

In my testing, I came to the conclusion that reload OOM is triggered much earlier if using shared memory, and unlike the non-shared, which can be fixed by closing and reopening the tab, only shared memory required an entire app reload.

Can someone confirm this?

ivancuric avatar Jun 18 '24 10:06 ivancuric

Hi everyone. This issue is biting me as it creates serious problems for the users of my barcode scanning library STRICH. I've created a simplified reproducer repro: https://github.com/pixelverse-llc/safari-wasm-oom-reproducer

It consists of just an index.c file containing:

#include <stdio.h>

int main() {
  printf("Hello from Safari iOS WASM OOM reproducer!\n");
  return 0;
}

and an accompanying build script:

#!/bin/sh
emcc \
    -sENVIRONMENT=web \
    -sINITIAL_MEMORY=67108864 \
    -sALLOW_MEMORY_GROWTH=1 \
    -sABORTING_MALLOC=1 \
    index.c -o index.html

Repeatedly loading the resulting index.html in Safari on iOS 17.5.1 triggers the issue after a couple of refreshes, and crucially persists across reloads, necessitating a TAB REOPEN (edit: previously though browser needed to be restarted). I will submit this reproducer to the Webkit issues mentioned in the previous post.

Edit: I can not reproduce this in the Safari iOS 18 Developer Beta (22A5282m). Can anyone confirm?

This the issue also happen if you remove -sALLOW_MEMORY_GROWTH?

How about if you add -sMAXIMUM_MEMORY=100mb?

sbc100 avatar Jun 18 '24 17:06 sbc100

@sbc100 I ran it again with no memory growth and also with increased memory. Both did not help. I also adapted the reproducer to not use -sALLOW_MEMORY_GROWTH, I was unaware it was off by default. Please let me know if I should test other things, I can very easily reproduce this.

This is what's being dumped to the console:

[Error] wasm streaming compile failed: RangeError: Out of memory
	(anonymous function) (index.js:708)
[Error] falling back to ArrayBuffer instantiation
	(anonymous function) (index.js:709)
[Error] failed to asynchronously prepare wasm: RangeError: Out of memory
	(anonymous function) (index.js:680)
[Error] Aborted(RangeError: Out of memory)
	abort (index.js:562)
	(anonymous function) (index.js:686)
[Error] Unhandled Promise Rejection: RuntimeError: Aborted(RangeError: Out of memory)

The playground is stuck in "preparing...".

emscripten_preparing

What's encouraging is that I can not reproduce the issue on Safari iOS 18 Developer Beta, so it might be fixed there. The release notes don't mention anything in that direction (https://developer.apple.com/documentation/safari-release-notes/safari-18-release-notes#Web-Assembly) but that's unsurprising to me.

From my view this looks like a browser bug. Maybe if you in your capacity as maintainer of Emscripten could add your voice to the WebKit issue, it might help raise awareness?

suzukieng avatar Jun 19 '24 05:06 suzukieng

For what it's worth, I also tried lower memory values like 32MB or 16MB, the issue also happens, just takes longer to appear.

suzukieng avatar Jun 19 '24 06:06 suzukieng

Yes, I agree this does sounds like a browser bug. If it really is fixed upstream already that would be great!

sbc100 avatar Jun 19 '24 17:06 sbc100

Good news: I can also no longer reproduce this in Safari on iOS 17.6 Beta (21G5052e). Can someone confirm? @ivancuric maybe?

suzukieng avatar Jun 20 '24 06:06 suzukieng

Will be able to test on Monday.

ivancuric avatar Jun 21 '24 07:06 ivancuric

Hi all, how's this issue going? I met the same error om safari 17.5. my initialize memory is 64MB

oatgnauh avatar Jul 11 '24 03:07 oatgnauh

Now that ios safari 17.6 has been officially released, based on @suzukieng 's observation, can anyone independently confirm that this issue has been resolved / improved?

dr-matt avatar Aug 01 '24 15:08 dr-matt

@dr-matt I tested again today on iOS 17.6 (21G80), and apparently the issue still exists (see comment on the WebKit issue). Would really appreciate if someone else can confirm the observation.

suzukieng avatar Aug 06 '24 06:08 suzukieng

@ivancuric @MathisRaibaud have you seen progress on this issue since iOS 17.6? I am still able to reproduce it, although it happens less frequently. Also I have an iPhone 12 on the iOS 18 Public Beta where I have not been able to reproduce yet. It's a bit frustrating that there is no indication from the WebKit team regarding the status of the issue.

suzukieng avatar Aug 06 '24 13:08 suzukieng

@suzukieng I've just checked and I'm still reproducing it in iOS 17.6.

MathisRaibaud avatar Aug 06 '24 13:08 MathisRaibaud

Thanks everybody for checking, I don't have access to a compatible device, but have had indirect user reports that agree it's not fixed. I'm hoping that this and growable shared memory are both resolved when iOS 18 is released in a month or two...

dr-matt avatar Aug 06 '24 21:08 dr-matt

FYI, I've just tested using my reproduction repository with an iPhone 11 Pro on iOS 18 beta 8 (22A5350a), it seems fixed πŸ™

I'll do another test when the official version of iOS 18 is available and let you know the result.

MathisRaibaud avatar Sep 03 '24 13:09 MathisRaibaud

It appears fixed to me in the officially released iOS 18.

dr-matt avatar Sep 23 '24 17:09 dr-matt

Awesome! Closing this for now. Feel free to re-open as needed.

sbc100 avatar Sep 23 '24 18:09 sbc100