Test concurrent calls to CallSite.AddRule() and MoveRule() (#40858)
authorCharles Stoner <chucks@microsoft.com>
Mon, 17 Aug 2020 19:59:34 +0000 (12:59 -0700)
committerGitHub <noreply@github.com>
Mon, 17 Aug 2020 19:59:34 +0000 (12:59 -0700)
src/libraries/System.Linq.Expressions/tests/Dynamic/CallSiteCachingTests.cs

index 2215443..cc829df 100644 (file)
@@ -2,8 +2,12 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Dynamic;
+using System.Linq;
 using System.Linq.Expressions;
+using System.Threading;
+using System.Threading.Tasks;
 using Microsoft.CSharp.RuntimeBinder;
 using Xunit;
 
@@ -218,5 +222,63 @@ namespace System.Runtime.CompilerServices.Tests
             Assert.NotEqual(rules1, rules3);
 
         }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+        public void ConcurrentAdds()
+        {
+            for (int i = 0; i < 10; i++)
+            {
+                ExecuteConcurrentAdds(i);
+            }
+        }
+
+        private sealed class TestCallSiteBinder : CallSiteBinder
+        {
+            public override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel) => throw new NotImplementedException();
+        }
+
+        private static void ExecuteConcurrentAdds(int run)
+        {
+            // Invoke CallSiteOps methods through reflection to avoid obsolete errors.
+            var addRuleMethod = GetCallSiteOpsMethod("AddRule");
+            var updateRulesMethod = GetCallSiteOpsMethod("UpdateRules");
+
+            var binder = new TestCallSiteBinder();
+            var callSite = CallSite<Func<CallSite, int>>.Create(binder);
+
+            const int nTasks = 5;
+            int nOperations = 0;
+            int nRules = 0;
+            var tasks = Enumerable.Range(0, nTasks).Select(i => Task.Factory.StartNew(
+                () => AddAndUpdateRules(run),
+                cancellationToken: default,
+                creationOptions: default,
+                scheduler: TaskScheduler.Default)).ToArray();
+            Task.WaitAll(tasks);
+
+            void AddAndUpdateRules(int run)
+            {
+                Thread.Sleep(10);
+                while (true)
+                {
+                    int op = Interlocked.Increment(ref nOperations);
+                    if (op > 100) break;
+                    if (op % 10 == 0)
+                    {
+                        AddRule(callSite, callSite => op);
+                        Interlocked.Increment(ref nRules);
+                    }
+                    UpdateRules(callSite, nRules - 1);
+                }
+            }
+
+            static System.Reflection.MethodInfo GetCallSiteOpsMethod(string methodName)
+            {
+                return typeof(CallSiteOps).GetMethod(methodName).MakeGenericMethod(typeof(Func<CallSite, int>));
+            }
+
+            void AddRule(CallSite<Func<CallSite, int>> callSite, Func<CallSite, int> rule) => addRuleMethod.Invoke(null, new object[] { callSite, rule });
+            void UpdateRules(CallSite<Func<CallSite, int>> callSite, int index) => updateRulesMethod.Invoke(null, new object[] { callSite, index });
+        }
     }
 }