From d7a07e7fdf935bc521dbdf7e89e983f21ad3b26f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 24 Apr 2024 11:42:58 -0500 Subject: [PATCH] [Mono.Android] fix potential leak in Java.Lang.Thread Fixes: https://github.com/dotnet/maui/issues/18757 Context: https://github.com/dotnet/maui/pull/22007 Context: https://cs.android.com/android/platform/superproject/+/main:frameworks/base/core/java/android/view/View.java;l=19612 If you do something like: new View(myContext).Post(() => { // do something }); If the `View` is never added to the `Window` (`IsAttachedToWindow` is false), the `Runnable` will never be executed, with data stored in this dictionary: static Dictionary instances = new Dictionary (); This is a problem if the `Action`: * Is an instance method, will cause the `Action.Target` to live forever * Is an anonymous method with captured variables, will cause the captured variables to live forever I could observe this behavior in a MAUI unit test that: * Creates a `ListView` * Creates the platform view that implements `ListView` * Never adds any of these objects to the `Window` * Makes sure none of the things leak -- *this fails* This seems less likely to occur in a real application, but it is still a bug. --- src/Mono.Android/Java.Lang/Thread.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mono.Android/Java.Lang/Thread.cs b/src/Mono.Android/Java.Lang/Thread.cs index 8c6155f41d3..cc45dd16aed 100644 --- a/src/Mono.Android/Java.Lang/Thread.cs +++ b/src/Mono.Android/Java.Lang/Thread.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Android.Runtime; @@ -27,7 +28,7 @@ public RunnableImplementor (Action handler, bool removable) this.removable = removable; if (removable) lock (instances) - instances [handler] = this; + instances.AddOrUpdate (handler, this); } public void Run () @@ -41,7 +42,7 @@ public void Run () Dispose (); } - static Dictionary instances = new Dictionary (); + static ConditionalWeakTable instances = new (); public static RunnableImplementor Remove (Action handler) {