[Android] Crash at Dependency service call possible fix (#4157)
authorAndrei <andrei.misiukevich@gmail.com>
Tue, 4 Dec 2018 18:35:23 +0000 (21:35 +0300)
committerShane Neuville <shane94@hotmail.com>
Tue, 4 Dec 2018 18:35:23 +0000 (11:35 -0700)
* https://github.com/xamarin/Xamarin.Forms/issues/4097 possible fix

* renamed lock object

* updated DS and sample

* merge fix

- fixes #4097

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4097.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Core/DependencyService.cs

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4097.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4097.cs
new file mode 100644 (file)
index 0000000..aa0439f
--- /dev/null
@@ -0,0 +1,140 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using static Xamarin.Forms.DependencyService;
+using System.Linq;
+using System.Threading.Tasks;
+using System;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.Github, 4097, "Dependency service thread safety test", PlatformAffected.All)]
+       public class Issue4097 : TestContentPage
+       {
+               private const int TasksQuantity = 3000;
+
+               protected override void Init()
+               {
+                       var infoLabel = new Label
+                       {
+                               TextColor = Color.Black,
+                               VerticalOptions = LayoutOptions.CenterAndExpand,
+                               HorizontalOptions = LayoutOptions.CenterAndExpand,
+                               IsVisible = false
+                       };
+
+                       Content = new StackLayout
+                       {
+                               Children = {
+                                       infoLabel,
+                                       new Button
+                                       {
+                                               BackgroundColor = Color.Black,
+                                               TextColor = Color.White,
+                                               Text = "START TEST",
+                                               Command = new Command(async () => {
+                                                       var parentChildren = (infoLabel.Parent as StackLayout).Children;
+                                                       parentChildren.Remove(parentChildren[1]);
+                                                       infoLabel.IsVisible = true;
+
+                                                       Register<Test1>();
+                                                       Register<Test2>();
+                                                       Register<Test3>();
+                                                       Register<Test4>();
+                                                       Register<Test5>();
+                                                       Register<Test6>();
+                                                       Register<Test7>();
+                                                       Register<Test8>();
+                                                       Register<Test9>();
+                                                       Register<Test10>();
+
+                                                       Action<int> getAction = (i) => {
+                                                               switch(i % 10 + 1)
+                                                               {
+                                                                       case 1:
+                                                                               DependencyService.Get<ITest1>();
+                                                                               break;
+                                                                       case 2:
+                                                                               DependencyService.Get<ITest2>();
+                                                                               break;
+                                                                       case 3:
+                                                                               DependencyService.Get<ITest3>();
+                                                                               break;
+                                                                       case 4:
+                                                                               DependencyService.Get<ITest4>();
+                                                                               break;
+                                                                       case 5:
+                                                                               DependencyService.Get<ITest5>();
+                                                                               break;
+                                                                       case 6:
+                                                                               DependencyService.Get<ITest6>();
+                                                                               break;
+                                                                       case 7:
+                                                                               DependencyService.Get<ITest7>();
+                                                                               break;
+                                                                       case 8:
+                                                                               DependencyService.Get<ITest8>();
+                                                                               break;
+                                                                       case 9:
+                                                                               DependencyService.Get<ITest9>();
+                                                                               break;
+                                                                       case 10:
+                                                                               DependencyService.Get<ITest10>();
+                                                                               break;
+                                                               }
+                                                       };
+
+                                                       var tasks = Enumerable.Range(0, TasksQuantity).Select(i => new Task(() => getAction(i))).ToArray();
+                                                       try
+                                                       {
+                                                               foreach(var t in tasks)
+                                                               {
+                                                                       t.Start();
+                                                               }
+                                                               await Task.WhenAll(tasks).ConfigureAwait(false);
+                                                       }
+                                                       catch
+                                                       {
+                                                               Device.BeginInvokeOnMainThread(() => infoLabel.Text = $"GOT EXCEPTION! FATAL");
+                                                               return;
+                                                       }
+                                                       Device.BeginInvokeOnMainThread(() => infoLabel.Text = $"TASKS QUANTITY {tasks.Length}\nSUCCESS {tasks.Count(x => !x.IsFaulted)}\nFAILED {tasks.Count(x => x.IsFaulted)}");
+                                               })
+                                       }
+                               }
+                       };
+               }
+
+               public interface ITest1 { }
+               public interface ITest2 { }
+               public interface ITest3 { }
+               public interface ITest4 { }
+               public interface ITest5 { }
+               public interface ITest6 { }
+               public interface ITest7 { }
+               public interface ITest8 { }
+               public interface ITest9 { }
+               public interface ITest10 { }
+
+               [Preserve(AllMembers = true)]
+               public class Test1 : ITest1 { }
+               [Preserve(AllMembers = true)]
+               public class Test2 : ITest2 { }
+               [Preserve(AllMembers = true)]
+               public class Test3 : ITest3 { }
+               [Preserve(AllMembers = true)]
+               public class Test4 : ITest4 { }
+               [Preserve(AllMembers = true)]
+               public class Test5 : ITest5 { }
+               [Preserve(AllMembers = true)]
+               public class Test6 : ITest6 { }
+               [Preserve(AllMembers = true)]
+               public class Test7 : ITest7 { }
+               [Preserve(AllMembers = true)]
+               public class Test8 : ITest8 { }
+               [Preserve(AllMembers = true)]
+               public class Test9 : ITest9 { }
+               [Preserve(AllMembers = true)]
+               public class Test10 : ITest10 { }
+       }
+}
\ No newline at end of file
index 6abf8d3..460b411 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)Issue4040.xaml.cs">
       <DependentUpon>Issue4040.xaml</DependentUpon>
     </Compile>
