vue.jsmemory-leaksopenlayersintersection-observerwms

How to fix IntersectionObserver causing a memory leak in my OpenLayers web app?


So I'm developing a web application called MSC AniMet. This application is coded using Vue2 and Vuetify2 and it's essentially a big OpenLayers map with a bunch of stuff and functionality added to it and works with WMS layers.

I recently made an update so the layers added would refresh whenever new information was available so that you'd always have Weather layers with the latest information on them and so you could use the application as a dashboard. It seemed to work fine, until 3 days after running it as a dashboard chrome crashed saying Out of Memory.

After some investigation, I've found using the chrome devtools that the culprit seemed to be IntersectionObserver which was taking more and more memory at an alarming rate. Here's 2 snapshots taken 15min apart: enter image description here enter image description here

The problem is that I'm not using an IntersectionObserver inside my code so I have no clue what's causing this. I have a feeling it comes from OpenLayers but that's only my guess because of what libraries I'm using:

├── @mdi/font@7.3.67
├── @mdi/js@7.1.96
├── @vue/cli-plugin-babel@5.0.8
├── @vue/cli-service@5.0.8
├── axios@0.26.1
├── babel-eslint@10.1.0
├── canvas-txt@3.0.0
├── core-js@3.27.2
├── countries-and-timezones@3.5.1
├── debounce@1.2.1
├── eslint-plugin-vue@6.2.2
├── eslint@6.8.0
├── h264-mp4-encoder@1.0.12
├── luxon@2.5.2
├── node-polyfill-webpack-plugin@2.0.1
├── ol@6.15.1
├── proj4@2.10.0
├── sass-loader@8.0.2
├── sass@1.32.13
├── saxon-js@2.6.0
├── vue-cli-plugin-vuetify@2.5.8
├── vue-i18n@8.28.2
├── vue-router@3.6.5
├── vue-template-compiler@2.7.14
├── vue@2.7.14
├── vuetify-loader@1.9.2
├── vuetify@2.6.14
└── vuex@3.6.2

Here's a permalink for the website for a reproducible example: https://eccc-msc.github.io/msc-animet/?layers=RADAR_1KM_RRAI%3B0.75%3B0%3B1%3B0%3B1,RADAR_1KM_RSNO%3B0.75%3B0%3B1%3B0%3B0,RADAR_COVERAGE_RRAI%3B0.75%3B0%3B1%3B0%3B0&extent=-19416707,2902733,-620801,11820999

In the time controller, over the play button there's a little cog icon, in there you can set the animation to loop and then just press the play button. If you check the memory usage you'll see the IntersectionObserver creeping up as time goes on.

I will put github links to show what the looping does in the code and what's called:

  1. The Play button triggers https://github.com/ECCC-MSC/msc-animet/blob/main/src/components/Time/AnimationControls/PlayPauseControls.vue#L84, which then calls https://github.com/ECCC-MSC/msc-animet/blob/main/src/components/Time/AnimationControls/PlayPauseControls.vue#L131 to start the animation
  2. The Play button increments the DateIndex, which triggers this watcher: https://github.com/ECCC-MSC/msc-animet/blob/main/src/components/Time/TimeControls.vue#L388
  3. This calls this method: https://github.com/ECCC-MSC/msc-animet/blob/main/src/components/Time/TimeControls.vue#L183, which then updates the layers' TIME parameter here https://github.com/ECCC-MSC/msc-animet/blob/main/src/components/Time/TimeControls.vue#L378
  4. Finally, the method awaits for the map's rendercomplete event and throws and event https://github.com/ECCC-MSC/msc-animet/blob/main/src/components/Time/TimeControls.vue#L248, which is caught again by the step 2 which increments the DateIndex again and that is the entire loop.

I tried upgrading openlayers to the latest version (9.1) and it didn't change anything (apart from another unrelated breaking change which is why I'm currently sticking to 6.15.1). I've also tried commenting out many of the imports like h264-mp4-encoder but it didn't impact the Intersect problem.


Solution

  • After looking through the libraries I was using and looking at which ones contained IntersectionObserver, I found that vuetify's v-progress-linear was using it. This component was used in my code when a layer was loading: I incremented a flag called loading when a layer was loading and when it was loaded I decremented it. The v-progress-linear would appear when the flag was above 0 and, for some reason, it was using an IntersectionObserver that ended up taking more and more memory. Since I was in a loop where I always loaded new timesteps, this loading bar would always be active. I have removed this component in my branch and the problem has indeed disappeared.

    Edit: Looked more into the problem and it was just a parent issue. The VProgressLinear uses the IntersectionObserver, which checks intersections with its parent. For some reason, putting it inside of the openlayers map makes it very unhappy and if I just move it out of that div it's doing fine.

    Edit2: Nevermind moving it out of the div just made it a little slower to appear but memory leak is still there. Decided to reimplement a progress-bar from scratch instead since vuetify's v-progress-bar seems to always cause memory leaks whatever I try. Based it off this code https://medium.com/@kapilkumar0037/indeterminate-progress-bar-using-css-in-angular-0fd5f0151795.