mono / SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
MIT License
4.14k stars 522 forks source link

[CRASH] System.NullReferenceException when Invalidating an SKGLView after porting Xamarin.Forms app to MAUI #2812

Closed Picao84 closed 3 weeks ago

Picao84 commented 1 month ago

Description

I'm seeing crashes on Production when manually invalidating a SKGLView (HasRenderLoop is false) on a timer (every 16.7 ms) on an app that was ported from Xamarin.Forms to MAUI. I've never seen this crash once on Forms and did not make any code change on this control when porting to MAUI. I cannot reproduce the issue myself either.

The stack trace looks like this:

SkiaSharp.Views.Maui.Controls.Compatibility.SKGLViewRendererBase`2[[SkiaSharp.Views.Maui.Controls.SKGLView, SkiaSharp.Views.Maui.Controls, Version=2.88.0.0, Culture=neutral, PublicKeyToken=null],[SkiaSharp.Views.Android.SKGLTextureView, SkiaSharp.Views.Android, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756]].OnSurfaceInvalidated(Object sender, EventArgs eventArgs)
SkiaSharp.Views.Maui.Controls.SKGLView.InvalidateSurface()
Sueca.Forms.FormsPages.GamePage.<Timer_Elapsed>b__16_0()
Microsoft.Maui.Dispatching.Dispatcher.<>c__DisplayClass4_0.<DispatchImplementation>b__0()
Java.Lang.Thread.RunnableImplementor.Run()
Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this)
Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz)

Code

  protected override void OnPaintSurface(SKPaintGLSurfaceEventArgs e)
  {

      if (drawing)
          return;

      try
      {

          drawing = true;

              if(e.Surface == null)
              {
                  Analytics.TrackEvent("Surface is null");
                  return;
              }

              var canvas = e.Surface.Canvas;

              if (canvas == null)
              {
                  Analytics.TrackEvent("Canvas is null");
                  return;
              }

              canvas.Clear();

              if ((Cards == null || Cards.Count == 0) && (PlayedCards == null || PlayedCards.Count == 0) || (UserCards == null || UserCards.Count == 0))
                  return;

          try
          {
              AllButPlayedCards = Cards?.Where(x => !PlayedCards.Contains(x)).ToList();
          }
          catch(Exception ex)
          {
              Crashes.TrackError(ex, null, ErrorAttachmentLog.AttachmentWithText("Cards is Null", string.Empty));
              return;
          }

              for (int index = 0; index < AllButPlayedCards.Count; index++)
              {
                  if (index < AllButPlayedCards.Count && AllButPlayedCards[index] != null)
                  {

                      using (new SKAutoCanvasRestore(canvas, true))
                      {
                          try
                          {
                              AllButPlayedCards[index].DrawCard(canvas);
                          }

                          catch (Exception ex)
                          {

                              Crashes.TrackError(ex, null, ErrorAttachmentLog.AttachmentWithText("Error on AllButPlayedCards", "TableCanvas"));

                          }
                      }
                  }       
              }

              for (int index = 0; index < PlayedCards.Count; index++)
              {
                  if (index < PlayedCards.Count && PlayedCards[index] != null)
                  {

                      using (new SKAutoCanvasRestore(canvas, true))
                      {
                          try
                          {
                              if (drawPlayedCards)
                              {
                                  PlayedCards[index].DrawCard(canvas);
                              }

                          }
                          catch (Exception ex)
                          {
                              Crashes.TrackError(ex, null, ErrorAttachmentLog.AttachmentWithText("Error on PlayedCards", "TableCanvas"));
                          }
                      }
                  }
              }

      }

      catch (Exception ex)
      {
          Crashes.TrackError(ex, null, ErrorAttachmentLog.AttachmentWithText("Error when drawing", "TableCanvas"));
      }
      finally
      {
          drawing = false;
      }

  }

Expected Behavior

There should not be a a null pointer exception.

Actual Behavior

0.05% of users are experiencing this crash.

Version of SkiaSharp

2.88.3 (Current)

Last Known Good Version of SkiaSharp

2.88.2 (Previous)

IDE / Editor

Visual Studio (Windows)

Platform / Operating System

Android

Platform / Operating System Version

Android 14 & Android 13

Devices

Samsung Galaxy A13, Samsung Galaxy A34 5G, Samsung Galaxy A54 5G, Samsung Galaxy S20 FE 5G

Relevant Screenshots

No response

Relevant Log Output

SkiaSharp.Views.Maui.Controls.Compatibility.SKGLViewRendererBase`2[[SkiaSharp.Views.Maui.Controls.SKGLView, SkiaSharp.Views.Maui.Controls, Version=2.88.0.0, Culture=neutral, PublicKeyToken=null],[SkiaSharp.Views.Android.SKGLTextureView, SkiaSharp.Views.Android, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756]].OnSurfaceInvalidated(Object sender, EventArgs eventArgs)
SkiaSharp.Views.Maui.Controls.SKGLView.InvalidateSurface()
Sueca.Forms.FormsPages.GamePage.<Timer_Elapsed>b__16_0()
Microsoft.Maui.Dispatching.Dispatcher.<>c__DisplayClass4_0.<DispatchImplementation>b__0()
Java.Lang.Thread.RunnableImplementor.Run()
Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this)
Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz)

Code of Conduct

mattleibow commented 1 month ago

I am not sure what the core issue is here, but i see something that may be causing an issue:

try
{
    AllButPlayedCards = Cards?.Where(x => !PlayedCards.Contains(x)).ToList();
}
catch(Exception ex)
{
    Crashes.TrackError(ex, null, ErrorAttachmentLog.AttachmentWithText("Cards is Null", string.Empty));
    return;
}

for (int index = 0; index < AllButPlayedCards.Count; index++)
{
    // ...

The Cards?.Where( will probably not throw, but will assign null to the property. If you remove the ? the "Cards is Null" error will then be logged.

mattleibow commented 1 month ago

Can you try SkiaSharp 3.0 previews? This should allow you to use the new GL things.

taublast commented 3 weeks ago

Can you please show the code you use to invalidate the SKGLView?

Picao84 commented 3 weeks ago

Hi, I found the issue is related to MAUI. It seems that sometimes when the app goes into the background and Android eventually kills the activity, the MAUI process is not fully stopped. When the user re-opens the app, the existing MAUI process is resumed but the activity is gone leading to issues like this. I have mitigated it somewhat by setting Launch Mode as Single Task. I think we can close this.