From 9ac75d4641bb7b58075c9d6fc8ac92dbb32a4fbf Mon Sep 17 00:00:00 2001 From: Lluis Sanchez Date: Thu, 16 Jun 2022 12:51:43 +0200 Subject: Add multi-threading tests And fixed threading issue. --- Mono.Addins/Mono.Addins/TreeNode.cs | 13 ++- Test/UnitTests/TestMultithreading.cs | 195 +++++++++++++++++++++++++++++++++++ Test/UnitTests/UnitTests.csproj | 1 + 3 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 Test/UnitTests/TestMultithreading.cs diff --git a/Mono.Addins/Mono.Addins/TreeNode.cs b/Mono.Addins/Mono.Addins/TreeNode.cs index 3a79e49..1c60176 100644 --- a/Mono.Addins/Mono.Addins/TreeNode.cs +++ b/Mono.Addins/Mono.Addins/TreeNode.cs @@ -33,6 +33,7 @@ using System.Collections; using Mono.Addins.Description; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading; namespace Mono.Addins { @@ -70,7 +71,11 @@ namespace Mono.Addins internal void AttachExtensionNode (ExtensionNode enode) { - this.extensionNode = enode; + if (Interlocked.CompareExchange (ref extensionNode, enode, null) != null) { + // Another thread already assigned the node + return; + } + if (extensionNode != null) extensionNode.SetTreeNode (this); } @@ -82,9 +87,9 @@ namespace Mono.Addins public ExtensionNode ExtensionNode { get { if (extensionNode == null && extensionPoint != null) { - extensionNode = new ExtensionNode (); - extensionNode.SetData (addinEngine, extensionPoint.RootAddin, null, null); - AttachExtensionNode (extensionNode); + var newNode = new ExtensionNode (); + newNode.SetData (addinEngine, extensionPoint.RootAddin, null, null); + AttachExtensionNode (newNode); } return extensionNode; } diff --git a/Test/UnitTests/TestMultithreading.cs b/Test/UnitTests/TestMultithreading.cs new file mode 100644 index 0000000..fd3cbcf --- /dev/null +++ b/Test/UnitTests/TestMultithreading.cs @@ -0,0 +1,195 @@ + +using System; +using System.IO; +using NUnit.Framework; +using Mono.Addins; +using SimpleApp; +using System.Threading; +using System.Diagnostics; +using System.Linq; + +namespace UnitTests +{ + [TestFixture ()] + public class TestMultiThreading: TestBase + { + public override void Setup () + { + base.Setup (); + GlobalInfoCondition.Value = "res"; + } + + [Test] + public void ChildConditionAttributeMultithread () + { + int threads = 50; + using var testData = new TestData(threads); + + GlobalInfoCondition.Value = ""; + + testData.StartThreads (RunChildConditionAttributeTest); + + for (int n = 0; n < 20; n++) { + Console.WriteLine ("Step " + n); + testData.CheckCounters (0, 10000); + + GlobalInfoCondition.Value = "foo"; + + testData.CheckCounters (1, 1000); + + GlobalInfoCondition.Value = ""; + } + } + + void RunChildConditionAttributeTest (int index, TestData data) + { + while (!data.Stopped) { + var writers = AddinManager.GetExtensionObjects ("/SimpleApp/ConditionedWriters"); + data.Counters [index] = writers.Length; + } + } + + [Test] + public void EnableDisableMultithread () + { + int threads = 50; + int steps = 20; + + using var testData = new TestData (threads); + + testData.StartThreads ((index, data) => { + while (!data.Stopped) { + var writers = AddinManager.GetExtensionObjects ("/SimpleApp/Writers"); + testData.Counters [index] = writers.Length; + } + }); + + for (int n = 0; n < steps; n++) { + Console.WriteLine ("Step " + n); + + testData.CheckCounters (4, 10000); + + var ainfo1 = AddinManager.Registry.GetAddin ("SimpleApp.HelloWorldExtension"); + ainfo1.Enabled = false; + + testData.CheckCounters (3, 1000); + + var ainfo2 = AddinManager.Registry.GetAddin ("SimpleApp.FileContentExtension"); + ainfo2.Enabled = false; + + testData.CheckCounters (2, 1000); + + ainfo1.Enabled = true; + ainfo2.Enabled = true; + } + } + + [Test] + public void EventsMultithread () + { + int threads = 50; + int steps = 20; + + var testData = new TestData (threads); + + GlobalInfoCondition.Value = ""; + + testData.StartThreads (RunEventsMultithread); + + for (int n = 0; n < steps; n++) { + Console.WriteLine ("Step " + n); + testData.CheckCounters (0, 10000); + + GlobalInfoCondition.Value = "foo"; + + testData.CheckCounters (1, 1000); + + GlobalInfoCondition.Value = ""; + } + } + + void RunEventsMultithread (int index, TestData data) + { + while (!data.Stopped) { + int count = 0; + ExtensionNodeEventHandler handler = (s, args) => { + count++; + }; + AddinManager.AddExtensionNodeHandler("/SimpleApp/ConditionedWriters", handler); + data.Counters [index] = count; + AddinManager.RemoveExtensionNodeHandler ("/SimpleApp/ConditionedWriters", handler); + } + } + + [Test] + public void ChildConditionWithContextMultithread () + { + int threads = 50; + using var testData = new TestData (threads); + + GlobalInfoCondition.Value = ""; + + testData.StartThreads (RunChildConditionWithContextMultithread); + + for (int n = 0; n < 20; n++) { + Console.WriteLine ("Step " + n); + testData.CheckCounters (0, 10000); + + GlobalInfoCondition.Value = "foo"; + + testData.CheckCounters (1, 1000); + + GlobalInfoCondition.Value = ""; + } + } + + void RunChildConditionWithContextMultithread (int index, TestData data) + { + var ctx = AddinManager.CreateExtensionContext (); + var writers = ctx.GetExtensionNode ("/SimpleApp/ConditionedWriters"); + while (!data.Stopped) { + data.Counters [index] = writers.ChildNodes.Count; + } + } + class TestData: IDisposable + { + int numThreads; + + public bool Stopped; + public int [] Counters; + + public TestData (int numThreads) + { + this.numThreads = numThreads; + Counters = new int [numThreads]; + } + + public void CheckCounters (int expectedResult, int timeout) + { + var sw = Stopwatch.StartNew (); + do { + if (Counters.All (c => c == expectedResult)) + return; + Thread.Sleep (10); + } while (sw.ElapsedMilliseconds < timeout); + + Assert.Fail (); + } + + public void Dispose () + { + Stopped = true; + } + + public void StartThreads (Action threadAction) + { + for (int n = 0; n < numThreads; n++) { + Counters [n] = -1; + var index = n; + var t = new Thread (() => threadAction(index, this)); + t.Start (); + } + } + } + } +} diff --git a/Test/UnitTests/UnitTests.csproj b/Test/UnitTests/UnitTests.csproj index 28d5659..aae8ac1 100644 --- a/Test/UnitTests/UnitTests.csproj +++ b/Test/UnitTests/UnitTests.csproj @@ -13,6 +13,7 @@ True ..\..\mono-addins.snk net472;net6.0 + 9.0 True -- cgit v1.2.3