+    <Compile Include="$(MSBuildThisFileDirectory)Issue4097.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue1480.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue2223.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue4001.cs" />
index 7fbaf22..3a12754 100644 (file)
@@ -10,6 +10,9 @@ namespace Xamarin.Forms
        {
                static bool s_initialized;
 
+               static readonly object s_dependencyLock = new object();
+               static readonly object s_initializeLock = new object();
+
                static readonly List<Type> DependencyTypes = new List<Type>();
                static readonly Dictionary<Type, DependencyData> DependencyImplementations = new Dictionary<Type, DependencyData>();
 
@@ -24,15 +27,18 @@ namespace Xamarin.Forms
                {
                        Initialize();
 
-                       Type targetType = typeof(T);
-
-                       if (!DependencyImplementations.ContainsKey(targetType))
+                       DependencyData dependencyImplementation;
+                       lock (s_dependencyLock)
                        {
-                               Type implementor = FindImplementor(targetType);
-                               DependencyImplementations[targetType] = implementor != null ? new DependencyData { ImplementorType = implementor } : null;
+                               Type targetType = typeof(T);
+                               if (!DependencyImplementations.ContainsKey(targetType))
+                               {
+                                       Type implementor = FindImplementor(targetType);
+                                       DependencyImplementations[targetType] = implementor != null ? new DependencyData { ImplementorType = implementor } : null;
+                               }
+                               dependencyImplementation = DependencyImplementations[targetType];
                        }
 
-                       DependencyData dependencyImplementation = DependencyImplementations[targetType];
                        if (dependencyImplementation == null)
                                return null;
 
@@ -40,7 +46,13 @@ namespace Xamarin.Forms
                        {
                                if (dependencyImplementation.GlobalInstance == null)
                                {
-                                       dependencyImplementation.GlobalInstance = Activator.CreateInstance(dependencyImplementation.ImplementorType);
+                                       lock (dependencyImplementation)
+                                       {
+                                               if (dependencyImplementation.GlobalInstance == null)
+                                               {
+                                                       dependencyImplementation.GlobalInstance = Activator.CreateInstance(dependencyImplementation.ImplementorType);
+                                               }
+                                       }
                                }
                                return (T)dependencyImplementation.GlobalInstance;
                        }
@@ -61,7 +73,8 @@ namespace Xamarin.Forms
                        if (!DependencyTypes.Contains(targetType))
                                DependencyTypes.Add(targetType);
 
-                       DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType };
+                       lock (s_dependencyLock)
+                               DependencyImplementations[targetType] = new DependencyData { ImplementorType = implementorType };
                }
 
                static Type FindImplementor(Type target)
