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

Safari on macOS and iOS memory overuse from user movement #12906

Closed
tristan-morris opened this issue Sep 25, 2023 · 4 comments · Fixed by #12924
Closed

Safari on macOS and iOS memory overuse from user movement #12906

tristan-morris opened this issue Sep 25, 2023 · 4 comments · Fixed by #12924
Labels
needs information 🙏 needs investigation 🔍 Issues that require further research (e.g. it's not clear whether it's GL JS or something else) performance ⚡ Speed, stability, CPU usage, memory usage, or power usage

Comments

@tristan-morris
Copy link
Contributor

tristan-morris commented Sep 25, 2023

For some reason, the underlying "mapboxgl-canvas" is being copied and then references aren't cleared up. I haven't dug deep enough yet to find out what causes the copy to occur and why it persists. It could be Webkit not running GC frequently enough, or something dangling that shouldn't be. Either way, it takes up a fair chunk of memory and can do so very quicky especially if other information is loaded. It occurs when the user zooms/pans. It definitely could be a WebKit bug but I'm not familiar enough with the mapbox-gl-js codebase.

63 copies of the 10MB mapboxgl-canvas container, after 50 seconds (macOS, Safari)
image

Another run
image

To the broader issue:

I've been debugging #9691 (context lost on iOS in particular). From XCode Instrumentation I can see a Low Memory warning is issued a few times, and then a reaper kills off the WebGL process (it's killing whatever it can), which then causes the context to be lost. I don't quite know if this is entirely causal to the related issue #9691 as instrumenting crashing processes is difficult, the debug context is lost along with memory snapshots when the webgl/webkit process is killed. However, I can see my iOS process has a memory budget of ~1.5GB on my test device. Each Mapbox instantiation takes up ~100mb and it is killed within < 60 seconds after a full load of my app. GC is definitely occurring to bring memory back under control, but it doesn't occur fast enough when it's finally reaped--assuming memory is still growing anyway. The issue detailed above might not be entirely causal but it's in the path to figuring out what is holding all of the memory.

Low memory warning on iOS and then some coerced memory release. You'll notice memory usage only goes up over t.
image]

mapbox-gl-js version: main/c072bf7 and 2.15.0.

browser: Safari on macOS and iOS - (Safari 16.6 (18615.3.12.11.2)). It does not occur in Chrome/Edge on macOS.

Steps to Trigger Behavior

  1. Git clone mapbox-gl-js repo. Install and run per contributing.
  2. Launch debug map: http://localhost:9966/debug
  3. Run Javascript in console:
let goat = 0;
let pleaseStop = setInterval(() => {
  
     if ((goat % 2) === 0){
        map.flyTo({
          center: [-122.41381885225483 + Math.random() * 20,
        37.73787623085941],
          zoom: 16,
          duration: 7000
      });
    } else {
      map.flyTo({
            center: [148.44217489435937 - Math.random() * 10,
          -35.721640804238895],
            zoom: 16,
            duration: 7000
        });
    
    }
    
    goat++;
 
 }, 5000);
  1. Open Performance tab, start recording (ensuring Memory is enabled/captured/shown).
  2. Wait like 30 seconds before stopping recording, while the map is moved around, and before your Safari gets too angry.
  3. Run clearInterval(pleaseStop);
  4. Look at graphs.

Link to Demonstration

This fiddle isn't exactly as stripped down as the code above, but it should have the same effect.

https://jsfiddle.net/ykxaLh6t/

Expected Behavior

Canvas isn't copied around. Or if it is, then it's cleaned up.

Actual Behavior

My memory usage goes from 10Mb to 1Gb+ very quickly on Safari.

@mourner mourner added performance ⚡ Speed, stability, CPU usage, memory usage, or power usage needs investigation 🔍 Issues that require further research (e.g. it's not clear whether it's GL JS or something else) labels Sep 25, 2023
@mourner
Copy link
Member

mourner commented Sep 25, 2023

This is really interesting, thanks for the detailed report! Can't think of any logical reason that canvas object would be leaked when simply animating the camera back and forth, but will investigate. @tristan-morris just one thing — can you confirm that the memory usage grows on Safari without falling back if you don't open the dev tools at all? Just to exclude the possibility that Safari is itself leaking through a devtools bug that doesn't affect normal users.

@tristan-morris
Copy link
Contributor Author

Thanks @mourner. It's pretty strange indeed.

It looks like the significant memory growth is due to Safari instrumentation, but the actual object allocations aren't... If instrumentation is running on page load, the memory growth significantly increases. But if instrumentation isn't running on page load, but captured later, there isn't significant memory growth but there are still growing references.

image

However, if instrumentation isn't running the MB growth doesn't go up but the references do? (224 refs to canvas).

Screenshot 2023-09-25 at 14 15 23

So, either references are being copied around and aren't ordinarily released - which makes more sense - feels very webkit. Or, whole copies of the element are, which never really made sense, but could be(?). I'm thinking these reference copies add up with a busy map - but will continue investigating and get some more data.

@tristan-morris
Copy link
Contributor Author

I feel like I've made very little tangible progress looking into this further.

  • The above code is pretty reliable in crashing WkWebView and Safari on iOS and macOS.
  • The crash is due to excessive memory and WebKit or WebGL being reaped by the OS. I still can't really determine which, and instrumenting the GPU to a useful level is seemingly hard.
  • While the flyTo/flyTo loop above is a pathological case, unfortunately my users are experiencing this pretty frequently. If memory usage is "high" (i.e. when your map is loaded with stuff - but nothing too wild) the tolerance to zooming is very low. And so in prod, I'm experiencing crashes maybe every 10-20 seconds with a loaded map.
  • Safari diagnostics connected or not doesn't impact the crash.

Two interesting things which might not be related at all, but don't feel right...

  • If I set window.createBitmapImage = null;, it crashes less frequently - this was a wild guess after looking at related Safari issues (3d terrain crashes Safari 15.3 #11494).
  • If I set flyTo with preloadOnly: true it will still crash.
  • As a bonus observation, after running this for a few minutes iOS itself will restart...

To replicate the issue more quickly (iOS is faster as more resource constrained):

  • you can put the above javascript into the index.html in debug/
  • run the yarn start-debug command
  • Browse to the page in Safari
  • in XCode, Debug > Induce Device Conditions > GPU > Minimum.
  • Wait a few mins.

Any pro-tips or insight would be appreciated!

@tristan-morris
Copy link
Contributor Author

Managed to crack what's going on. Excessive video memory is being allocated. See PR #12924, needs input from mb.

stepankuzmin pushed a commit that referenced this issue Dec 4, 2023
…2906 (#12924)

* Consistent Tile.destroy() with texture caching

* Fix up potential race

Co-authored-by: Volodymyr Agafonkin <agafonkin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs information 🙏 needs investigation 🔍 Issues that require further research (e.g. it's not clear whether it's GL JS or something else) performance ⚡ Speed, stability, CPU usage, memory usage, or power usage
Projects
None yet
2 participants