react-native-skia icon indicating copy to clipboard operation
react-native-skia copied to clipboard

Crashing on Android with only a Canvas

Open gustavomts opened this issue 1 year ago • 18 comments

Description

For some reason, the app is crashing on Android after opening a modal with Canvas multiple times. Each time the modal is opened the memory usage goes up but it doesn't go back down when it's closed.

This problem was also happening on iOS but it seems like it was fixed in 0.1.184 beta.

This problem didn't seem to exist in v0.1.176 for Android.

Version

0.1.182 and 0.1.185

Steps to reproduce

Basically, you just need to keep opening and closing the modal until the app crashes.

Snack, code example, screenshot, or link to a repository

https://github.com/ivensgustavo/rnskia-android-crash

gustavomts avatar Apr 20 '23 16:04 gustavomts

Hi @gustavomts! Thanks for the report and the example. With the latest version I'm not able to make it crash. The memory seems to be released correctly released when the Modal is dismissed. I'm running the profiler on an Android device. The fix we had in 0.1.184 was primarily targeting a regression we had on iOS that was not happening on Android. Could you try to profile the behaviour you're seeing and / or debug to see where or why it is crashing?

chrfalch avatar Apr 21 '23 11:04 chrfalch

Hi @chrfalch, sure. I'll try to get back to you before the end of the week.

gustavomts avatar Apr 24 '23 13:04 gustavomts

@chrfalch I have some logs here

2023-04-24 11:08:13.908 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 55 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:13.910 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:16.105 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:17.308 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:20.556 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:22.428 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:24.534 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:26.486 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:27.918 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 54 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:27.921 28184-8145  OpenGLRenderer          com.rnskiacrash                      I  Davey! duration=992ms; Flags=0, IntendedVsync=25991502724076, Vsync=25991569390740, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=25991581687105, AnimationStart=25991581689345, PerformTraversalsStart=25992490084071, DrawStart=25992490230789, SyncQueued=25992490700321, SyncStart=25992491538602, IssueDrawCommandsStart=25992492231779, SwapBuffers=25992494698602, FrameCompleted=25992496200165, DequeueBufferDuration=379531, QueueBufferDuration=728698, GpuCompleted=25958811601184, 
2023-04-24 11:08:28.401 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:28.566 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:29.713 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:30.647 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:33.016 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:33.603 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:33.829 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:34.765 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:38.816 28184-28184 unknown:ReactModalHost  com.rnskiacrash                      E  Creating new dialog from context: com.rnskiacrash.MainActivity@e589d42@240688450
2023-04-24 11:08:39.527 28184-8145  OpenGLRenderer          com.rnskiacrash                      I  Davey! duration=715ms; Flags=1, IntendedVsync=26003386098672, Vsync=26003386098672, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=26003386509687, AnimationStart=26003386511042, PerformTraversalsStart=26003881847080, DrawStart=26004041132669, SyncQueued=26004092232310, SyncStart=26004092662726, IssueDrawCommandsStart=26004092768872, SwapBuffers=26004101621738, FrameCompleted=26004102203821, DequeueBufferDuration=103125, QueueBufferDuration=306198, GpuCompleted=0, 
2023-04-24 11:08:39.528 28184-28184 unknown:ReactModalHost  com.rnskiacrash                      E  Updating existing dialog with context: com.rnskiacrash.MainActivity@e589d42@240688450
2023-04-24 11:08:39.528 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 42 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:40.480 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:42.603 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 35 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:43.943 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 78 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:44.015 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:44.015 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:45.207 28184-28184 unknown:ReactModalHost  com.rnskiacrash                      E  Creating new dialog from context: com.rnskiacrash.MainActivity@e589d42@240688450
2023-04-24 11:08:45.692 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 36 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:45.803 28184-28184 unknown:ReactModalHost  com.rnskiacrash                      E  Updating existing dialog with context: com.rnskiacrash.MainActivity@e589d42@240688450
2023-04-24 11:08:47.226 28184-8145  OpenGLRenderer          com.rnskiacrash                      D  endAllActiveAnimators on 0xb4000076b13b7300 (LayerDrawable) with handle 0xb4000076d55a6960
2023-04-24 11:08:47.860 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 45 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:47.869 28184-8145  OpenGLRenderer          com.rnskiacrash                      I  Davey! duration=784ms; Flags=0, IntendedVsync=26011652765731, Vsync=26011652765731, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=26011653152334, AnimationStart=26011653153271, PerformTraversalsStart=26012433451630, DrawStart=26012433543818, SyncQueued=26012433688037, SyncStart=26012434428818, IssueDrawCommandsStart=26012434592620, SwapBuffers=26012436153193, FrameCompleted=26012438023922, DequeueBufferDuration=343594, QueueBufferDuration=1144427, GpuCompleted=25959886312958, 
2023-04-24 11:08:48.561 28184-28184 Choreographer           com.rnskiacrash                      I  Skipped 36 frames!  The application may be doing too much work on its main thread.
2023-04-24 11:08:48.611 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
2023-04-24 11:08:48.770 28184-8193  ReactNativeJNI          com.rnskiacrash                      I  Memory warning (pressure level: TRIM_MEMORY_RUNNING_LOW) received by JS VM, ignoring because it's non-severe
2023-04-24 11:08:49.504  1726-2405  InputDispatcher         system_server                        E  channel 'b285455 com.rnskiacrash/com.rnskiacrash.MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!