@@ -72,63 +85,71 @@ namespace Xamarin.Forms
                static void Initialize()
                {
                        if (s_initialized)
-                       {
                                return;
-                       }
 
-                       Assembly[] assemblies = Device.GetAssemblies();
-                       if (Internals.Registrar.ExtraAssemblies != null)
+                       lock (s_initializeLock)
                        {
-                               assemblies = assemblies.Union(Internals.Registrar.ExtraAssemblies).ToArray();
-                       }
+                               if (s_initialized)
+                                       return;
 
-                       Initialize(assemblies);
+                               Assembly[] assemblies = Device.GetAssemblies();
+                               if (Internals.Registrar.ExtraAssemblies != null)
+                               {
+                                       assemblies = assemblies.Union(Internals.Registrar.ExtraAssemblies).ToArray();
+                               }
+
+                               Initialize(assemblies);
+                       }
                }
 
                internal static void Initialize(Assembly[] assemblies)
                {
                        if (s_initialized)
-                       {
                                return;
-                       }
-
-                       Type targetAttrType = typeof(DependencyAttribute);
 
-                       // Don't use LINQ for performance reasons
-                       // Naive implementation can easily take over a second to run
-                       foreach (Assembly assembly in assemblies)
+                       lock (s_initializeLock)
                        {
-                               object[] attributes;
-                               try
+                               if (s_initialized)
+                                       return;
+
+                               Type targetAttrType = typeof(DependencyAttribute);
+
+                               // Don't use LINQ for performance reasons
+                               // Naive implementation can easily take over a second to run
+                               foreach (Assembly assembly in assemblies)
                                {
+                                       object[] attributes;
+                                       try
+                                       {
 #if NETSTANDARD2_0
-                                       attributes = assembly.GetCustomAttributes(targetAttrType, true);
+                                               attributes = assembly.GetCustomAttributes(targetAttrType, true);
 #else
-                                       attributes = assembly.GetCustomAttributes(targetAttrType).ToArray();
+                                               attributes = assembly.GetCustomAttributes(targetAttrType).ToArray();
 #endif
-                               }
-                               catch (System.IO.FileNotFoundException)
-                               {
-                                       // Sometimes the previewer doesn't actually have everything required for these loads to work
-                                       Log.Warning(nameof(Registrar), "Could not load assembly: {0} for Attibute {1} | Some renderers may not be loaded", assembly.FullName, targetAttrType.FullName);
-                                       continue;
-                               }
+                                       }
+                                       catch (System.IO.FileNotFoundException)
+                                       {
+                                               // Sometimes the previewer doesn't actually have everything required for these loads to work
+                                               Log.Warning(nameof(Registrar), "Could not load assembly: {0} for Attibute {1} | Some renderers may not be loaded", assembly.FullName, targetAttrType.FullName);
+                                               continue;
+                                       }
 
-                               var length = attributes.Length;
-                               if (length == 0)
-                                       continue;
+                                       var length = attributes.Length;
+                                       if (length == 0)
+                                               continue;
 
-                               for (int i = 0; i < length; i++)
-                               {
-                                       DependencyAttribute attribute = (DependencyAttribute)attributes[i];
-                                       if (!DependencyTypes.Contains(attribute.Implementor))
+                                       for (int i = 0; i < length; i++)
                                        {
-                                               DependencyTypes.Add(attribute.Implementor);
+                                               DependencyAttribute attribute = (DependencyAttribute)attributes[i];
+                                               if (!DependencyTypes.Contains(attribute.Implementor))
+                                               {
+                                                       DependencyTypes.Add(attribute.Implementor);
+                                               }
                                        }
                                }
-                       }
 
-                       s_initialized = true;
+                               s_initialized = true;
+                       }
                }
 
                class DependencyData