From fcd6b0324ceaad72978fc95d7df1ef70c492d658 Mon Sep 17 00:00:00 2001 From: Lluis Sanchez Date: Wed, 14 Sep 2022 16:45:28 +0200 Subject: Fix threading issue Split the context transaction class in two classes, one for extension context and one for add-in engine (which is a context by itself). In this was there is no danger of providing a context transaction to an add-in engine method and expect it to work. Added method for stating an engine transaction from a context transaction. Fixes tests. --- Mono.Addins/Mono.Addins.Database/AddinDatabase.cs | 16 +- Mono.Addins/Mono.Addins/AddinEngine.cs | 59 +++--- Mono.Addins/Mono.Addins/AddinRegistry.cs | 2 +- Mono.Addins/Mono.Addins/ConditionType.cs | 2 +- Mono.Addins/Mono.Addins/ExtensionContext.cs | 8 +- .../Mono.Addins/ExtensionContextTransaction.cs | 230 +++++++++++++-------- Mono.Addins/Mono.Addins/ExtensionTree.cs | 7 +- Mono.Addins/Mono.Addins/TreeNode.cs | 2 +- Test/UnitTests/TestMultithreading.cs | 121 +++++------ Test/UnitTests/UnitTests.csproj | 1 + 10 files changed, 257 insertions(+), 191 deletions(-) diff --git a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs index 73cb55c..842c12b 100644 --- a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs +++ b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs @@ -75,7 +75,7 @@ namespace Mono.Addins.Database fileDatabase = new FileDatabase (AddinDbDir); } - public AddinDatabaseTransaction BeginTransaction (ExtensionContextTransaction addinEngineTransaction = null) + public AddinDatabaseTransaction BeginTransaction (AddinEngineTransaction addinEngineTransaction = null) { return new AddinDatabaseTransaction (this, localLock, addinEngineTransaction); } @@ -1096,7 +1096,7 @@ namespace Mono.Addins.Database Update (monitor, domain, context); } - public void Update(IProgressStatus monitor, string domain, ScanOptions context = null, ExtensionContextTransaction addinEngineTransaction = null) + public void Update(IProgressStatus monitor, string domain, ScanOptions context = null, AddinEngineTransaction addinEngineTransaction = null) { if (monitor == null) monitor = new ConsoleProgressStatus(false); @@ -1108,7 +1108,7 @@ namespace Mono.Addins.Database DateTime tim = DateTime.Now; - var dbTransaction = BeginTransaction(addinEngineTransaction); + using var dbTransaction = BeginTransaction(addinEngineTransaction); RunPendingUninstalls(dbTransaction, monitor); @@ -2020,10 +2020,10 @@ namespace Mono.Addins.Database { readonly AddinDatabase addinDatabase; readonly object localLock; - ExtensionContextTransaction addinEngineTransaction; + AddinEngineTransaction addinEngineTransaction; bool addinEngineTransactionStarted; - public AddinDatabaseTransaction (AddinDatabase addinDatabase, object localLock, ExtensionContextTransaction addinEngineTransaction) + public AddinDatabaseTransaction (AddinDatabase addinDatabase, object localLock, AddinEngineTransaction addinEngineTransaction) { this.addinDatabase = addinDatabase; this.localLock = localLock; @@ -2031,19 +2031,19 @@ namespace Mono.Addins.Database Monitor.Enter (localLock); } - public ExtensionContextTransaction GetAddinEngineTransaction() + public AddinEngineTransaction GetAddinEngineTransaction() { if (addinEngineTransaction != null) return addinEngineTransaction; addinEngineTransactionStarted = true; - return addinEngineTransaction = addinDatabase.AddinEngine.BeginTransaction(); + return addinEngineTransaction = addinDatabase.AddinEngine.BeginEngineTransaction(); } public void Dispose () { + Monitor.Exit(localLock); if (addinEngineTransactionStarted) addinEngineTransaction.Dispose(); - Monitor.Exit (localLock); } } diff --git a/Mono.Addins/Mono.Addins/AddinEngine.cs b/Mono.Addins/Mono.Addins/AddinEngine.cs index 507d973..3bfe794 100644 --- a/Mono.Addins/Mono.Addins/AddinEngine.cs +++ b/Mono.Addins/Mono.Addins/AddinEngine.cs @@ -130,6 +130,16 @@ namespace Mono.Addins base.SetSnapshot(newSnapshot); } + internal override ExtensionContextTransaction BeginTransaction() + { + return new AddinEngineTransaction(this); + } + + internal AddinEngineTransaction BeginEngineTransaction() + { + return (AddinEngineTransaction)BeginTransaction(); + } + /// /// Initializes the add-in engine /// @@ -284,7 +294,7 @@ namespace Mono.Addins else registry = new AddinRegistry (this, configDir, startupDirectory, addinsDir, databaseDir, null); - using var transaction = BeginTransaction(); + using var transaction = BeginEngineTransaction(); if ((asmFile != null && registry.CreateHostAddinsFile (asmFile)) || registry.UnknownDomain) registry.Update (new ConsoleProgressStatus (false), transaction); @@ -495,14 +505,17 @@ namespace Mono.Addins /// public bool IsAddinLoaded (string id) { - return IsAddinLoaded(currentSnapshot, id); + return IsAddinLoaded(null, id); } - bool IsAddinLoaded(AddinEngineSnapshot snapshot, string id) + bool IsAddinLoaded(AddinEngineTransaction transaction, string id) { CheckInitialized(); ValidateAddinRoots(); - return snapshot.LoadedAddins.ContainsKey(Addin.GetIdName(id)); + if (transaction?.Context == this) + return transaction.AddinEngineSnapshot.LoadedAddins.ContainsKey(Addin.GetIdName(id)); + else + return currentSnapshot.LoadedAddins.ContainsKey(Addin.GetIdName(id)); } internal RuntimeAddin GetAddin (string id) @@ -513,7 +526,7 @@ namespace Mono.Addins return a; } - internal RuntimeAddin GetAddin(ExtensionContextTransaction transaction, string id) + internal RuntimeAddin GetAddin(AddinEngineTransaction transaction, string id) { ValidateAddinRoots(); RuntimeAddin a; @@ -521,7 +534,7 @@ namespace Mono.Addins // If a transaction is provided, try using the snapshot from that transaction, // but this is only possible if the transaction was created for this context - if (transaction.Context == this) + if (transaction?.Context == this) transaction.AddinEngineSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a); else currentSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a); @@ -535,7 +548,7 @@ namespace Mono.Addins currentSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a); if (a == null) { - using var tr = BeginTransaction(); + using var tr = BeginEngineTransaction(); LoadAddin(tr, null, id, throwExceptions); tr.AddinEngineSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a); } @@ -547,7 +560,7 @@ namespace Mono.Addins ActivateAddinExtensions (transaction, id); } - internal void UnloadAddin (ExtensionContextTransaction transaction, string id) + internal void UnloadAddin (AddinEngineTransaction transaction, string id) { RemoveAddinExtensions (transaction, id); @@ -580,17 +593,17 @@ namespace Mono.Addins public void LoadAddin (IProgressStatus statusMonitor, string id) { CheckInitialized (); - using var transaction = BeginTransaction(); + using var transaction = BeginEngineTransaction(); if (LoadAddin (transaction, statusMonitor, id, true)) { var adn = GetAddin (transaction, id); adn.EnsureAssembliesLoaded (); } } - internal bool LoadAddin (ExtensionContextTransaction transaction, IProgressStatus statusMonitor, string id, bool throwExceptions) + internal bool LoadAddin (AddinEngineTransaction transaction, IProgressStatus statusMonitor, string id, bool throwExceptions) { try { - if (IsAddinLoaded (transaction.AddinEngineSnapshot, id)) + if (IsAddinLoaded (transaction, id)) return true; if (!Registry.IsAddinEnabled (id)) { @@ -616,7 +629,7 @@ namespace Mono.Addins statusMonitor.SetProgress ((double)n / (double)addins.Count); Addin iad = addins [n]; - if (IsAddinLoaded (transaction.AddinEngineSnapshot, iad.Id)) + if (IsAddinLoaded (transaction, iad.Id)) continue; if (statusMonitor != null) @@ -645,7 +658,7 @@ namespace Mono.Addins base.OnResetCachedData (transaction); } - bool InsertAddin (ExtensionContextTransaction transaction, IProgressStatus statusMonitor, Addin iad) + bool InsertAddin (AddinEngineTransaction transaction, IProgressStatus statusMonitor, Addin iad) { try { RuntimeAddin runtimeAddin = new RuntimeAddin (this, iad); @@ -680,7 +693,7 @@ namespace Mono.Addins } } - void RegisterAssemblyResolvePaths (ExtensionContextTransaction transaction, RuntimeAddin addin, ModuleDescription description) + void RegisterAssemblyResolvePaths (AddinEngineTransaction transaction, RuntimeAddin addin, ModuleDescription description) { foreach (var asm in description.AssemblyNames) { //Debug.Assert(assemblyResolvePaths[asm] == addin); This does not look right. Not called in old project since DEBUG symbol is not defined. @@ -688,7 +701,7 @@ namespace Mono.Addins } } - internal void InsertExtensionPoint (ExtensionContextTransaction transaction, RuntimeAddin addin, ExtensionPoint ep) + internal void InsertExtensionPoint (AddinEngineTransaction transaction, RuntimeAddin addin, ExtensionPoint ep) { CreateExtensionPoint (transaction, ep); foreach (ExtensionNodeType nt in ep.NodeSet.NodeTypes) { @@ -698,9 +711,9 @@ namespace Mono.Addins } } - bool ResolveLoadDependencies (ExtensionContextTransaction transaction, List addins, Stack depCheck, string id, bool optional) + bool ResolveLoadDependencies (AddinEngineTransaction transaction, List addins, Stack depCheck, string id, bool optional) { - if (IsAddinLoaded (transaction.AddinEngineSnapshot, id)) + if (IsAddinLoaded (transaction, id)) return true; if (depCheck.Contains (id)) @@ -798,14 +811,14 @@ namespace Mono.Addins return null; } - internal void RegisterAutoTypeExtensionPoint (ExtensionContextTransaction transaction, string typeName, string path) + internal void RegisterAutoTypeExtensionPoint (AddinEngineTransaction transaction, string typeName, string path) { if (Util.TryParseTypeName (typeName, out var t, out var _)) typeName = t; transaction.RegisterAutoTypeExtensionPoint (typeName, path); } - internal void UnregisterAutoTypeExtensionPoint (ExtensionContextTransaction transaction, string typeName, string path) + internal void UnregisterAutoTypeExtensionPoint (AddinEngineTransaction transaction, string typeName, string path) { if (Util.TryParseTypeName (typeName, out var t, out var _)) typeName = t; @@ -842,13 +855,13 @@ namespace Mono.Addins } } if (copy != null) { - using var tr = BeginTransaction(); + using var tr = BeginEngineTransaction(); foreach (Assembly asm in copy) CheckHostAssembly (tr, asm); } } - internal void ActivateRoots (ExtensionContextTransaction transaction) + internal void ActivateRoots (AddinEngineTransaction transaction) { lock (pendingRootChecks) pendingRootChecks.Clear (); @@ -857,7 +870,7 @@ namespace Mono.Addins CheckHostAssembly (transaction, asm); } - void CheckHostAssembly (ExtensionContextTransaction transaction, Assembly asm) + void CheckHostAssembly (AddinEngineTransaction transaction, Assembly asm) { if (AddinDatabase.RunningSetupProcess || asm is System.Reflection.Emit.AssemblyBuilder || asm.IsDynamic) return; @@ -884,7 +897,7 @@ namespace Mono.Addins ainfo = Registry.GetAddinForHostAssembly (asmFile); } - if (ainfo != null && !IsAddinLoaded (transaction.AddinEngineSnapshot, ainfo.Id)) { + if (ainfo != null && !IsAddinLoaded (transaction, ainfo.Id)) { AddinDescription adesc = null; try { adesc = ainfo.Description; diff --git a/Mono.Addins/Mono.Addins/AddinRegistry.cs b/Mono.Addins/Mono.Addins/AddinRegistry.cs index 1346883..944d96e 100644 --- a/Mono.Addins/Mono.Addins/AddinRegistry.cs +++ b/Mono.Addins/Mono.Addins/AddinRegistry.cs @@ -639,7 +639,7 @@ namespace Mono.Addins database.Update (monitor, currentDomain); } - internal void Update(IProgressStatus monitor, ExtensionContextTransaction addinEngineTransaction) + internal void Update(IProgressStatus monitor, AddinEngineTransaction addinEngineTransaction) { database.Update(monitor, currentDomain, addinEngineTransaction:addinEngineTransaction); } diff --git a/Mono.Addins/Mono.Addins/ConditionType.cs b/Mono.Addins/Mono.Addins/ConditionType.cs index d5bf6f6..c5937f6 100644 --- a/Mono.Addins/Mono.Addins/ConditionType.cs +++ b/Mono.Addins/Mono.Addins/ConditionType.cs @@ -210,7 +210,7 @@ namespace Mono.Addins if (!string.IsNullOrEmpty (addin)) { // Make sure the add-in that implements the condition is loaded - using var tr = addinEngine.BeginTransaction(); + using var tr = addinEngine.BeginEngineTransaction(); addinEngine.LoadAddin (tr, null, addin, true); addin = null; // Don't try again } diff --git a/Mono.Addins/Mono.Addins/ExtensionContext.cs b/Mono.Addins/Mono.Addins/ExtensionContext.cs index bb9c478..7d03a1e 100644 --- a/Mono.Addins/Mono.Addins/ExtensionContext.cs +++ b/Mono.Addins/Mono.Addins/ExtensionContext.cs @@ -809,7 +809,7 @@ namespace Mono.Addins RemoveExtensionNodeHandler (path, handler); } - internal ExtensionContextTransaction BeginTransaction () + internal virtual ExtensionContextTransaction BeginTransaction () { return new ExtensionContextTransaction (this); } @@ -957,7 +957,7 @@ namespace Mono.Addins // event without first getting the list of nodes that may change). // We get the runtime add-in because the add-in may already have been deleted from the registry - RuntimeAddin addin = AddinEngine.GetAddin (transaction, id); + RuntimeAddin addin = AddinEngine.GetAddin (transaction.GetAddinEngineTransaction(), id); if (addin != null) { var paths = new List (); // Using addin.Module.ParentAddinDescription here because addin.Addin.Description may not @@ -1076,7 +1076,7 @@ namespace Mono.Addins Addin pinfo = null; // Root add-ins are not returned by GetInstalledAddin. - RuntimeAddin addin = AddinEngine.GetAddin (transaction, id); + RuntimeAddin addin = AddinEngine.GetAddin (transaction.GetAddinEngineTransaction(), id); if (addin != null) pinfo = addin.Addin; else @@ -1128,7 +1128,7 @@ namespace Mono.Addins var addedNodes = new List (); tree.LoadExtension (transaction, node, addinId, extension, addedNodes); - RuntimeAddin ad = AddinEngine.GetAddin (transaction, addinId); + RuntimeAddin ad = AddinEngine.GetAddin (transaction.GetAddinEngineTransaction(), addinId); if (ad != null) { foreach (TreeNode nod in addedNodes) { // Don't call OnAddinLoaded here. Do it when the entire extension point has been loaded. diff --git a/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs b/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs index 62b8d52..ba2a245 100644 --- a/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs +++ b/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs @@ -55,15 +55,10 @@ namespace Mono.Addins HashSet extensionsChanged; List<(TreeNode Node, BaseCondition Condition)> nodeConditions; List<(TreeNode Node, BaseCondition Condition)> nodeConditionUnregistrations; - List> registeredAutoExtensionPoints; - List unregisteredAutoExtensionPoints; - List> registeredAssemblyResolvePaths; - List unregisteredAssemblyResolvePaths; - List addinLoadEvents; - List addinUnloadEvents; List treeNodeTransactions; ExtensionContextSnapshot snapshot; bool snaphotChanged; + AddinEngineTransaction nestedAddinEngineTransaction; public ExtensionContextTransaction (ExtensionContext context) { @@ -80,7 +75,32 @@ namespace Mono.Addins public ExtensionContextSnapshot Snapshot => snapshot; - void EnsureNewSnapshot() + /// + /// Gets an add-in engine transaction, if there is one in progress. Returns null if there isn't one. + /// + public AddinEngineTransaction GetAddinEngineTransaction() + { + if (this is AddinEngineTransaction et) + return et; + if (nestedAddinEngineTransaction != null) + return nestedAddinEngineTransaction; + return null; + } + + /// + /// Gets or creates an add-in engine transaction, which will be committed together with this transaction + /// + public AddinEngineTransaction GetOrCreateAddinEngineTransaction() + { + if (this is AddinEngineTransaction et) + return et; + if (nestedAddinEngineTransaction != null) + return nestedAddinEngineTransaction; + + return nestedAddinEngineTransaction = Context.AddinEngine.BeginEngineTransaction(); + } + + protected void EnsureNewSnapshot() { if (!snaphotChanged) { @@ -93,41 +113,11 @@ namespace Mono.Addins public void Dispose () { - var engine = Context as AddinEngine; - try { // Update the context - if (nodeConditions != null) { - BulkRegisterNodeConditions (nodeConditions); - } - - if (nodeConditionUnregistrations != null) { - BulkUnregisterNodeConditions (nodeConditionUnregistrations); - } - - if (engine != null) - { - if (registeredAutoExtensionPoints != null) - AddinEngineSnapshot.AutoExtensionTypes = AddinEngineSnapshot.AutoExtensionTypes.SetItems(registeredAutoExtensionPoints); - - if (unregisteredAutoExtensionPoints != null) - AddinEngineSnapshot.AutoExtensionTypes = AddinEngineSnapshot.AutoExtensionTypes.RemoveRange(unregisteredAutoExtensionPoints); - - if (registeredAssemblyResolvePaths != null) - AddinEngineSnapshot.AssemblyResolvePaths = AddinEngineSnapshot.AssemblyResolvePaths.SetItems(registeredAssemblyResolvePaths); - - if (unregisteredAssemblyResolvePaths != null) - AddinEngineSnapshot.AssemblyResolvePaths = AddinEngineSnapshot.AssemblyResolvePaths.RemoveRange(unregisteredAssemblyResolvePaths); - } - - // Commit tree node transactions - if (treeNodeTransactions != null) - { - foreach (var node in treeNodeTransactions) - node.CommitChildrenUpdateTransaction(); - } + UpdateSnapshot(); if (snaphotChanged) Context.SetSnapshot(snapshot); @@ -136,33 +126,54 @@ namespace Mono.Addins Monitor.Exit (Context.LocalLock); } + // If there is a nested engine transaction, make sure it is disposed + using var _ = nestedAddinEngineTransaction; + // Do notifications outside the lock - if (loadedNodes != null) { - foreach (var node in loadedNodes) - node.NotifyAddinLoaded (); + DispatchNotifications(); + } + + protected virtual void UpdateSnapshot() + { + if (nodeConditions != null) + { + BulkRegisterNodeConditions(nodeConditions); } - if (childrenChanged != null) { - foreach (var node in childrenChanged) { - if (node.NotifyChildrenChanged ()) - NotifyExtensionsChangedEvent (node.GetPath ()); - } + + if (nodeConditionUnregistrations != null) + { + BulkUnregisterNodeConditions(nodeConditionUnregistrations); } - if (extensionsChanged != null) { - foreach (var path in extensionsChanged) - Context.NotifyExtensionsChanged (new ExtensionEventArgs (path)); + + // Commit tree node transactions + if (treeNodeTransactions != null) + { + foreach (var node in treeNodeTransactions) + node.CommitChildrenUpdateTransaction(); } + } - if (engine != null) { - if (addinLoadEvents != null) { - foreach (var addin in addinLoadEvents) - engine.ReportAddinLoad (addin); - } - if (addinUnloadEvents != null) { - foreach (var id in addinUnloadEvents) - engine.ReportAddinUnload (id); + protected virtual void DispatchNotifications() + { + if (loadedNodes != null) + { + foreach (var node in loadedNodes) + node.NotifyAddinLoaded(); + } + if (childrenChanged != null) + { + foreach (var node in childrenChanged) + { + if (node.NotifyChildrenChanged()) + NotifyExtensionsChangedEvent(node.GetPath()); } } + if (extensionsChanged != null) + { + foreach (var path in extensionsChanged) + Context.NotifyExtensionsChanged(new ExtensionEventArgs(path)); + } } void BulkRegisterNodeConditions(IEnumerable<(TreeNode Node, BaseCondition Condition)> nodeConditions) @@ -292,58 +303,107 @@ namespace Mono.Addins nodeConditions.Remove ((node, cond)); } - public void RegisterAutoTypeExtensionPoint (string typeName, string path) + public void RegisterChildrenUpdateTransaction (TreeNode node) { - EnsureNewSnapshot(); - if (registeredAutoExtensionPoints == null) - registeredAutoExtensionPoints = new List> (); - registeredAutoExtensionPoints.Add (new KeyValuePair (typeName, path)); + if (treeNodeTransactions == null) + treeNodeTransactions = new List (); + treeNodeTransactions.Add (node); } - public void UnregisterAutoTypeExtensionPoint (string typeName) - { - EnsureNewSnapshot(); - if (unregisteredAutoExtensionPoints == null) - unregisteredAutoExtensionPoints = new List (); - unregisteredAutoExtensionPoints.Add (typeName); - } + } + + class AddinEngineTransaction : ExtensionContextTransaction + { + List> registeredAutoExtensionPoints; + List unregisteredAutoExtensionPoints; + List> registeredAssemblyResolvePaths; + List unregisteredAssemblyResolvePaths; + List addinLoadEvents; + List addinUnloadEvents; + + public AddinEngineTransaction (AddinEngine engine) : base (engine) + { + } - public void RegisterAssemblyResolvePaths (string assembly, RuntimeAddin addin) + public void RegisterAssemblyResolvePaths(string assembly, RuntimeAddin addin) { EnsureNewSnapshot(); if (registeredAssemblyResolvePaths == null) - registeredAssemblyResolvePaths = new List> (); - registeredAssemblyResolvePaths.Add (new KeyValuePair (assembly, addin)); + registeredAssemblyResolvePaths = new List>(); + registeredAssemblyResolvePaths.Add(new KeyValuePair(assembly, addin)); } - public void UnregisterAssemblyResolvePaths (string assembly) + public void UnregisterAssemblyResolvePaths(string assembly) { EnsureNewSnapshot(); if (unregisteredAssemblyResolvePaths == null) - unregisteredAssemblyResolvePaths = new List (); - unregisteredAssemblyResolvePaths.Add (assembly); + unregisteredAssemblyResolvePaths = new List(); + unregisteredAssemblyResolvePaths.Add(assembly); } - public void ReportAddinLoad (RuntimeAddin addin) + public void ReportAddinLoad(RuntimeAddin addin) { if (addinLoadEvents == null) - addinLoadEvents = new List (); - addinLoadEvents.Add (addin); + addinLoadEvents = new List(); + addinLoadEvents.Add(addin); } - public void ReportAddinUnload (string id) + public void ReportAddinUnload(string id) { if (addinUnloadEvents == null) - addinUnloadEvents = new List (); - addinUnloadEvents.Add (id); + addinUnloadEvents = new List(); + addinUnloadEvents.Add(id); } - public void RegisterChildrenUpdateTransaction (TreeNode node) + public void RegisterAutoTypeExtensionPoint(string typeName, string path) { - if (treeNodeTransactions == null) - treeNodeTransactions = new List (); - treeNodeTransactions.Add (node); + EnsureNewSnapshot(); + if (registeredAutoExtensionPoints == null) + registeredAutoExtensionPoints = new List>(); + registeredAutoExtensionPoints.Add(new KeyValuePair(typeName, path)); + } + + public void UnregisterAutoTypeExtensionPoint(string typeName) + { + EnsureNewSnapshot(); + if (unregisteredAutoExtensionPoints == null) + unregisteredAutoExtensionPoints = new List(); + unregisteredAutoExtensionPoints.Add(typeName); } + protected override void UpdateSnapshot() + { + base.UpdateSnapshot(); + + if (registeredAutoExtensionPoints != null) + AddinEngineSnapshot.AutoExtensionTypes = AddinEngineSnapshot.AutoExtensionTypes.SetItems(registeredAutoExtensionPoints); + + if (unregisteredAutoExtensionPoints != null) + AddinEngineSnapshot.AutoExtensionTypes = AddinEngineSnapshot.AutoExtensionTypes.RemoveRange(unregisteredAutoExtensionPoints); + + if (registeredAssemblyResolvePaths != null) + AddinEngineSnapshot.AssemblyResolvePaths = AddinEngineSnapshot.AssemblyResolvePaths.SetItems(registeredAssemblyResolvePaths); + + if (unregisteredAssemblyResolvePaths != null) + AddinEngineSnapshot.AssemblyResolvePaths = AddinEngineSnapshot.AssemblyResolvePaths.RemoveRange(unregisteredAssemblyResolvePaths); + } + + protected override void DispatchNotifications() + { + base.DispatchNotifications(); + + var engine = (AddinEngine)Context; + + if (addinLoadEvents != null) + { + foreach (var addin in addinLoadEvents) + engine.ReportAddinLoad(addin); + } + if (addinUnloadEvents != null) + { + foreach (var id in addinUnloadEvents) + engine.ReportAddinUnload(id); + } + } } } diff --git a/Mono.Addins/Mono.Addins/ExtensionTree.cs b/Mono.Addins/Mono.Addins/ExtensionTree.cs index 72f065d..117eafe 100644 --- a/Mono.Addins/Mono.Addins/ExtensionTree.cs +++ b/Mono.Addins/Mono.Addins/ExtensionTree.cs @@ -201,15 +201,16 @@ namespace Mono.Addins bool InitializeNodeType (ExtensionNodeType ntype, ExtensionContextTransaction transaction) { - RuntimeAddin p = addinEngine.GetAddin(transaction, ntype.AddinId); + RuntimeAddin p = addinEngine.GetAddin(transaction.GetAddinEngineTransaction(), ntype.AddinId); if (p == null) { - if (!addinEngine.LoadAddin(transaction, null, ntype.AddinId, false)) + var engineTransaction = transaction.GetOrCreateAddinEngineTransaction(); + if (!addinEngine.LoadAddin(engineTransaction, null, ntype.AddinId, false)) { addinEngine.ReportError("Add-in not found", ntype.AddinId, null, false); return false; } - p = addinEngine.GetAddin(transaction, ntype.AddinId); + p = addinEngine.GetAddin(engineTransaction, ntype.AddinId); } // If no type name is provided, use TypeExtensionNode by default diff --git a/Mono.Addins/Mono.Addins/TreeNode.cs b/Mono.Addins/Mono.Addins/TreeNode.cs index 0c0d992..6beab23 100644 --- a/Mono.Addins/Mono.Addins/TreeNode.cs +++ b/Mono.Addins/Mono.Addins/TreeNode.cs @@ -512,7 +512,7 @@ namespace Mono.Addins { if (extensionPoint != null) { string aid = Addin.GetIdName (extensionPoint.ParentAddinDescription.AddinId); - RuntimeAddin ad = addinEngine.GetAddin (transaction, aid); + RuntimeAddin ad = addinEngine.GetAddin (transaction.GetAddinEngineTransaction(), aid); if (ad != null) extensionPoint = ad.Addin.Description.ExtensionPoints [GetPath ()]; } diff --git a/Test/UnitTests/TestMultithreading.cs b/Test/UnitTests/TestMultithreading.cs index f827788..f4eb63a 100644 --- a/Test/UnitTests/TestMultithreading.cs +++ b/Test/UnitTests/TestMultithreading.cs @@ -87,94 +87,85 @@ namespace UnitTests } } - int EnableDisableStress_totalAdd; - int EnableDisableStress_totalRemove; - int EnableDisableStress_nodesCount; - int EnableDisableStress_minCount; - int EnableDisableStress_maxCount; - [Test] public void EventsMultithread() { int threads = 50; - int addinsLoaded = 0; - int addinsUnloaded = 0; + int totalAdded = 0; + int totalRemoved = 0; + int nodesCount = 0; + int minCount = 0; + int maxCount = 0; var node = AddinManager.GetExtensionNode("/SimpleApp/Writers"); - try - { - EnableDisableStress_nodesCount = 0; + nodesCount = 0; - node.ExtensionNodeChanged += Node_ExtensionNodeChanged; + node.ExtensionNodeChanged += (s, args) => + { + if (args.Change == ExtensionChange.Add) + { + nodesCount++; + totalAdded++; + } + else + { + nodesCount--; + totalRemoved++; + } - Assert.AreEqual(4, EnableDisableStress_nodesCount); + if (nodesCount < minCount) + minCount = nodesCount; + if (nodesCount > maxCount) + maxCount = nodesCount; + }; - EnableDisableStress_minCount = 4; - EnableDisableStress_maxCount = 4; - EnableDisableStress_totalAdd = 0; - EnableDisableStress_totalRemove = 0; + Assert.AreEqual(4, nodesCount); - var ainfo1 = AddinManager.Registry.GetAddin("SimpleApp.HelloWorldExtension"); - var ainfo2 = AddinManager.Registry.GetAddin("SimpleApp.FileContentExtension"); + minCount = 4; + maxCount = 4; + totalAdded = 0; + totalRemoved = 0; - AddinManager.AddinLoaded += (s,a) => addinsLoaded++; - AddinManager.AddinUnloaded += (s,a) => addinsUnloaded++; + var ainfo1 = AddinManager.Registry.GetAddin("SimpleApp.HelloWorldExtension"); + var ainfo2 = AddinManager.Registry.GetAddin("SimpleApp.FileContentExtension"); - using var enablers = new TestData(threads); + using var enablers = new TestData(threads); - enablers.StartThreads((index, data) => + enablers.StartThreads((index, data) => + { + var random = new Random(10000 + index); + int iterations = 100; + while (--iterations > 0) { - var random = new Random(10000 + index); - while (!data.Stopped) + var action = random.Next(4); + switch (action) { - var action = random.Next(4); - switch (action) - { - case 0: ainfo1.Enabled = false; break; - case 1: ainfo1.Enabled = true; break; - case 2: ainfo2.Enabled = false; break; - case 3: ainfo2.Enabled = true; break; - } + case 0: ainfo1.Enabled = false; break; + case 1: ainfo1.Enabled = true; break; + case 2: ainfo2.Enabled = false; break; + case 3: ainfo2.Enabled = true; break; } - }); - Thread.Sleep(3000); - } - finally - { - node.ExtensionNodeChanged -= Node_ExtensionNodeChanged; - } - - // If all events have been sent correctly, the node count should have never gone below 2 and over 4. + } + data.Counters[index] = 1; + }); - Assert.That(EnableDisableStress_minCount, Is.AtLeast(2)); - Assert.That(EnableDisableStress_maxCount, Is.AtMost(4)); + // Wait for the threads to do the work. 5 seconds should be enough + enablers.CheckCounters(1, 5000); - // Every time one of these add-ins is enabled, a new node is added (likewise when removed), so - // the total count of nodes added must match the number of times the addins were enabled. + // Go back to the initial status - Assert.AreEqual(EnableDisableStress_totalAdd, addinsLoaded); - Assert.AreEqual(EnableDisableStress_totalRemove, addinsUnloaded); - } + ainfo1.Enabled = true; + ainfo2.Enabled = true; - private void Node_ExtensionNodeChanged (object sender, ExtensionNodeEventArgs args) - { - if (args.Change == ExtensionChange.Add) - { - EnableDisableStress_nodesCount++; - EnableDisableStress_totalAdd++; - } - else - { - EnableDisableStress_nodesCount--; - EnableDisableStress_totalRemove++; - } + // If all events have been sent correctly, the node count should have never gone below 2 and over 4. - if (EnableDisableStress_nodesCount < EnableDisableStress_minCount) - EnableDisableStress_minCount = EnableDisableStress_nodesCount; - if (EnableDisableStress_nodesCount > EnableDisableStress_maxCount) - EnableDisableStress_maxCount = EnableDisableStress_nodesCount; + Assert.That(nodesCount, Is.EqualTo(4)); + Assert.That(totalAdded, Is.AtLeast(100)); + Assert.That(totalAdded, Is.EqualTo(totalRemoved)); + Assert.That(minCount, Is.AtLeast(2)); + Assert.That(maxCount, Is.AtMost(4)); } [Test] diff --git a/Test/UnitTests/UnitTests.csproj b/Test/UnitTests/UnitTests.csproj index aae8ac1..d7f43ca 100644 --- a/Test/UnitTests/UnitTests.csproj +++ b/Test/UnitTests/UnitTests.csproj @@ -30,6 +30,7 @@ + -- cgit v1.2.3