In this video, you can see the app increasing the memory usage until it crashes. It takes a while so I had to cut it short, but you can still notice how the memory usage grows. https://user-images.githubusercontent.com/39544919/234285127-d3d6657b-ed07-4fef-83d9-3e09b2e57d0c.mp4

gustavomts avatar Apr 25 '23 13:04 gustavomts

  public afterMount(): void {
    setTimeout(this.toggle, 1000);
  }

  @bind
  public toggle(): void {
    this.setState({visible: !Boolean(this.state.visible)}, () => {
      setTimeout(this.toggle, 1);
    });
  }

  public render(): any {
    if (this.state.visible) {
      return (
        <Canvas key={'canvas'} style={{ width: '100%', height: 300, display: 'flex'}}>
          <Circle cx={128} cy={128} r={128} color="lightblue"/>
        </Canvas>
      );
    }
  }

I confirm, this code crashes the application in 30 seconds. Android 9 Version 0.1.193

checked version 0.1.176, the application crashes there too

Update There is no memory leak when using SkiaView

<SkiaView onDraw={this.onDraw}/>

hitman249 avatar Jun 06 '23 06:06 hitman249

The example was not possible to run out of the box, but I created this example component:


export const Breathe = () => {
  const [visible, setVisible] = React.useState(true);
  const update = () => {
    setTimeout(() => {
      setVisible((v) => !v);
      update();
    }, 1000);
  };
  useEffect(() => {
    update();
  }, []);

  return visible ? (
    <Canvas
      key={"canvas"}
      style={{ width: "100%", height: 300, display: "flex" }}
    >
      <Circle cx={128} cy={128} r={128} color="lightblue" />
    </Canvas>
  ) : null;
};

When run in Android it refreshes every second, and the memory stays pretty stable over time (we see a small increase in memory but no crash - how long do you need to run this to see the crash?

chrfalch avatar Jun 07 '23 07:06 chrfalch

With your interval of 1000 milliseconds, I need to wait 1000 times longer, because in my case the interval is 1 millisecond.

1000 * 30 = 30000 seconds 30000 / 60 = 500 minutes 500 / 60 = 8,3 hours

Your example will crash in only 8 hours..

In a real application from production, the frequency of re-creating the canvas reaches ~10+ times per second

hitman249 avatar Jun 07 '23 10:06 hitman249

Makes sense, I just took the numbers from your example where you used 1000 ms as the timeout :) :)

chrfalch avatar Jun 07 '23 10:06 chrfalch

We would definetly look for another way to solve this than by recreating the Canvas so often. Could you describe your use case a bit more?

chrfalch avatar Jun 07 '23 10:06 chrfalch

<VerticalList>
  <HorizontalList>[canvas1][canvas2]</HorizontalList>
  <HorizontalList>[canvas1][canvas2]</HorizontalList>
  <HorizontalList>[canvas1][canvas2]</HorizontalList>
  <HorizontalList>[canvas1][canvas2]</HorizontalList>
  ...
</VerticalList>

When scrolling HorizontalList to the right, the canvas jumps into view. When changing the screen, all these canvases are deleted, others are created, which leads to the crash of the application within ~ 10 minutes of continuous use.

Update Specifically for me, the task is not relevant.

I rewrote my project to the imperative style and SkiaView, as a result of which the memory leaks disappeared, and the code began to work faster than through Canvas.

hitman249 avatar Jun 07 '23 10:06 hitman249

@hitman249 Could you please share your leak-free implementation? I'm curious what the imperative style looks like

cjhines avatar Jul 13 '23 13:07 cjhines

@cjhines

@hitman249 Could you please share your leak-free implementation? I'm curious what the imperative style looks like

An example is in the documentation Custom Drawing

hitman249 avatar Jul 14 '23 07:07 hitman249

@hitman249 I would be curious to see the code that runs faster in SkiaView than Canvas. I would also be curious to look at the suspected memory leak

wcandillon avatar Jul 25 '23 06:07 wcandillon

In my issue discussing the crash present in the workaround, I observed that even an empty canvas is gradually accumulating memory.

Screenshot 2023-08-02 at 11 38 02

https://github.com/Shopify/react-native-skia/assets/16542421/ddbd4357-a7d4-40d5-8003-aaf59c56f1aa

cjhines avatar Aug 02 '23 09:08 cjhines

@cjhines do you have a small reproducible example for this?

wcandillon avatar Aug 03 '23 06:08 wcandillon

@wcandillon Sure! Here's a repo: https://github.com/cjhines/skia-memory-example

By repeatedly opening and closing the second screen with the buttons on the top, you will see memory consumption growing rapidly.

In our real app this is even worse due to SVG variety.

https://github.com/Shopify/react-native-skia/assets/16542421/4b2c986f-7dd9-4e42-8616-b204c1c428d4

cjhines avatar Aug 03 '23 10:08 cjhines

Thanks, @cjhines - I'm looking into this - the example is a bit big and we'd like to be able to reduce any number of potential causes for this to fail, would it be possible to reduce it to a single source file without any other dependencies than RN Skia (and Reanimated + navigation (stack/tabs)) if possible? Would help us a lot.

chrfalch avatar Aug 03 '23 10:08 chrfalch

@chrfalch Hugely appreciated! I've pushed a commit to simplify the project to be contained in App.tsx and removed many unrelated dependencies.

cjhines avatar Aug 03 '23 11:08 cjhines

Thanks, appreciate it!

chrfalch avatar Aug 03 '23 11:08 chrfalch