Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono-addins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLluis Sanchez <llsan@microsoft.com>2022-10-06 13:59:24 +0300
committerGitHub <noreply@github.com>2022-10-06 13:59:24 +0300
commit9269dca26784bc7c8286b1c034a71edaafd67f86 (patch)
treeba93f663fd353b8ea2bd563c826d7e26970bc031
parent85b194814d9630f17b98c2c713e603cb9695cab3 (diff)
parent186642cdb5a3d6938cb693b4900db67ee5dca83c (diff)
Merge pull request #187 from mono/dev/lluis/threadsafe
Make mono.addins thread safe
-rw-r--r--Mono.Addins/Mono.Addins.Database/AddinDatabase.cs530
-rw-r--r--Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs68
-rw-r--r--Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs129
-rw-r--r--Mono.Addins/Mono.Addins.csproj1
-rw-r--r--Mono.Addins/Mono.Addins/Addin.cs33
-rw-r--r--Mono.Addins/Mono.Addins/AddinEngine.cs403
-rw-r--r--Mono.Addins/Mono.Addins/AddinInfo.cs37
-rw-r--r--Mono.Addins/Mono.Addins/AddinManager.cs32
-rw-r--r--Mono.Addins/Mono.Addins/AddinRegistry.cs48
-rw-r--r--Mono.Addins/Mono.Addins/ConditionType.cs3
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionContext.cs804
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs409
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionNode.cs302
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionNodeList.cs31
-rw-r--r--Mono.Addins/Mono.Addins/ExtensionTree.cs88
-rw-r--r--Mono.Addins/Mono.Addins/InstanceExtensionNode.cs9
-rw-r--r--Mono.Addins/Mono.Addins/RuntimeAddin.cs56
-rw-r--r--Mono.Addins/Mono.Addins/TreeNode.cs382
-rw-r--r--Mono.Addins/Mono.Addins/TreeNodeCollection.cs86
-rw-r--r--Test/UnitTests/TestMultithreading.cs243
-rw-r--r--Test/UnitTests/UnitTests.csproj1
-rw-r--r--Version.props2
22 files changed, 2420 insertions, 1277 deletions
diff --git a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs
index 34f931f..842c12b 100644
--- a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs
+++ b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs
@@ -28,40 +28,43 @@
using System;
-using System.Threading;
using System.Collections;
-using System.Collections.Specialized;
-using System.IO;
-using System.Xml;
-using System.Reflection;
-using Mono.Addins.Description;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
using System.Linq;
-using Mono.Addins.Serialization;
+using System.Threading;
+using Mono.Addins.Description;
namespace Mono.Addins.Database
{
- class AddinDatabase
+ class AddinDatabase
{
public const string GlobalDomain = "global";
public const string UnknownDomain = "unknown";
public const string VersionTag = "004";
- List<Addin> allSetupInfos;
- List<Addin> addinSetupInfos;
- List<Addin> rootSetupInfos;
+ readonly AddinEngine addinEngine;
+ readonly AddinRegistry registry;
+ readonly string addinDbDir;
+ readonly FileDatabase fileDatabase;
+ readonly object localLock = new object ();
+
+ bool allSetupInfosLoaded;
+ ImmutableArray<Addin> allSetupInfos;
+ ImmutableArray<Addin> addinSetupInfos;
+ ImmutableArray<Addin> rootSetupInfos;
+
+ Dictionary<string, Addin> cachedAddinSetupInfos = new Dictionary<string, Addin> ();
+ ImmutableAddinHostIndex hostIndex;
+
internal static bool RunningSetupProcess;
+
bool fatalDatabseError;
- Hashtable cachedAddinSetupInfos = new Hashtable ();
- AddinHostIndex hostIndex;
- FileDatabase fileDatabase;
- string addinDbDir;
DatabaseConfiguration config = null;
- AddinRegistry registry;
int lastDomainId;
- AddinEngine addinEngine;
- AddinFileSystemExtension fs = new AddinFileSystemExtension ();
+ AddinFileSystemExtension fileSystemExtension = new AddinFileSystemExtension ();
List<object> extensions = new List<object> ();
public AddinDatabase (AddinEngine addinEngine, AddinRegistry registry)
@@ -71,13 +74,20 @@ namespace Mono.Addins.Database
addinDbDir = Path.Combine (registry.AddinCachePath, "addin-db-" + VersionTag);
fileDatabase = new FileDatabase (AddinDbDir);
}
-
+
+ public AddinDatabaseTransaction BeginTransaction (AddinEngineTransaction addinEngineTransaction = null)
+ {
+ return new AddinDatabaseTransaction (this, localLock, addinEngineTransaction);
+ }
+
+ internal AddinEngine AddinEngine => addinEngine;
+
string AddinDbDir {
get { return addinDbDir; }
}
public AddinFileSystemExtension FileSystem {
- get { return fs; }
+ get { return fileSystemExtension; }
}
public string AddinCachePath {
@@ -116,36 +126,34 @@ namespace Mono.Addins.Database
}
}
- public void Clear ()
- {
- if (Directory.Exists (AddinCachePath))
- Directory.Delete (AddinCachePath, true);
- if (Directory.Exists (AddinFolderCachePath))
- Directory.Delete (AddinFolderCachePath, true);
- }
-
public void CopyExtensions (AddinDatabase other)
{
- foreach (object o in other.extensions)
- RegisterExtension (o);
+ lock (extensions) {
+ foreach (object o in other.extensions)
+ RegisterExtension (o);
+ }
}
public void RegisterExtension (object extension)
{
- extensions.Add (extension);
- if (extension is AddinFileSystemExtension)
- fs = (AddinFileSystemExtension) extension;
- else
- throw new NotSupportedException ();
+ lock (extensions) {
+ extensions.Add (extension);
+ if (extension is AddinFileSystemExtension)
+ fileSystemExtension = (AddinFileSystemExtension)extension;
+ else
+ throw new NotSupportedException ();
+ }
}
public void UnregisterExtension (object extension)
{
- extensions.Remove (extension);
- if ((extension as AddinFileSystemExtension) == fs)
- fs = new AddinFileSystemExtension ();
- else
- throw new InvalidOperationException ();
+ lock (extensions) {
+ extensions.Remove (extension);
+ if ((extension as AddinFileSystemExtension) == fileSystemExtension)
+ fileSystemExtension = new AddinFileSystemExtension ();
+ else
+ throw new InvalidOperationException ();
+ }
}
public ExtensionNodeSet FindNodeSet (string domain, string addinId, string id)
@@ -190,98 +198,69 @@ namespace Mono.Addins.Database
// Get the cached list if the add-in list has already been loaded.
// The domain doesn't have to be checked again, since it is always the same
- IEnumerable<Addin> result = null;
-
- if ((flags & AddinSearchFlagsInternal.IncludeAll) == AddinSearchFlagsInternal.IncludeAll) {
- if (allSetupInfos != null)
- result = allSetupInfos;
- }
- else if ((flags & AddinSearchFlagsInternal.IncludeAddins) == AddinSearchFlagsInternal.IncludeAddins) {
- if (addinSetupInfos != null)
- result = addinSetupInfos;
- }
- else {
- if (rootSetupInfos != null)
- result = rootSetupInfos;
- }
-
- if (result == null) {
- InternalCheck (domain);
- using (fileDatabase.LockRead ()) {
- result = InternalGetInstalledAddins (domain, null, flags & ~AddinSearchFlagsInternal.LatestVersionsOnly);
- }
- }
-
- if ((flags & AddinSearchFlagsInternal.LatestVersionsOnly) == AddinSearchFlagsInternal.LatestVersionsOnly)
- result = result.Where (a => a.IsLatestVersion);
-
- if ((flags & AddinSearchFlagsInternal.ExcludePendingUninstall) == AddinSearchFlagsInternal.ExcludePendingUninstall)
- result = result.Where (a => !IsRegisteredForUninstall (a.Description.Domain, a.Id));
-
- return result;
+ return InternalGetInstalledAddins (domain, null, flags & ~AddinSearchFlagsInternal.LatestVersionsOnly, false);
}
- IEnumerable<Addin> InternalGetInstalledAddins (string domain, AddinSearchFlagsInternal type)
+ IEnumerable<Addin> InternalGetInstalledAddins (string domain, AddinSearchFlagsInternal type, bool dbIsLockedForRead)
{
- return InternalGetInstalledAddins (domain, null, type);
+ return InternalGetInstalledAddins (domain, null, type, dbIsLockedForRead);
}
- IEnumerable<Addin> InternalGetInstalledAddins (string domain, string idFilter, AddinSearchFlagsInternal type)
+ IEnumerable<Addin> InternalGetInstalledAddins (string domain, string idFilter, AddinSearchFlagsInternal type, bool dbIsLockedForRead)
{
- if ((type & AddinSearchFlagsInternal.LatestVersionsOnly) != 0)
- throw new InvalidOperationException ("LatestVersionsOnly flag not supported here");
-
- if (allSetupInfos == null) {
- Dictionary<string,Addin> adict = new Dictionary<string, Addin> ();
+ if (!allSetupInfosLoaded) {
+ lock (localLock) {
+ if (!allSetupInfosLoaded) {
+ Dictionary<string, Addin> adict = new Dictionary<string, Addin> ();
- // Global add-ins are valid for any private domain
- if (domain != AddinDatabase.GlobalDomain)
- FindInstalledAddins (adict, AddinDatabase.GlobalDomain, idFilter);
+ using (!dbIsLockedForRead ? fileDatabase.LockRead() : null) {
+ // Global add-ins are valid for any private domain
+ if (domain != AddinDatabase.GlobalDomain)
+ FindInstalledAddins (adict, AddinDatabase.GlobalDomain);
- FindInstalledAddins (adict, domain, idFilter);
- List<Addin> alist = new List<Addin> (adict.Values);
- UpdateLastVersionFlags (alist);
- if (idFilter != null)
- return alist;
- allSetupInfos = alist;
- }
- if ((type & AddinSearchFlagsInternal.IncludeAll) == AddinSearchFlagsInternal.IncludeAll)
- return FilterById (allSetupInfos, idFilter);
-
- if ((type & AddinSearchFlagsInternal.IncludeAddins) == AddinSearchFlagsInternal.IncludeAddins) {
- if (addinSetupInfos == null) {
- addinSetupInfos = new List<Addin> ();
- foreach (Addin adn in allSetupInfos)
- if (!adn.Description.IsRoot)
- addinSetupInfos.Add (adn);
+ FindInstalledAddins (adict, domain);
+ }
+ List<Addin> alist = new List<Addin> (adict.Values);
+ UpdateLastVersionFlags (alist);
+ allSetupInfos = alist.ToImmutableArray ();
+ addinSetupInfos = alist.Where (addin => !addin.Description.IsRoot).ToImmutableArray ();
+ rootSetupInfos = alist.Where (addin => addin.Description.IsRoot).ToImmutableArray ();
+ allSetupInfosLoaded = true;
+ }
}
- return FilterById (addinSetupInfos, idFilter);
}
- else {
- if (rootSetupInfos == null) {
- rootSetupInfos = new List<Addin> ();
- foreach (Addin adn in allSetupInfos)
- if (adn.Description.IsRoot)
- rootSetupInfos.Add (adn);
- }
- return FilterById (rootSetupInfos, idFilter);
+ IEnumerable<Addin> result;
+
+ if ((type & AddinSearchFlagsInternal.IncludeAll) == AddinSearchFlagsInternal.IncludeAll) {
+ result = allSetupInfos;
+ } else if ((type & AddinSearchFlagsInternal.IncludeAddins) == AddinSearchFlagsInternal.IncludeAddins) {
+ result = addinSetupInfos;
+ } else {
+ result = rootSetupInfos;
}
+
+ result = FilterById (result, idFilter);
+
+ if ((type & AddinSearchFlagsInternal.LatestVersionsOnly) == AddinSearchFlagsInternal.LatestVersionsOnly)
+ result = result.Where (a => a.IsLatestVersion);
+
+ if ((type & AddinSearchFlagsInternal.ExcludePendingUninstall) == AddinSearchFlagsInternal.ExcludePendingUninstall)
+ result = result.Where (a => !IsRegisteredForUninstall (a.Description.Domain, a.Id));
+ return result;
}
-
- IEnumerable<Addin> FilterById (List<Addin> addins, string id)
+
+ IEnumerable<Addin> FilterById (IEnumerable<Addin> addins, string id)
{
if (id == null)
return addins;
return addins.Where (a => Addin.GetIdName (a.Id) == id);
}
- void FindInstalledAddins (Dictionary<string,Addin> result, string domain, string idFilter)
+ void FindInstalledAddins (Dictionary<string,Addin> result, string domain)
{
- if (idFilter == null)
- idFilter = "*";
string dir = Path.Combine (AddinCachePath, domain);
if (Directory.Exists (dir)) {
- foreach (string file in fileDatabase.GetDirectoryFiles (dir, idFilter + ",*.maddin")) {
+ foreach (string file in fileDatabase.GetDirectoryFiles (dir, "*,*.maddin")) {
string id = Path.GetFileNameWithoutExtension (file);
if (!result.ContainsKey (id)) {
var adesc = GetInstalledDomainAddin (domain, id, true, false, false);
@@ -330,23 +309,26 @@ namespace Mono.Addins.Database
else
return null;
}
-
+
Addin GetInstalledDomainAddin (string domain, string id, bool exactVersionMatch, bool enabledOnly, bool dbLockCheck)
{
- Addin sinfo = null;
string idd = id + " " + domain;
- object ob = cachedAddinSetupInfos [idd];
- if (ob != null) {
- sinfo = ob as Addin;
+ Addin sinfo;
+ bool found;
+ lock (cachedAddinSetupInfos) {
+ found = cachedAddinSetupInfos.TryGetValue (idd, out sinfo);
+ }
+
+ if (found) {
if (sinfo != null) {
if (!enabledOnly || sinfo.Enabled)
return sinfo;
if (exactVersionMatch)
return null;
- }
- else if (enabledOnly)
+ } else if (enabledOnly) {
// Ignore the 'not installed' flag when disabled add-ins are allowed
return null;
+ }
}
if (dbLockCheck)
@@ -376,13 +358,15 @@ namespace Mono.Addins.Database
}
if (foundDomain != null) {
sinfo = new Addin (this, foundDomain, id);
- cachedAddinSetupInfos [idd] = sinfo;
- if (!enabledOnly || sinfo.Enabled)
- return sinfo;
- if (exactVersionMatch) {
- // Cache lookups with negative result
- cachedAddinSetupInfos [idd] = this;
- return null;
+ lock (cachedAddinSetupInfos) {
+ cachedAddinSetupInfos [idd] = sinfo;
+ if (!enabledOnly || sinfo.Enabled)
+ return sinfo;
+ if (exactVersionMatch) {
+ // Cache lookups with negative result
+ cachedAddinSetupInfos [idd] = null;
+ return null;
+ }
}
}
}
@@ -393,7 +377,7 @@ namespace Mono.Addins.Database
string bestVersion = null;
Addin.GetIdParts (id, out name, out version);
- foreach (Addin ia in InternalGetInstalledAddins (domain, name, AddinSearchFlagsInternal.IncludeAll))
+ foreach (Addin ia in InternalGetInstalledAddins (domain, name, AddinSearchFlagsInternal.IncludeAll, true))
{
if ((!enabledOnly || ia.Enabled) &&
(version.Length == 0 || ia.SupportsVersion (version)) &&
@@ -404,15 +388,20 @@ namespace Mono.Addins.Database
}
}
if (sinfo != null) {
- cachedAddinSetupInfos [idd] = sinfo;
+ lock (cachedAddinSetupInfos) {
+ cachedAddinSetupInfos [idd] = sinfo;
+ }
return sinfo;
}
}
-
+
// Cache lookups with negative result
// Ignore the 'not installed' flag when disabled add-ins are allowed
- if (enabledOnly)
- cachedAddinSetupInfos [idd] = this;
+ if (enabledOnly) {
+ lock (cachedAddinSetupInfos) {
+ cachedAddinSetupInfos [idd] = null;
+ }
+ }
return null;
}
}
@@ -426,20 +415,22 @@ namespace Mono.Addins.Database
{
InternalCheck (domain);
Addin ainfo = null;
-
- object ob = cachedAddinSetupInfos [assemblyLocation];
- if (ob != null)
- return ob as Addin; // Don't use a cast here is ob may not be an Addin.
- AddinHostIndex index = GetAddinHostIndex ();
+ lock (cachedAddinSetupInfos) {
+ if (cachedAddinSetupInfos.TryGetValue (assemblyLocation, out var ob))
+ return ob; // this can be null, if the add-in is disabled
+ }
+
+ var index = GetAddinHostIndex ();
string addin, addinFile, rdomain;
if (index.GetAddinForAssembly (assemblyLocation, out addin, out addinFile, out rdomain)) {
string sid = addin + " " + rdomain;
- ainfo = cachedAddinSetupInfos [sid] as Addin;
- if (ainfo == null)
- ainfo = new Addin (this, rdomain, addin);
- cachedAddinSetupInfos [assemblyLocation] = ainfo;
- cachedAddinSetupInfos [addin + " " + rdomain] = ainfo;
+ lock (cachedAddinSetupInfos) {
+ if (!cachedAddinSetupInfos.TryGetValue(sid, out ainfo))
+ ainfo = new Addin (this, rdomain, addin);
+ cachedAddinSetupInfos [assemblyLocation] = ainfo;
+ cachedAddinSetupInfos [sid] = ainfo;
+ }
}
return ainfo;
@@ -469,8 +460,14 @@ namespace Mono.Addins.Database
{
EnableAddin (domain, id, true);
}
-
- internal void EnableAddin (string domain, string id, bool exactVersionMatch)
+
+ public void EnableAddin (string domain, string id, bool exactVersionMatch)
+ {
+ using var transaction = BeginTransaction ();
+ EnableAddin (transaction, domain, id, exactVersionMatch);
+ }
+
+ void EnableAddin (AddinDatabaseTransaction dbTransaction, string domain, string id, bool exactVersionMatch)
{
Addin ainfo = GetInstalledAddin (domain, id, exactVersionMatch, false);
if (ainfo == null)
@@ -486,19 +483,26 @@ namespace Mono.Addins.Database
if (dep is AddinDependency) {
AddinDependency adep = dep as AddinDependency;
string adepid = Addin.GetFullId (ainfo.AddinInfo.Namespace, adep.AddinId, adep.Version);
- EnableAddin (domain, adepid, false);
+ EnableAddin (dbTransaction, domain, adepid, false);
}
}
- Configuration.SetEnabled (id, true, ainfo.AddinInfo.EnabledByDefault, true);
- SaveConfiguration ();
+ Configuration.SetEnabled (dbTransaction, id, true, ainfo.AddinInfo.EnabledByDefault, true);
+ SaveConfiguration (dbTransaction);
- if (addinEngine != null && addinEngine.IsInitialized)
- addinEngine.ActivateAddin (id);
+ if (addinEngine != null && addinEngine.IsInitialized) {
+ addinEngine.ActivateAddin (dbTransaction.GetAddinEngineTransaction(), id);
+ }
}
-
+
public void DisableAddin (string domain, string id, bool exactVersionMatch = false, bool onlyForCurrentSession = false)
{
+ using var transaction = BeginTransaction ();
+ DisableAddin (transaction, domain, id, exactVersionMatch, onlyForCurrentSession);
+ }
+
+ void DisableAddin (AddinDatabaseTransaction dbTransaction, string domain, string id, bool exactVersionMatch = false, bool onlyForCurrentSession = false)
+ {
Addin ai = GetInstalledAddin (domain, id, true);
if (ai == null)
throw new InvalidOperationException ("Add-in '" + id + "' not installed.");
@@ -506,8 +510,8 @@ namespace Mono.Addins.Database
if (!IsAddinEnabled (domain, id, exactVersionMatch))
return;
- Configuration.SetEnabled (id, false, ai.AddinInfo.EnabledByDefault, exactVersionMatch, onlyForCurrentSession);
- SaveConfiguration ();
+ Configuration.SetEnabled (dbTransaction, id, false, ai.AddinInfo.EnabledByDefault, exactVersionMatch, onlyForCurrentSession);
+ SaveConfiguration (dbTransaction);
// Disable all add-ins which depend on it
@@ -531,7 +535,7 @@ namespace Mono.Addins.Database
Addin adepinfo = GetInstalledAddin (domain, adepid, false, true);
if (adepinfo == null) {
- DisableAddin (domain, ainfo.Id, onlyForCurrentSession: onlyForCurrentSession);
+ DisableAddin (dbTransaction, domain, ainfo.Id, onlyForCurrentSession: onlyForCurrentSession);
break;
}
}
@@ -539,25 +543,26 @@ namespace Mono.Addins.Database
}
catch {
// If something goes wrong, enable the add-in again
- Configuration.SetEnabled (id, true, ai.AddinInfo.EnabledByDefault, false, onlyForCurrentSession);
- SaveConfiguration ();
+ Configuration.SetEnabled (dbTransaction, id, true, ai.AddinInfo.EnabledByDefault, false, onlyForCurrentSession);
+ SaveConfiguration (dbTransaction);
throw;
}
- if (addinEngine != null && addinEngine.IsInitialized)
- addinEngine.UnloadAddin (id);
+ if (addinEngine != null && addinEngine.IsInitialized) {
+ addinEngine.UnloadAddin (dbTransaction.GetAddinEngineTransaction(), id);
+ }
}
- public void UpdateEnabledStatus ()
+ void UpdateEnabledStatus (AddinDatabaseTransaction transaction)
{
// Ensure that all enabled addins that have dependencies also have their dependencies enabled.
HashSet<Addin> updatedAddins = new HashSet<Addin> ();
var allAddins = GetInstalledAddins (registry.CurrentDomain, AddinSearchFlagsInternal.IncludeAddins | AddinSearchFlagsInternal.LatestVersionsOnly).ToList ();
foreach (Addin addin in allAddins)
- UpdateEnabledStatus (registry.CurrentDomain, addin, allAddins, updatedAddins);
+ UpdateEnabledStatus (transaction, registry.CurrentDomain, addin, allAddins, updatedAddins);
}
- void UpdateEnabledStatus (string domain, Addin addin, List<Addin> allAddins, HashSet<Addin> updatedAddins)
+ void UpdateEnabledStatus (AddinDatabaseTransaction transaction, string domain, Addin addin, List<Addin> allAddins, HashSet<Addin> updatedAddins)
{
if (!updatedAddins.Add (addin))
return;
@@ -575,12 +580,12 @@ namespace Mono.Addins.Database
string adepid = Addin.GetFullId (addin.AddinInfo.Namespace, adep.AddinId, null);
var dependency = allAddins.FirstOrDefault (a => Addin.GetFullId (a.Namespace, a.LocalId, null) == adepid);
if (dependency != null) {
- UpdateEnabledStatus (domain, dependency, allAddins, updatedAddins);
+ UpdateEnabledStatus (transaction, domain, dependency, allAddins, updatedAddins);
if (!dependency.Enabled) {
// One of the dependencies is disabled, so this add-in also needs to be disabled.
// However, we disabled only for the current configuration, we don't want to change
// what the user configured.
- DisableAddin (domain, addin.Id, onlyForCurrentSession: true);
+ DisableAddin (transaction, domain, addin.Id, onlyForCurrentSession: true);
return;
}
}
@@ -589,9 +594,10 @@ namespace Mono.Addins.Database
public void RegisterForUninstall (string domain, string id, IEnumerable<string> files)
{
- DisableAddin (domain, id, true);
- Configuration.RegisterForUninstall (id, files);
- SaveConfiguration ();
+ using var transaction = BeginTransaction ();
+ DisableAddin (transaction, domain, id, true);
+ Configuration.RegisterForUninstall (transaction, id, files);
+ SaveConfiguration (transaction);
}
public bool IsRegisteredForUninstall (string domain, string addinId)
@@ -630,7 +636,8 @@ namespace Mono.Addins.Database
AddinUpdateData updateData = new AddinUpdateData (this, monitor);
// Clear cached data
- cachedAddinSetupInfos.Clear ();
+ lock(cachedAddinSetupInfos)
+ cachedAddinSetupInfos.Clear ();
// Collect all information
@@ -703,7 +710,7 @@ namespace Mono.Addins.Database
}
// If the original file does not exist, the description can be deleted
- if (!fs.FileExists (conf.AddinFile)) {
+ if (!fileSystemExtension.FileExists (conf.AddinFile)) {
SafeDelete (monitor, file);
continue;
}
@@ -1012,19 +1019,19 @@ namespace Mono.Addins.Database
internal void ResetBasicCachedData ()
{
- allSetupInfos = null;
- addinSetupInfos = null;
- rootSetupInfos = null;
+ lock(localLock)
+ allSetupInfosLoaded = false;
}
- internal void ResetCachedData ()
+ internal void ResetCachedData (AddinDatabaseTransaction dbTransaction = null)
{
ResetBasicCachedData ();
hostIndex = null;
- cachedAddinSetupInfos.Clear ();
+ lock(cachedAddinSetupInfos)
+ cachedAddinSetupInfos.Clear ();
dependsOnCache.Clear ();
if (addinEngine != null)
- addinEngine.ResetCachedData ();
+ addinEngine.ResetCachedData (dbTransaction?.GetAddinEngineTransaction());
}
Dictionary<string, HashSet<string>> dependsOnCache = new Dictionary<string, HashSet<string>> ();
@@ -1088,79 +1095,87 @@ namespace Mono.Addins.Database
Update (monitor, domain, context);
}
-
- public void Update (IProgressStatus monitor, string domain, ScanOptions context = null)
+
+ public void Update(IProgressStatus monitor, string domain, ScanOptions context = null, AddinEngineTransaction addinEngineTransaction = null)
{
if (monitor == null)
- monitor = new ConsoleProgressStatus (false);
+ monitor = new ConsoleProgressStatus(false);
if (RunningSetupProcess)
return;
-
+
fatalDatabseError = false;
-
+
DateTime tim = DateTime.Now;
-
- RunPendingUninstalls (monitor);
-
- Hashtable installed = new Hashtable ();
- bool changesFound = CheckFolders (monitor, domain);
-
+
+ using var dbTransaction = BeginTransaction(addinEngineTransaction);
+
+ RunPendingUninstalls(dbTransaction, monitor);
+
+ Hashtable installed = new Hashtable();
+ bool changesFound = CheckFolders(monitor, domain);
+
if (monitor.IsCanceled)
return;
-
+
if (monitor.LogLevel > 1)
- monitor.Log ("Folders checked (" + (int) (DateTime.Now - tim).TotalMilliseconds + " ms)");
-
- if (changesFound) {
+ monitor.Log("Folders checked (" + (int)(DateTime.Now - tim).TotalMilliseconds + " ms)");
+
+ if (changesFound)
+ {
// Something has changed, the add-ins need to be re-scanned, but it has
// to be done in an external process
-
- if (domain != null) {
- using (fileDatabase.LockRead ()) {
- foreach (Addin ainfo in InternalGetInstalledAddins (domain, AddinSearchFlagsInternal.IncludeAddins)) {
- installed [ainfo.Id] = ainfo.Id;
- }
+
+ if (domain != null)
+ {
+ foreach (Addin ainfo in InternalGetInstalledAddins(domain, AddinSearchFlagsInternal.IncludeAddins, false))
+ {
+ installed[ainfo.Id] = ainfo.Id;
}
}
-
- RunScannerProcess (monitor, context);
-
- ResetCachedData ();
-
- registry.NotifyDatabaseUpdated ();
+
+ RunScannerProcess(monitor, context);
+
+ ResetCachedData(dbTransaction);
+
+ registry.NotifyDatabaseUpdated();
}
-
+
if (fatalDatabseError)
- monitor.ReportError ("The add-in database could not be updated. It may be due to file corruption. Try running the setup repair utility", null);
-
+ monitor.ReportError("The add-in database could not be updated. It may be due to file corruption. Try running the setup repair utility", null);
+
// Update the currently loaded add-ins
- if (changesFound && domain != null && addinEngine != null && addinEngine.IsInitialized) {
- Hashtable newInstalled = new Hashtable ();
- foreach (Addin ainfo in GetInstalledAddins (domain, AddinSearchFlagsInternal.IncludeAddins)) {
- newInstalled [ainfo.Id] = ainfo.Id;
+ if (changesFound && domain != null && addinEngine != null && addinEngine.IsInitialized)
+ {
+ Hashtable newInstalled = new Hashtable();
+ foreach (Addin ainfo in GetInstalledAddins(domain, AddinSearchFlagsInternal.IncludeAddins))
+ {
+ newInstalled[ainfo.Id] = ainfo.Id;
}
-
- foreach (string aid in installed.Keys) {
+
+ foreach (string aid in installed.Keys)
+ {
// Always try to unload, event if the add-in was not currently loaded.
// Required since the add-ins has to be marked as 'disabled', to avoid
// extensions from this add-in to be loaded
- if (!newInstalled.Contains (aid))
- addinEngine.UnloadAddin (aid);
+ if (!newInstalled.Contains(aid))
+ addinEngine.UnloadAddin(dbTransaction.GetAddinEngineTransaction(), aid);
}
-
- foreach (string aid in newInstalled.Keys) {
- if (!installed.Contains (aid)) {
- Addin addin = addinEngine.Registry.GetAddin (aid);
+
+ foreach (string aid in newInstalled.Keys)
+ {
+ if (!installed.Contains(aid))
+ {
+ Addin addin = addinEngine.Registry.GetAddin(aid);
if (addin != null)
- addinEngine.ActivateAddin (aid);
+ addinEngine.ActivateAddin(dbTransaction.GetAddinEngineTransaction(), aid);
}
}
}
- UpdateEnabledStatus ();
+ UpdateEnabledStatus(dbTransaction);
}
- void RunPendingUninstalls (IProgressStatus monitor)
+ void RunPendingUninstalls (AddinDatabaseTransaction dbTransaction, IProgressStatus monitor)
{
bool changesDone = false;
@@ -1198,12 +1213,12 @@ namespace Mono.Addins.Database
}
if (canUninstall) {
- Configuration.UnregisterForUninstall (adn.AddinId);
+ Configuration.UnregisterForUninstall (dbTransaction, adn.AddinId);
changesDone = true;
}
}
if (changesDone)
- SaveConfiguration ();
+ SaveConfiguration (dbTransaction);
}
void RunScannerProcess (IProgressStatus monitor, ScanOptions context)
@@ -1214,8 +1229,8 @@ namespace Mono.Addins.Database
IProgressStatus scanMonitor = monitor;
context = context ?? new ScanOptions ();
- if (fs.GetType () != typeof (AddinFileSystemExtension))
- context.FileSystemExtension = fs;
+ if (fileSystemExtension.GetType () != typeof (AddinFileSystemExtension))
+ context.FileSystemExtension = fileSystemExtension;
bool retry = false;
do {
@@ -1350,10 +1365,10 @@ namespace Mono.Addins.Database
void InternalScanFolders (IProgressStatus monitor, AddinScanResult scanResult)
{
try {
- fs.ScanStarted ();
+ fileSystemExtension.ScanStarted ();
InternalScanFolders2 (monitor, scanResult);
} finally {
- fs.ScanFinished ();
+ fileSystemExtension.ScanFinished ();
}
}
@@ -1366,7 +1381,7 @@ namespace Mono.Addins.Database
return;
try {
- scanResult.HostIndex = GetAddinHostIndex ();
+ scanResult.HostIndex = new AddinHostIndex(GetAddinHostIndex ());
}
catch (Exception ex) {
if (scanResult.CheckOnly) {
@@ -1385,7 +1400,7 @@ namespace Mono.Addins.Database
AddinScanFolderInfo folderInfo;
bool res = ReadFolderInfo (monitor, file, out folderInfo);
bool validForDomain = scanResult.Domain == null || folderInfo.Domain == GlobalDomain || folderInfo.Domain == scanResult.Domain;
- if (!res || (validForDomain && !fs.DirectoryExists (folderInfo.Folder))) {
+ if (!res || (validForDomain && !fileSystemExtension.DirectoryExists (folderInfo.Folder))) {
if (res) {
// Folder has been deleted. Remove the add-ins it had.
updater.UpdateDeletedAddins (monitor, folderInfo);
@@ -1444,7 +1459,7 @@ namespace Mono.Addins.Database
if (monitor.LogLevel > 1)
monitor.Log ("Folders scan completed (" + (int) (DateTime.Now - tim).TotalMilliseconds + " ms)");
- SaveAddinHostIndex ();
+ SaveAddinHostIndex (scanResult);
ResetCachedData ();
if (!scanResult.ChangesFound) {
@@ -1472,7 +1487,9 @@ namespace Mono.Addins.Database
if (monitor.LogLevel > 1)
monitor.Log ("Add-in relations analyzed (" + (int) (DateTime.Now - tim).TotalMilliseconds + " ms)");
- SaveAddinHostIndex ();
+ SaveAddinHostIndex (scanResult);
+
+ hostIndex = scanResult.HostIndex.ToImmutableAddinHostIndex ();
}
public void ParseAddin (IProgressStatus progressStatus, string domain, string file, string outFile, bool inProcess)
@@ -1768,25 +1785,25 @@ namespace Mono.Addins.Database
return false;
}
}
-
- AddinHostIndex GetAddinHostIndex ()
+
+ ImmutableAddinHostIndex GetAddinHostIndex ()
{
if (hostIndex != null)
return hostIndex;
using (fileDatabase.LockRead ()) {
if (fileDatabase.Exists (HostIndexFile))
- hostIndex = AddinHostIndex.Read (fileDatabase, HostIndexFile);
+ hostIndex = AddinHostIndex.ReadAsImmutable (fileDatabase, HostIndexFile);
else
- hostIndex = new AddinHostIndex ();
+ hostIndex = new ImmutableAddinHostIndex ();
}
return hostIndex;
}
- void SaveAddinHostIndex ()
+ void SaveAddinHostIndex (AddinScanResult scanResult)
{
- if (hostIndex != null)
- hostIndex.Write (fileDatabase, HostIndexFile);
+ if (scanResult.HostIndex != null)
+ scanResult.HostIndex.Write (fileDatabase, HostIndexFile);
}
internal string GetUniqueAddinId (string file, string oldId, string ns, string version)
@@ -1844,18 +1861,20 @@ namespace Mono.Addins.Database
DatabaseConfiguration Configuration {
get {
if (config == null) {
- using (fileDatabase.LockRead ()) {
- if (fileDatabase.Exists (ConfigFile))
- config = DatabaseConfiguration.Read (ConfigFile);
- else
- config = DatabaseConfiguration.ReadAppConfig ();
+ lock (localLock) {
+ using (fileDatabase.LockRead ()) {
+ if (fileDatabase.Exists (ConfigFile))
+ config = DatabaseConfiguration.Read (ConfigFile);
+ else
+ config = DatabaseConfiguration.ReadAppConfig ();
+ }
}
}
return config;
}
}
- void SaveConfiguration ()
+ void SaveConfiguration (AddinDatabaseTransaction dbTransaction)
{
if (config != null) {
using (fileDatabase.LockWrite ()) {
@@ -1996,7 +2015,38 @@ namespace Mono.Addins.Database
list.Add (desc);
}
}
-
+
+ class AddinDatabaseTransaction : IDisposable
+ {
+ readonly AddinDatabase addinDatabase;
+ readonly object localLock;
+ AddinEngineTransaction addinEngineTransaction;
+ bool addinEngineTransactionStarted;
+
+ public AddinDatabaseTransaction (AddinDatabase addinDatabase, object localLock, AddinEngineTransaction addinEngineTransaction)
+ {
+ this.addinDatabase = addinDatabase;
+ this.localLock = localLock;
+ this.addinEngineTransaction = addinEngineTransaction;
+ Monitor.Enter (localLock);
+ }
+
+ public AddinEngineTransaction GetAddinEngineTransaction()
+ {
+ if (addinEngineTransaction != null)
+ return addinEngineTransaction;
+ addinEngineTransactionStarted = true;
+ return addinEngineTransaction = addinDatabase.AddinEngine.BeginEngineTransaction();
+ }
+
+ public void Dispose ()
+ {
+ Monitor.Exit(localLock);
+ if (addinEngineTransactionStarted)
+ addinEngineTransaction.Dispose();
+ }
+ }
+
// Keep in sync with AddinSearchFlags
[Flags]
enum AddinSearchFlagsInternal
diff --git a/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs b/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs
index 7919497..571deda 100644
--- a/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs
+++ b/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs
@@ -31,26 +31,46 @@ using System;
using System.Collections;
using Mono.Addins.Serialization;
using System.IO;
+using System.Collections.Generic;
namespace Mono.Addins.Database
{
class AddinHostIndex: IBinaryXmlElement
{
static BinaryXmlTypeMap typeMap = new BinaryXmlTypeMap (typeof(AddinHostIndex));
-
- Hashtable index = new Hashtable ();
-
+
+ Dictionary<string, string> index;
+
+ public AddinHostIndex ()
+ {
+ index = new Dictionary<string, string> ();
+ }
+
+ public AddinHostIndex (ImmutableAddinHostIndex immutableIndex)
+ {
+ this.index = immutableIndex.ToDictionary();
+ }
+
+ public ImmutableAddinHostIndex ToImmutableAddinHostIndex ()
+ {
+ return new ImmutableAddinHostIndex (new Dictionary<string, string> (index));
+ }
+
public void RegisterAssembly (string assemblyLocation, string addinId, string addinLocation, string domain)
{
assemblyLocation = NormalizeFileName (assemblyLocation);
index [Path.GetFullPath (assemblyLocation)] = addinId + " " + addinLocation + " " + domain;
}
-
+
public bool GetAddinForAssembly (string assemblyLocation, out string addinId, out string addinLocation, out string domain)
{
+ return LookupAddinForAssembly (index, assemblyLocation, out addinId, out addinLocation, out domain);
+ }
+
+ internal static bool LookupAddinForAssembly (Dictionary<string, string> index, string assemblyLocation, out string addinId, out string addinLocation, out string domain)
+ {
assemblyLocation = NormalizeFileName (assemblyLocation);
- string s = index [Path.GetFullPath (assemblyLocation)] as string;
- if (s == null) {
+ if (!index.TryGetValue(Path.GetFullPath (assemblyLocation), out var s)) {
addinId = null;
addinLocation = null;
domain = null;
@@ -70,7 +90,7 @@ namespace Mono.Addins.Database
{
string loc = addinId + " " + Path.GetFullPath (addinLocation) + " ";
ArrayList todelete = new ArrayList ();
- foreach (DictionaryEntry e in index) {
+ foreach (var e in index) {
if (((string)e.Value).StartsWith (loc))
todelete.Add (e.Key);
}
@@ -82,7 +102,13 @@ namespace Mono.Addins.Database
{
return (AddinHostIndex) fileDatabase.ReadObject (file, typeMap);
}
-
+
+ public static ImmutableAddinHostIndex ReadAsImmutable (FileDatabase fileDatabase, string file)
+ {
+ var hostIndex = (AddinHostIndex)fileDatabase.ReadObject (file, typeMap);
+ return new ImmutableAddinHostIndex (hostIndex.index);
+ }
+
public void Write (FileDatabase fileDatabase, string file)
{
fileDatabase.WriteObject (file, this, typeMap);
@@ -98,7 +124,7 @@ namespace Mono.Addins.Database
reader.ReadValue ("index", index);
}
- string NormalizeFileName (string name)
+ internal static string NormalizeFileName (string name)
{
if (Util.IsWindows)
return name.ToLower ();
@@ -106,4 +132,28 @@ namespace Mono.Addins.Database
return name;
}
}
+
+ class ImmutableAddinHostIndex
+ {
+ Dictionary<string, string> index;
+
+ public ImmutableAddinHostIndex () : this (new Dictionary<string, string> ())
+ {
+ }
+
+ public ImmutableAddinHostIndex (Dictionary<string, string> index)
+ {
+ this.index = index;
+ }
+
+ public bool GetAddinForAssembly (string assemblyLocation, out string addinId, out string addinLocation, out string domain)
+ {
+ return AddinHostIndex.LookupAddinForAssembly (index, assemblyLocation, out addinId, out addinLocation, out domain);
+ }
+
+ public Dictionary<string, string> ToDictionary ()
+ {
+ return new Dictionary<string, string> (index);
+ }
+ }
}
diff --git a/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs b/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs
index 14d8840..fa208d0 100644
--- a/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs
+++ b/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs
@@ -33,25 +33,64 @@ using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
+using System.Collections.Immutable;
namespace Mono.Addins.Database
{
internal class DatabaseConfiguration
{
- Dictionary<string,AddinStatus> addinStatus = new Dictionary<string,AddinStatus> ();
+ ImmutableDictionary<string, AddinStatus> addinStatus = ImmutableDictionary<string, AddinStatus>.Empty;
internal class AddinStatus
{
- public AddinStatus (string addinId)
+ public AddinStatus (string addinId, bool configEnabled = false, bool? sessionEnabled = null, bool uninstalled = false, ImmutableArray<string> files = default)
{
this.AddinId = addinId;
+ ConfigEnabled = configEnabled;
+ SessionEnabled = sessionEnabled;
+ Uninstalled = uninstalled;
+ if (files.IsDefault)
+ Files = ImmutableArray<string>.Empty;
+ else
+ Files = files;
}
- public string AddinId;
- public bool ConfigEnabled;
- public bool? SessionEnabled;
- public bool Uninstalled;
- public List<string> Files;
+ AddinStatus Copy ()
+ {
+ return new AddinStatus (AddinId,
+ configEnabled: ConfigEnabled,
+ sessionEnabled: SessionEnabled,
+ uninstalled: Uninstalled,
+ files: Files
+ );
+ }
+
+ public AddinStatus AsEnabled (bool enabled, bool sessionOnly)
+ {
+ var copy = Copy ();
+ if (sessionOnly)
+ copy.SessionEnabled = enabled;
+ else {
+ copy.ConfigEnabled = enabled;
+ copy.SessionEnabled = null;
+ }
+ return copy;
+ }
+
+ public AddinStatus AsUninstalled ()
+ {
+ var copy = Copy ();
+ copy.ConfigEnabled = false;
+ copy.Uninstalled = true;
+ return copy;
+ }
+
+ public string AddinId { get; private set;}
+ public bool ConfigEnabled { get; private set; }
+ public bool? SessionEnabled { get; private set; }
+ public bool Uninstalled { get; private set; }
+ public ImmutableArray<string> Files { get; private set; }
+
public bool Enabled {
get { return SessionEnabled ?? ConfigEnabled; }
}
@@ -63,56 +102,51 @@ namespace Mono.Addins.Database
AddinStatus s;
+ var status = addinStatus;
+
// If the add-in is globaly disabled, it is disabled no matter what the version specific status is
- if (addinStatus.TryGetValue (addinName, out s)) {
+ if (status.TryGetValue (addinName, out s)) {
if (!s.Enabled)
return false;
}
- if (addinStatus.TryGetValue (addinId, out s))
- return s.Enabled && !IsRegisteredForUninstall (addinId);
+ if (status.TryGetValue (addinId, out s))
+ return s.Enabled && !s.Uninstalled;
else
return defaultValue;
}
- public void SetEnabled (string addinId, bool enabled, bool defaultValue, bool exactVersionMatch, bool onlyForTheSession = false)
+ public void SetEnabled (AddinDatabaseTransaction transaction, string addinId, bool enabled, bool defaultValue, bool exactVersionMatch, bool onlyForTheSession = false)
{
if (IsRegisteredForUninstall (addinId))
return;
var addinName = exactVersionMatch ? addinId : Addin.GetIdName (addinId);
- AddinStatus s;
- addinStatus.TryGetValue (addinName, out s);
-
- if (s == null)
- s = addinStatus [addinName] = new AddinStatus (addinName);
- if (onlyForTheSession)
- s.SessionEnabled = enabled;
- else {
- s.ConfigEnabled = enabled;
- s.SessionEnabled = null;
- }
+ if (!addinStatus.TryGetValue (addinName, out var s))
+ s = new AddinStatus (addinName);
+
+ s = s.AsEnabled (enabled, onlyForTheSession);
+ addinStatus = addinStatus.SetItem (addinName, s);
// If enabling a specific version of an add-in, make sure the add-in is enabled as a whole
if (enabled && exactVersionMatch)
- SetEnabled (addinId, true, defaultValue, false, onlyForTheSession);
+ SetEnabled (transaction, addinId, true, defaultValue, false, onlyForTheSession);
}
- public void RegisterForUninstall (string addinId, IEnumerable<string> files)
+ public void RegisterForUninstall (AddinDatabaseTransaction transaction, string addinId, IEnumerable<string> files)
{
AddinStatus s;
if (!addinStatus.TryGetValue (addinId, out s))
- s = addinStatus [addinId] = new AddinStatus (addinId);
+ s = new AddinStatus (addinId);
- s.ConfigEnabled = false;
- s.Uninstalled = true;
- s.Files = new List<string> (files);
+ s = s.AsUninstalled ();
+ addinStatus = addinStatus.SetItem (addinId, s);
}
-
- public void UnregisterForUninstall (string addinId)
+
+ public void UnregisterForUninstall (AddinDatabaseTransaction transaction, string addinId)
{
- addinStatus.Remove (addinId);
+ addinStatus = addinStatus.Remove (addinId);
}
public bool IsRegisteredForUninstall (string addinId)
@@ -143,8 +177,7 @@ namespace Mono.Addins.Database
return config;
// Overwrite app config values with user config values
- foreach (var entry in config.addinStatus)
- appConfig.addinStatus [entry.Key] = entry.Value;
+ appConfig.addinStatus = appConfig.addinStatus.SetItems (config.addinStatus);
return appConfig;
}
@@ -170,25 +203,29 @@ namespace Mono.Addins.Database
XmlElement disabledElem = (XmlElement) doc.DocumentElement.SelectSingleNode ("DisabledAddins");
if (disabledElem != null) {
// For back compatibility
- foreach (XmlElement elem in disabledElem.SelectNodes ("Addin"))
- config.SetEnabled (elem.InnerText, false, true, false);
+ var dictionary = ImmutableDictionary.CreateBuilder<string, AddinStatus> ();
+ foreach (XmlElement elem in disabledElem.SelectNodes ("Addin")) {
+ AddinStatus status = new AddinStatus (Addin.GetIdName (elem.GetAttribute ("id")), configEnabled: false);
+ dictionary [status.AddinId] = status;
+ }
+ config.addinStatus = dictionary.ToImmutable ();
return config;
}
-
+
XmlElement statusElem = (XmlElement) doc.DocumentElement.SelectSingleNode ("AddinStatus");
if (statusElem != null) {
+ var dictionary = ImmutableDictionary.CreateBuilder<string, AddinStatus> ();
foreach (XmlElement elem in statusElem.SelectNodes ("Addin")) {
- AddinStatus status = new AddinStatus (elem.GetAttribute ("id"));
string senabled = elem.GetAttribute ("enabled");
- status.ConfigEnabled = senabled.Length == 0 || senabled == "True";
- status.Uninstalled = elem.GetAttribute ("uninstalled") == "True";
- config.addinStatus [status.AddinId] = status;
- foreach (XmlElement fileElem in elem.SelectNodes ("File")) {
- if (status.Files == null)
- status.Files = new List<string> ();
- status.Files.Add (fileElem.InnerText);
- }
+
+ AddinStatus status = new AddinStatus (elem.GetAttribute ("id"),
+ configEnabled: senabled.Length == 0 || senabled == "True",
+ uninstalled: elem.GetAttribute ("uninstalled") == "True",
+ files: elem.SelectNodes ("File").OfType<XmlElement> ().Select (fileElem => fileElem.InnerText).ToImmutableArray ()
+ );
+ dictionary [status.AddinId] = status;
}
+ config.addinStatus = dictionary.ToImmutable ();
}
return config;
}
@@ -208,7 +245,7 @@ namespace Mono.Addins.Database
tw.WriteAttributeString ("enabled", e.ConfigEnabled.ToString ());
if (e.Uninstalled)
tw.WriteAttributeString ("uninstalled", "True");
- if (e.Files != null && e.Files.Count > 0) {
+ if (e.Files.Length > 0) {
foreach (var f in e.Files)
tw.WriteElementString ("File", f);
}
diff --git a/Mono.Addins/Mono.Addins.csproj b/Mono.Addins/Mono.Addins.csproj
index 9bdd5d0..73bf194 100644
--- a/Mono.Addins/Mono.Addins.csproj
+++ b/Mono.Addins/Mono.Addins.csproj
@@ -20,6 +20,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
+ <PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Remove="CustomConditionAttribute.cs" />
diff --git a/Mono.Addins/Mono.Addins/Addin.cs b/Mono.Addins/Mono.Addins/Addin.cs
index 47f9917..792eb84 100644
--- a/Mono.Addins/Mono.Addins/Addin.cs
+++ b/Mono.Addins/Mono.Addins/Addin.cs
@@ -136,14 +136,16 @@ namespace Mono.Addins
internal AddinInfo AddinInfo {
get {
- if (addin == null) {
+ var addinInfo = addin;
+
+ if (addinInfo == null) {
try {
- addin = AddinInfo.ReadFromDescription (Description);
+ addinInfo = addin = AddinInfo.ReadFromDescription (Description);
} catch (Exception ex) {
throw new InvalidOperationException ("Could not read add-in file: " + database.GetDescriptionPath (domain, id), ex);
}
}
- return addin;
+ return addinInfo;
}
}
@@ -235,17 +237,16 @@ namespace Mono.Addins
/// </summary>
public AddinDescription Description {
get {
- if (desc != null) {
- AddinDescription d = desc.Target as AddinDescription;
- if (d != null)
- return d;
+ var addinDescription = (AddinDescription) desc?.Target;
+ if (addinDescription != null) {
+ return addinDescription;
}
var configFile = database.GetDescriptionPath (domain, id);
- AddinDescription m;
- database.ReadAddinDescription (new ConsoleProgressStatus (true), configFile, out m);
- if (m == null) {
+ database.ReadAddinDescription (new ConsoleProgressStatus (true), configFile, out addinDescription);
+
+ if (addinDescription == null) {
try {
if (File.Exists (configFile)) {
// The file is corrupted. Remove it.
@@ -257,14 +258,14 @@ namespace Mono.Addins
throw new InvalidOperationException ("Could not read add-in description");
}
if (addin == null) {
- addin = AddinInfo.ReadFromDescription (m);
- sourceFile = m.AddinFile;
+ addin = AddinInfo.ReadFromDescription (addinDescription);
+ sourceFile = addinDescription.AddinFile;
}
- SetIsUserAddin (m);
+ SetIsUserAddin (addinDescription);
if (!isUserAddin.Value)
- m.Flags |= AddinFlags.CantUninstall;
- desc = new WeakReference (m);
- return m;
+ addinDescription.Flags |= AddinFlags.CantUninstall;
+ desc = new WeakReference (addinDescription);
+ return addinDescription;
}
}
diff --git a/Mono.Addins/Mono.Addins/AddinEngine.cs b/Mono.Addins/Mono.Addins/AddinEngine.cs
index 9b361a0..3bfe794 100644
--- a/Mono.Addins/Mono.Addins/AddinEngine.cs
+++ b/Mono.Addins/Mono.Addins/AddinEngine.cs
@@ -37,7 +37,7 @@ using Mono.Addins.Description;
using Mono.Addins.Database;
using Mono.Addins.Localization;
using System.Collections.Generic;
-using System.Diagnostics;
+using System.Collections.Immutable;
namespace Mono.Addins
{
@@ -60,10 +60,35 @@ namespace Mono.Addins
IAddinInstaller installer;
bool checkAssemblyLoadConflicts;
- Dictionary<string,RuntimeAddin> loadedAddins = new Dictionary<string,RuntimeAddin> ();
- Dictionary<string,ExtensionNodeSet> nodeSets = new Dictionary<string, ExtensionNodeSet> ();
- Dictionary<string,string> autoExtensionTypes = new Dictionary<string, string> ();
- Dictionary<string, RuntimeAddin> assemblyResolvePaths = new Dictionary<string, RuntimeAddin>();
+
+ // This collection is only used during a transaction, so it doesn't need to be immutable
+ Dictionary<string, ExtensionNodeSet> nodeSets = new Dictionary<string, ExtensionNodeSet>();
+
+ AddinEngineSnapshot currentSnapshot = new AddinEngineSnapshot();
+
+ internal class AddinEngineSnapshot: ExtensionContextSnapshot
+ {
+ public ImmutableDictionary<string, RuntimeAddin> LoadedAddins;
+ public ImmutableDictionary<string, string> AutoExtensionTypes;
+ public ImmutableDictionary<string, RuntimeAddin> AssemblyResolvePaths;
+
+ public AddinEngineSnapshot()
+ {
+ LoadedAddins = ImmutableDictionary<string, RuntimeAddin>.Empty;
+ AutoExtensionTypes = ImmutableDictionary<string, string>.Empty;
+ AssemblyResolvePaths = ImmutableDictionary<string, RuntimeAddin>.Empty;
+ }
+
+ public override void CopyFrom(ExtensionContextSnapshot other)
+ {
+ base.CopyFrom(other);
+ var context = (AddinEngineSnapshot)other;
+ LoadedAddins = context.LoadedAddins;
+ AutoExtensionTypes = context.AutoExtensionTypes;
+ AssemblyResolvePaths = context.AssemblyResolvePaths;
+ }
+ }
+
AddinLocalizer defaultLocalizer;
IProgressStatus defaultProgressStatus = new ConsoleProgressStatus (false);
@@ -93,7 +118,28 @@ namespace Mono.Addins
public AddinEngine ()
{
}
-
+
+ internal override ExtensionContextSnapshot CreateSnapshot ()
+ {
+ return new AddinEngineSnapshot();
+ }
+
+ internal override void SetSnapshot (ExtensionContextSnapshot newSnapshot)
+ {
+ currentSnapshot = (AddinEngineSnapshot)newSnapshot;
+ base.SetSnapshot(newSnapshot);
+ }
+
+ internal override ExtensionContextTransaction BeginTransaction()
+ {
+ return new AddinEngineTransaction(this);
+ }
+
+ internal AddinEngineTransaction BeginEngineTransaction()
+ {
+ return (AddinEngineTransaction)BeginTransaction();
+ }
+
/// <summary>
/// Initializes the add-in engine
/// </summary>
@@ -246,14 +292,16 @@ namespace Mono.Addins
if (string.IsNullOrEmpty (configDir))
registry = AddinRegistry.GetGlobalRegistry (this, startupDirectory);
else
- registry = new AddinRegistry (this, configDir, startupDirectory, addinsDir, databaseDir);
+ registry = new AddinRegistry (this, configDir, startupDirectory, addinsDir, databaseDir, null);
+
+ using var transaction = BeginEngineTransaction();
if ((asmFile != null && registry.CreateHostAddinsFile (asmFile)) || registry.UnknownDomain)
- registry.Update (new ConsoleProgressStatus (false));
+ registry.Update (new ConsoleProgressStatus (false), transaction);
initialized = true;
- ActivateRoots ();
+ ActivateRoots (transaction);
OnAssemblyLoaded (null, null);
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler (OnAssemblyLoaded);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
@@ -262,25 +310,23 @@ namespace Mono.Addins
Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
- lock (LocalLock) {
- string assemblyName = args.Name;
+ string assemblyName = args.Name;
- if (assemblyResolvePaths.TryGetValue (assemblyName, out var inAddin)) {
- if (inAddin.TryGetAssembly (assemblyName, out var assembly))
- return assembly;
+ if (currentSnapshot.AssemblyResolvePaths.TryGetValue (assemblyName, out var inAddin)) {
+ if (inAddin.TryGetAssembly (assemblyName, out var assembly))
+ return assembly;
- int index = inAddin.Module.AssemblyNames.IndexOf (assemblyName);
- if (index != -1) {
- var path = inAddin.GetFilePath (inAddin.Module.Assemblies[index]);
- assembly = Assembly.LoadFrom (path);
- inAddin.RegisterAssemblyLoad (assemblyName, assembly);
+ int index = inAddin.Module.AssemblyNames.IndexOf (assemblyName);
+ if (index != -1) {
+ var path = inAddin.GetFilePath (inAddin.Module.Assemblies[index]);
+ assembly = Assembly.LoadFrom (path);
+ inAddin.RegisterAssemblyLoad (assemblyName, assembly);
- return assembly;
- }
+ return assembly;
}
-
- return null;
}
+
+ return null;
}
/// <summary>
@@ -292,7 +338,6 @@ namespace Mono.Addins
initialized = false;
AppDomain.CurrentDomain.AssemblyLoad -= new AssemblyLoadEventHandler (OnAssemblyLoaded);
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomainAssemblyResolve;
- loadedAddins = new Dictionary<string, RuntimeAddin>();
registry.Dispose ();
registry = null;
startupDirectory = null;
@@ -395,7 +440,7 @@ namespace Mono.Addins
{
ValidateAddinRoots ();
- assemblyResolvePaths.TryGetValue(asm.FullName, out var ad);
+ currentSnapshot.AssemblyResolvePaths.TryGetValue(asm.FullName, out var ad);
return ad;
}
@@ -460,44 +505,76 @@ namespace Mono.Addins
/// </returns>
public bool IsAddinLoaded (string id)
{
- CheckInitialized ();
- ValidateAddinRoots ();
- return loadedAddins.ContainsKey (Addin.GetIdName (id));
+ return IsAddinLoaded(null, id);
}
-
+
+ bool IsAddinLoaded(AddinEngineTransaction transaction, string id)
+ {
+ CheckInitialized();
+ ValidateAddinRoots();
+ 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)
{
ValidateAddinRoots ();
RuntimeAddin a;
- loadedAddins.TryGetValue (Addin.GetIdName (id), out a);
+ currentSnapshot.LoadedAddins.TryGetValue (Addin.GetIdName (id), out a);
return a;
}
-
- internal void ActivateAddin (string id)
+
+ internal RuntimeAddin GetAddin(AddinEngineTransaction transaction, string id)
{
- ActivateAddinExtensions (id);
+ ValidateAddinRoots();
+ RuntimeAddin a;
+
+ // 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)
+ transaction.AddinEngineSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a);
+ else
+ currentSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a);
+ return a;
}
-
- internal void UnloadAddin (string id)
+
+ internal RuntimeAddin GetOrLoadAddin(string id, bool throwExceptions)
{
- RemoveAddinExtensions (id);
+ ValidateAddinRoots();
+ RuntimeAddin a;
+ currentSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a);
+ if (a == null)
+ {
+ using var tr = BeginEngineTransaction();
+ LoadAddin(tr, null, id, throwExceptions);
+ tr.AddinEngineSnapshot.LoadedAddins.TryGetValue(Addin.GetIdName(id), out a);
+ }
+ return a;
+ }
+
+ internal void ActivateAddin (ExtensionContextTransaction transaction, string id)
+ {
+ ActivateAddinExtensions (transaction, id);
+ }
+
+ internal void UnloadAddin (AddinEngineTransaction transaction, string id)
+ {
+ RemoveAddinExtensions (transaction, id);
- RuntimeAddin addin = GetAddin (id);
+ RuntimeAddin addin = GetAddin (transaction, id);
if (addin != null) {
- addin.UnloadExtensions ();
- lock (LocalLock) {
- var loadedAddinsCopy = new Dictionary<string,RuntimeAddin> (loadedAddins);
- loadedAddinsCopy.Remove (Addin.GetIdName (id));
- loadedAddins = loadedAddinsCopy;
-
- foreach (var asm in addin.Module.AssemblyNames) {
- assemblyResolvePaths.Remove (asm);
- }
+ addin.UnloadExtensions (transaction);
+ transaction.AddinEngineSnapshot.LoadedAddins = transaction.AddinEngineSnapshot.LoadedAddins.Remove (Addin.GetIdName (id));
+ foreach (var asm in addin.Module.AssemblyNames) {
+ transaction.UnregisterAssemblyResolvePaths (asm);
}
- ReportAddinUnload (id);
+ transaction.ReportAddinUnload (id);
}
}
-
+
/// <summary>
/// Forces the loading of an add-in.
/// </summary>
@@ -516,52 +593,53 @@ namespace Mono.Addins
public void LoadAddin (IProgressStatus statusMonitor, string id)
{
CheckInitialized ();
- if (LoadAddin (statusMonitor, id, true)) {
- var adn = GetAddin (id);
+ using var transaction = BeginEngineTransaction();
+ if (LoadAddin (transaction, statusMonitor, id, true)) {
+ var adn = GetAddin (transaction, id);
adn.EnsureAssembliesLoaded ();
}
}
- internal bool LoadAddin (IProgressStatus statusMonitor, string id, bool throwExceptions)
+ internal bool LoadAddin (AddinEngineTransaction transaction, IProgressStatus statusMonitor, string id, bool throwExceptions)
{
try {
- lock (LocalLock) {
- if (IsAddinLoaded (id))
- return true;
-
- if (!Registry.IsAddinEnabled (id)) {
- string msg = GettextCatalog.GetString ("Disabled add-ins can't be loaded.");
- ReportError (msg, id, null, false);
- if (throwExceptions)
- throw new InvalidOperationException (msg);
- return false;
- }
+ if (IsAddinLoaded (transaction, id))
+ return true;
- var addins = new List<Addin> ();
- Stack depCheck = new Stack ();
- ResolveLoadDependencies (addins, depCheck, id, false);
- addins.Reverse ();
-
- if (statusMonitor != null)
- statusMonitor.SetMessage ("Loading Addins");
+ if (!Registry.IsAddinEnabled (id)) {
+ string msg = GettextCatalog.GetString ("Disabled add-ins can't be loaded.");
+ ReportError (msg, id, null, false);
+ if (throwExceptions)
+ throw new InvalidOperationException (msg);
+ return false;
+ }
+
+ var addins = new List<Addin> ();
+ Stack depCheck = new Stack ();
+ ResolveLoadDependencies (transaction, addins, depCheck, id, false);
+ addins.Reverse ();
- for (int n=0; n<addins.Count; n++) {
-
+ if (statusMonitor != null)
+ statusMonitor.SetMessage ("Loading Addins");
+
+ if (addins.Count > 0) {
+ for (int n = 0; n < addins.Count; n++) {
+
if (statusMonitor != null)
- statusMonitor.SetProgress ((double) n / (double)addins.Count);
-
+ statusMonitor.SetProgress ((double)n / (double)addins.Count);
+
Addin iad = addins [n];
- if (IsAddinLoaded (iad.Id))
+ if (IsAddinLoaded (transaction, iad.Id))
continue;
if (statusMonitor != null)
- statusMonitor.SetMessage (string.Format(GettextCatalog.GetString("Loading {0} add-in"), iad.Id));
-
- if (!InsertAddin (statusMonitor, iad))
+ statusMonitor.SetMessage (string.Format (GettextCatalog.GetString ("Loading {0} add-in"), iad.Id));
+
+ if (!InsertAddin (transaction, statusMonitor, iad))
return false;
}
- return true;
}
+ return true;
}
catch (Exception ex) {
ReportError ("Add-in could not be loaded: " + ex.Message, id, ex, false);
@@ -573,43 +651,38 @@ namespace Mono.Addins
}
}
- internal override void ResetCachedData ()
+ internal override void OnResetCachedData (ExtensionContextTransaction transaction)
{
- foreach (RuntimeAddin ad in loadedAddins.Values)
+ foreach (RuntimeAddin ad in transaction.AddinEngineSnapshot.LoadedAddins.Values)
ad.Addin.ResetCachedData ();
- base.ResetCachedData ();
+ base.OnResetCachedData (transaction);
}
- bool InsertAddin (IProgressStatus statusMonitor, Addin iad)
+ bool InsertAddin (AddinEngineTransaction transaction, IProgressStatus statusMonitor, Addin iad)
{
try {
- RuntimeAddin runtimeAddin = new RuntimeAddin (this);
+ RuntimeAddin runtimeAddin = new RuntimeAddin (this, iad);
+ AddinDescription description = iad.Description;
+
+ RegisterAssemblyResolvePaths (transaction, runtimeAddin, iad.Description.MainModule);
- RegisterAssemblyResolvePaths (runtimeAddin, iad.Description.MainModule);
-
- // Read the config file and load the add-in assemblies
- AddinDescription description = runtimeAddin.Load (iad);
-
// Register the add-in
- var loadedAddinsCopy = new Dictionary<string,RuntimeAddin> (loadedAddins);
- loadedAddinsCopy [Addin.GetIdName (runtimeAddin.Id)] = runtimeAddin;
- loadedAddins = loadedAddinsCopy;
+ transaction.AddinEngineSnapshot.LoadedAddins = transaction.AddinEngineSnapshot.LoadedAddins.SetItem(Addin.GetIdName (runtimeAddin.Id), runtimeAddin);
if (!AddinDatabase.RunningSetupProcess) {
// Load the extension points and other addin data
- RegisterNodeSets (iad.Id, description.ExtensionNodeSets);
+ RegisterNodeSets (transaction, iad.Id, description.ExtensionNodeSets);
foreach (ConditionTypeDescription cond in description.ConditionTypes)
- RegisterCondition (cond.Id, runtimeAddin, cond.TypeName);
+ RegisterCondition (transaction, cond.Id, runtimeAddin, cond.TypeName);
}
foreach (ExtensionPoint ep in description.ExtensionPoints)
- InsertExtensionPoint (runtimeAddin, ep);
+ InsertExtensionPoint (transaction, runtimeAddin, ep);
// Fire loaded event
- NotifyAddinLoaded (runtimeAddin);
- ReportAddinLoad (runtimeAddin.Id);
+ transaction.ReportAddinLoad (runtimeAddin);
return true;
}
catch (Exception ex) {
@@ -620,29 +693,27 @@ namespace Mono.Addins
}
}
- void RegisterAssemblyResolvePaths (RuntimeAddin addin, ModuleDescription description)
+ void RegisterAssemblyResolvePaths (AddinEngineTransaction transaction, RuntimeAddin addin, ModuleDescription description)
{
- lock (LocalLock) {
- 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.
- assemblyResolvePaths [asm] = addin;
- }
+ 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.
+ transaction.RegisterAssemblyResolvePaths (asm, addin);
}
}
-
- internal void InsertExtensionPoint (RuntimeAddin addin, ExtensionPoint ep)
- {
- CreateExtensionPoint (ep);
+
+ internal void InsertExtensionPoint (AddinEngineTransaction transaction, RuntimeAddin addin, ExtensionPoint ep)
+ {
+ CreateExtensionPoint (transaction, ep);
foreach (ExtensionNodeType nt in ep.NodeSet.NodeTypes) {
if (nt.ObjectTypeName.Length > 0) {
- RegisterAutoTypeExtensionPoint (nt.ObjectTypeName, ep.Path);
+ RegisterAutoTypeExtensionPoint (transaction, nt.ObjectTypeName, ep.Path);
}
}
}
- bool ResolveLoadDependencies (List<Addin> addins, Stack depCheck, string id, bool optional)
+ bool ResolveLoadDependencies (AddinEngineTransaction transaction, List<Addin> addins, Stack depCheck, string id, bool optional)
{
- if (IsAddinLoaded (id))
+ if (IsAddinLoaded (transaction, id))
return true;
if (depCheck.Contains (id))
@@ -671,7 +742,7 @@ namespace Mono.Addins
if (adep != null) {
try {
string adepid = Addin.GetFullId (iad.AddinInfo.Namespace, adep.AddinId, adep.Version);
- ResolveLoadDependencies (addins, depCheck, adepid, false);
+ ResolveLoadDependencies (transaction, addins, depCheck, adepid, false);
} catch (MissingDependencyException) {
if (optional)
return false;
@@ -686,7 +757,7 @@ namespace Mono.Addins
AddinDependency adep = dep as AddinDependency;
if (adep != null) {
string adepid = Addin.GetFullId (iad.Namespace, adep.AddinId, adep.Version);
- if (!ResolveLoadDependencies (addins, depCheck, adepid, true))
+ if (!ResolveLoadDependencies (transaction, addins, depCheck, adepid, true))
return false;
}
}
@@ -697,38 +768,27 @@ namespace Mono.Addins
return true;
}
- void RegisterNodeSets (string addinId, ExtensionNodeSetCollection nsets)
+ void RegisterNodeSets (ExtensionContextTransaction transaction, string addinId, ExtensionNodeSetCollection nsets)
{
- lock (LocalLock) {
- var nodeSetsCopy = new Dictionary<string,ExtensionNodeSet> (nodeSets);
- foreach (ExtensionNodeSet nset in nsets) {
- nset.SourceAddinId = addinId;
- nodeSetsCopy [nset.Id] = nset;
- }
- nodeSets = nodeSetsCopy;
+ foreach (ExtensionNodeSet nset in nsets)
+ {
+ nset.SourceAddinId = addinId;
+ nodeSets[nset.Id] = nset;
}
}
- internal void UnregisterAddinNodeSets (string addinId)
+ internal void UnregisterAddinNodeSets (ExtensionContextTransaction transaction, string addinId)
{
- lock (LocalLock) {
- var nodeSetsCopy = new Dictionary<string,ExtensionNodeSet> (nodeSets);
- foreach (var nset in nodeSetsCopy.Values.Where (n => n.SourceAddinId == addinId).ToArray ())
- nodeSetsCopy.Remove (nset.Id);
- nodeSets = nodeSetsCopy;
- }
+ foreach (var nset in nodeSets.Values.Where(n => n.SourceAddinId == addinId).ToList())
+ nodeSets.Remove(nset.Id);
}
- internal string GetNodeTypeAddin (ExtensionNodeSet nset, string type, string callingAddinId)
+ internal ExtensionNodeType FindType (ExtensionContextTransaction transaction, ExtensionNodeSet nset, string name, string callingAddinId)
{
- ExtensionNodeType nt = FindType (nset, type, callingAddinId);
- if (nt != null)
- return nt.AddinId;
- else
- return null;
+ return FindType (nodeSets, nset, name, callingAddinId);
}
-
- internal ExtensionNodeType FindType (ExtensionNodeSet nset, string name, string callingAddinId)
+
+ ExtensionNodeType FindType (Dictionary<string, ExtensionNodeSet> sets, ExtensionNodeSet nset, string name, string callingAddinId)
{
if (nset == null)
return null;
@@ -740,34 +800,37 @@ namespace Mono.Addins
foreach (string ns in nset.NodeSets) {
ExtensionNodeSet regSet;
- if (!nodeSets.TryGetValue (ns, out regSet)) {
+ if (!sets.TryGetValue (ns, out regSet)) {
ReportError ("Unknown node set: " + ns, callingAddinId, null, false);
return null;
}
- ExtensionNodeType nt = FindType (regSet, name, callingAddinId);
+ ExtensionNodeType nt = FindType (sets, regSet, name, callingAddinId);
if (nt != null)
return nt;
}
return null;
}
- internal void RegisterAutoTypeExtensionPoint (string typeName, string path)
+ internal void RegisterAutoTypeExtensionPoint (AddinEngineTransaction transaction, string typeName, string path)
{
if (Util.TryParseTypeName (typeName, out var t, out var _))
typeName = t;
- autoExtensionTypes [typeName] = path;
+ transaction.RegisterAutoTypeExtensionPoint (typeName, path);
}
- internal void UnregisterAutoTypeExtensionPoint (string typeName, string path)
+ internal void UnregisterAutoTypeExtensionPoint (AddinEngineTransaction transaction, string typeName, string path)
{
if (Util.TryParseTypeName (typeName, out var t, out var _))
typeName = t;
- autoExtensionTypes.Remove (typeName);
+ transaction.UnregisterAutoTypeExtensionPoint (typeName);
}
-
+
+ /// <summary>
+ /// Returns an extension point identified by a type
+ /// </summary>
internal string GetAutoTypeExtensionPoint (Type type)
{
- autoExtensionTypes.TryGetValue (type.FullName, out var path);
+ currentSnapshot.AutoExtensionTypes.TryGetValue (type.FullName, out var path);
return path;
}
@@ -792,20 +855,22 @@ namespace Mono.Addins
}
}
if (copy != null) {
+ using var tr = BeginEngineTransaction();
foreach (Assembly asm in copy)
- CheckHostAssembly (asm);
+ CheckHostAssembly (tr, asm);
}
}
- internal void ActivateRoots ()
+ internal void ActivateRoots (AddinEngineTransaction transaction)
{
lock (pendingRootChecks)
pendingRootChecks.Clear ();
+
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
- CheckHostAssembly (asm);
+ CheckHostAssembly (transaction, asm);
}
- void CheckHostAssembly (Assembly asm)
+ void CheckHostAssembly (AddinEngineTransaction transaction, Assembly asm)
{
if (AddinDatabase.RunningSetupProcess || asm is System.Reflection.Emit.AssemblyBuilder || asm.IsDynamic)
return;
@@ -832,7 +897,7 @@ namespace Mono.Addins
ainfo = Registry.GetAddinForHostAssembly (asmFile);
}
- if (ainfo != null && !IsAddinLoaded (ainfo.Id)) {
+ if (ainfo != null && !IsAddinLoaded (transaction, ainfo.Id)) {
AddinDescription adesc = null;
try {
adesc = ainfo.Description;
@@ -843,12 +908,12 @@ namespace Mono.Addins
// If the add-in has changed, update the add-in database.
// We do it here because once loaded, add-in roots can't be
// reloaded like regular add-ins.
- Registry.Update (null);
+ Registry.Update (null, transaction);
ainfo = Registry.GetAddinForHostAssembly (asmFile);
if (ainfo == null)
return;
}
- LoadAddin (null, ainfo.Id, false);
+ LoadAddin (transaction, null, ainfo.Id, false);
}
}
@@ -885,41 +950,35 @@ namespace Mono.Addins
}
}
- internal void ReportAddinLoad (string id)
+ internal void ReportAddinLoad (RuntimeAddin addin)
{
- var handler = AddinLoaded;
- if (handler != null) {
- try {
- handler (null, new AddinEventArgs (id));
- } catch {
- // Ignore subscriber exceptions
- }
+ NotifyAddinLoaded (addin);
+ if (AddinLoaded != null) {
+ InvokeCallback(() =>
+ {
+ AddinLoaded?.Invoke(this, new AddinEventArgs(addin.Id));
+ },null);
}
}
internal void ReportAddinUnload (string id)
{
- var handler = AddinUnloaded;
- if (handler != null) {
- try {
- handler (null, new AddinEventArgs (id));
- } catch {
- // Ignore subscriber exceptions
- }
+ if (AddinUnloaded != null) {
+ InvokeCallback(() =>
+ {
+ AddinUnloaded?.Invoke(this, new AddinEventArgs(id));
+ },null);
}
}
internal void ReportAddinAssembliesLoad (string id)
{
- var handler = AddinAssembliesLoaded;
- if (handler != null) {
- try {
- handler (null, new AddinEventArgs (id));
- } catch {
- // Ignore subscriber exceptions
- }
+ if (AddinAssembliesLoaded != null) {
+ InvokeCallback(() =>
+ {
+ AddinAssembliesLoaded?.Invoke(this, new AddinEventArgs(id));
+ },null);
}
}
}
-
}
diff --git a/Mono.Addins/Mono.Addins/AddinInfo.cs b/Mono.Addins/Mono.Addins/AddinInfo.cs
index b1125f7..eb8f127 100644
--- a/Mono.Addins/Mono.Addins/AddinInfo.cs
+++ b/Mono.Addins/Mono.Addins/AddinInfo.cs
@@ -32,6 +32,8 @@ using System.Collections;
using System.Xml;
using System.Xml.Serialization;
using Mono.Addins.Description;
+using System.Collections.Immutable;
+using System.Linq;
namespace Mono.Addins
{
@@ -49,14 +51,14 @@ namespace Mono.Addins
string category = "";
bool defaultEnabled = true;
bool isroot;
- DependencyCollection dependencies;
- DependencyCollection optionalDependencies;
+ ImmutableArray<Dependency> dependencies;
+ ImmutableArray<Dependency> optionalDependencies;
AddinPropertyCollection properties;
private AddinInfo ()
{
- dependencies = new DependencyCollection ();
- optionalDependencies = new DependencyCollection ();
+ dependencies = ImmutableArray<Dependency>.Empty;
+ optionalDependencies = ImmutableArray<Dependency>.Empty;
}
public string Id {
@@ -65,17 +67,14 @@ namespace Mono.Addins
public string LocalId {
get { return id; }
- set { id = value; }
}
public string Namespace {
get { return namspace; }
- set { namspace = value; }
}
public bool IsRoot {
get { return isroot; }
- set { isroot = value; }
}
public string Name {
@@ -90,17 +89,14 @@ namespace Mono.Addins
sid = sid.Substring (2);
return Addin.GetFullId (namspace, sid, null);
}
- set { name = value; }
}
public string Version {
get { return version; }
- set { version = value; }
}
public string BaseVersion {
get { return baseVersion; }
- set { baseVersion = value; }
}
public string Author {
@@ -110,7 +106,6 @@ namespace Mono.Addins
return s;
return author;
}
- set { author = value; }
}
public string Copyright {
@@ -120,7 +115,6 @@ namespace Mono.Addins
return s;
return copyright;
}
- set { copyright = value; }
}
public string Url {
@@ -130,7 +124,6 @@ namespace Mono.Addins
return s;
return url;
}
- set { url = value; }
}
public string Description {
@@ -140,7 +133,6 @@ namespace Mono.Addins
return s;
return description;
}
- set { description = value; }
}
public string Category {
@@ -150,19 +142,17 @@ namespace Mono.Addins
return s;
return category;
}
- set { category = value; }
}
public bool EnabledByDefault {
get { return defaultEnabled; }
- set { defaultEnabled = value; }
}
- public DependencyCollection Dependencies {
+ public ImmutableArray<Dependency> Dependencies {
get { return dependencies; }
}
- public DependencyCollection OptionalDependencies {
+ public ImmutableArray<Dependency> OptionalDependencies {
get { return optionalDependencies; }
}
@@ -185,14 +175,9 @@ namespace Mono.Addins
info.baseVersion = description.CompatVersion;
info.isroot = description.IsRoot;
info.defaultEnabled = description.EnabledByDefault;
-
- foreach (Dependency dep in description.MainModule.Dependencies)
- info.Dependencies.Add (dep);
-
- foreach (ModuleDescription mod in description.OptionalModules) {
- foreach (Dependency dep in mod.Dependencies)
- info.OptionalDependencies.Add (dep);
- }
+
+ info.dependencies = ImmutableArray<Dependency>.Empty.AddRange (description.MainModule.Dependencies);
+ info.optionalDependencies = ImmutableArray<Dependency>.Empty.AddRange (description.OptionalModules.SelectMany(module => module.Dependencies));
info.properties = description.Properties;
return info;
diff --git a/Mono.Addins/Mono.Addins/AddinManager.cs b/Mono.Addins/Mono.Addins/AddinManager.cs
index b5bef3f..663e8fa 100644
--- a/Mono.Addins/Mono.Addins/AddinManager.cs
+++ b/Mono.Addins/Mono.Addins/AddinManager.cs
@@ -155,7 +155,8 @@ namespace Mono.Addins
/// </summary>
public static void Shutdown ()
{
- AddinEngine.Shutdown ();
+ sessionService?.Shutdown ();
+ sessionService = null;
}
/// <summary>
@@ -688,7 +689,7 @@ namespace Mono.Addins
AddinEngine.CheckInitialized ();
return AddinEngine.GetExtensionObjects<T> (path, reuseCachedInstance);
}
-
+
/// <summary>
/// Extension change event.
/// </summary>
@@ -698,6 +699,9 @@ namespace Mono.Addins
/// it does not provide information about what changed. Hosts subscribing to
/// this event should get the new list of nodes using a query method such as
/// AddinManager.GetExtensionNodes() and then update whatever needs to be updated.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined.
+ /// Events in this class are guaranteed to be raised sequentially, never concurrently.
/// </remarks>
public static event ExtensionEventHandler ExtensionChanged {
add { AddinEngine.CheckInitialized(); AddinEngine.ExtensionChanged += value; }
@@ -720,6 +724,9 @@ namespace Mono.Addins
/// (Add or Remove) and the extension node added or removed.
///
/// NOTE: The handler will be called for all nodes existing in the path at the moment of registration.
+ ///
+ /// Threading information: the thread on which the handler is executed is undefined.
+ /// Handlers and events are guaranteed to be executed sequentially, never concurrently.
/// </remarks>
public static void AddExtensionNodeHandler (string path, ExtensionNodeEventHandler handler)
{
@@ -761,6 +768,9 @@ namespace Mono.Addins
/// (Add or Remove) and the extension node added or removed.
///
/// NOTE: The handler will be called for all nodes existing in the path at the moment of registration.
+ ///
+ /// Threading information: the thread on which the handler is executed is undefined.
+ /// Handlers and events are guaranteed to be executed sequentially, never concurrently.
/// </remarks>
public static void AddExtensionNodeHandler (Type instanceType, ExtensionNodeEventHandler handler)
{
@@ -782,35 +792,44 @@ namespace Mono.Addins
AddinEngine.CheckInitialized ();
AddinEngine.RemoveExtensionNodeHandler (instanceType, handler);
}
-
+
/// <summary>
/// Add-in loading error event.
/// </summary>
/// <remarks>
/// This event is fired when there is an error when loading the extension
/// of an add-in, or any other kind of error that may happen when querying extension points.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined.
+ /// Events in this class are guaranteed to be raised sequentially, never concurrently.
/// </remarks>
public static event AddinErrorEventHandler AddinLoadError {
add { AddinEngine.AddinLoadError += value; }
remove { AddinEngine.AddinLoadError -= value; }
}
-
+
/// <summary>
/// Add-in loaded event.
/// </summary>
/// <remarks>
/// Fired after loading an add-in in memory.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined.
+ /// Events in this class are guaranteed to be raised sequentially, never concurrently.
/// </remarks>
public static event AddinEventHandler AddinLoaded {
add { AddinEngine.AddinLoaded += value; }
remove { AddinEngine.AddinLoaded -= value; }
}
-
+
/// <summary>
/// Add-in unload event.
/// </summary>
/// <remarks>
/// Fired when an add-in is unloaded from memory. It may happen an add-in is disabled or uninstalled.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined.
+ /// Events in this class are guaranteed to be raised sequentially, never concurrently.
/// </remarks>
public static event AddinEventHandler AddinUnloaded {
add { AddinEngine.AddinUnloaded += value; }
@@ -822,6 +841,9 @@ namespace Mono.Addins
/// </summary>
/// <remarks>
/// Fired when the add-in assemblies are loaded.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined.
+ /// Events in this class are guaranteed to be raised sequentially, never concurrently.
/// </remarks>
public static event AddinEventHandler AddinAssembliesLoaded {
add { AddinEngine.AddinAssembliesLoaded += value; }
diff --git a/Mono.Addins/Mono.Addins/AddinRegistry.cs b/Mono.Addins/Mono.Addins/AddinRegistry.cs
index 0e1ef2a..944d96e 100644
--- a/Mono.Addins/Mono.Addins/AddinRegistry.cs
+++ b/Mono.Addins/Mono.Addins/AddinRegistry.cs
@@ -67,13 +67,13 @@ namespace Mono.Addins
/// </remarks>
public class AddinRegistry: IDisposable
{
- AddinDatabase database;
- StringCollection addinDirs;
- string basePath;
+ readonly AddinDatabase database;
+ readonly string [] addinDirs;
+ readonly string basePath;
+ readonly string startupDirectory;
+ readonly string addinsDir;
+ readonly string databaseDir;
string currentDomain;
- string startupDirectory;
- string addinsDir;
- string databaseDir;
/// <summary>
/// Initializes a new instance.
@@ -92,7 +92,7 @@ namespace Mono.Addins
/// of the Environment.SpecialFolder enumeration can be used (always between square
/// brackets)
/// </remarks>
- public AddinRegistry (string registryPath): this (null, registryPath, null, null, null)
+ public AddinRegistry (string registryPath): this (null, registryPath, null, null, null, null)
{
}
@@ -116,7 +116,7 @@ namespace Mono.Addins
/// of the Environment.SpecialFolder enumeration can be used (always between square
/// brackets)
/// </remarks>
- public AddinRegistry (string registryPath, string startupDirectory): this (null, registryPath, startupDirectory, null, null)
+ public AddinRegistry (string registryPath, string startupDirectory): this (null, registryPath, startupDirectory, null, null, null)
{
}
@@ -145,7 +145,7 @@ namespace Mono.Addins
/// of the Environment.SpecialFolder enumeration can be used (always between square
/// brackets)
/// </remarks>
- public AddinRegistry (string registryPath, string startupDirectory, string addinsDir): this (null, registryPath, startupDirectory, addinsDir, null)
+ public AddinRegistry (string registryPath, string startupDirectory, string addinsDir): this (null, registryPath, startupDirectory, addinsDir, null, null)
{
}
@@ -179,11 +179,11 @@ namespace Mono.Addins
/// of the Environment.SpecialFolder enumeration can be used (always between square
/// brackets)
/// </remarks>
- public AddinRegistry (string registryPath, string startupDirectory, string addinsDir, string databaseDir): this (null, registryPath, startupDirectory, addinsDir, databaseDir)
+ public AddinRegistry (string registryPath, string startupDirectory, string addinsDir, string databaseDir): this (null, registryPath, startupDirectory, addinsDir, databaseDir, null)
{
}
- internal AddinRegistry (AddinEngine engine, string registryPath, string startupDirectory, string addinsDir, string databaseDir)
+ internal AddinRegistry (AddinEngine engine, string registryPath, string startupDirectory, string addinsDir, string databaseDir, string additionalGlobalAddinDirectory)
{
basePath = Path.GetFullPath (Util.NormalizePath (registryPath));
@@ -208,8 +208,10 @@ namespace Mono.Addins
// Look for add-ins in the hosts directory and in the default
// addins directory
- addinDirs = new StringCollection ();
- addinDirs.Add (DefaultAddinsFolder);
+ if (additionalGlobalAddinDirectory != null)
+ addinDirs = new [] { DefaultAddinsFolder, additionalGlobalAddinDirectory };
+ else
+ addinDirs = new [] { DefaultAddinsFolder };
// Initialize the database after all paths have been set
database = new AddinDatabase (engine, this);
@@ -239,15 +241,15 @@ namespace Mono.Addins
internal static AddinRegistry GetGlobalRegistry (AddinEngine engine, string startupDirectory)
{
- AddinRegistry reg = new AddinRegistry (engine, GlobalRegistryPath, startupDirectory, null, null);
string baseDir;
if (Util.IsWindows)
baseDir = Environment.GetFolderPath (Environment.SpecialFolder.CommonProgramFiles);
else
baseDir = "/etc";
-
- reg.GlobalAddinDirectories.Add (Path.Combine (baseDir, "mono.addins"));
- return reg;
+
+ var globalDir = Path.Combine (baseDir, "mono.addins");
+
+ return new AddinRegistry (engine, GlobalRegistryPath, startupDirectory, null, null, globalDir);
}
internal bool UnknownDomain {
@@ -637,6 +639,11 @@ namespace Mono.Addins
database.Update (monitor, currentDomain);
}
+ internal void Update(IProgressStatus monitor, AddinEngineTransaction addinEngineTransaction)
+ {
+ database.Update(monitor, currentDomain, addinEngineTransaction:addinEngineTransaction);
+ }
+
/// <summary>
/// Regenerates the cached data of the add-in registry.
/// </summary>
@@ -743,10 +750,15 @@ namespace Mono.Addins
get { return databaseDir; }
}
- internal StringCollection GlobalAddinDirectories {
+ internal IEnumerable<string> GlobalAddinDirectories {
get { return addinDirs; }
}
+ internal void RegisterGlobalAddinDirectory (string dir)
+ {
+
+ }
+
internal string StartupDirectory {
get {
return startupDirectory;
diff --git a/Mono.Addins/Mono.Addins/ConditionType.cs b/Mono.Addins/Mono.Addins/ConditionType.cs
index 4f7b0db..c5937f6 100644
--- a/Mono.Addins/Mono.Addins/ConditionType.cs
+++ b/Mono.Addins/Mono.Addins/ConditionType.cs
@@ -210,7 +210,8 @@ namespace Mono.Addins
if (!string.IsNullOrEmpty (addin)) {
// Make sure the add-in that implements the condition is loaded
- addinEngine.LoadAddin (null, addin, true);
+ 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 7b510a3..7d03a1e 100644
--- a/Mono.Addins/Mono.Addins/ExtensionContext.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionContext.cs
@@ -30,35 +30,58 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Collections.Specialized;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
using Mono.Addins.Description;
namespace Mono.Addins
{
- /// <summary>
- /// An extension context.
- /// </summary>
- /// <remarks>
- /// Extension contexts can be used to query the extension tree
- /// using particular condition values. Extension points which
- /// declare the availability of a condition type can only be
- /// queryed using an extension context which provides an
- /// evaluator for that condition.
- /// </remarks>
- public class ExtensionContext
+ /// <summary>
+ /// An extension context.
+ /// </summary>
+ /// <remarks>
+ /// Extension contexts can be used to query the extension tree
+ /// using particular condition values. Extension points which
+ /// declare the availability of a condition type can only be
+ /// queryed using an extension context which provides an
+ /// evaluator for that condition.
+ /// </remarks>
+ public class ExtensionContext
{
internal object LocalLock = new object ();
- Hashtable conditionTypes = new Hashtable ();
- Hashtable conditionsToNodes = new Hashtable ();
- List<WeakReference> childContexts;
+ ImmutableArray<WeakReference> childContexts = ImmutableArray<WeakReference>.Empty;
ExtensionContext parentContext;
ExtensionTree tree;
- bool fireEvents = false;
-
- List<string> runTimeEnabledAddins;
- List<string> runTimeDisabledAddins;
-
+
+ NotificationQueue notificationQueue;
+
+ // runTimeEnabledAddins and runTimeDisabledAddins are modified only within a transaction,
+ // so they don't need to be immutable and don't need to be in the snapshot
+ HashSet<string> runTimeEnabledAddins = new HashSet<string>();
+ HashSet<string> runTimeDisabledAddins = new HashSet<string>();
+
+ ExtensionContextSnapshot currentSnapshot = new ExtensionContextSnapshot();
+
+ internal class ExtensionContextSnapshot
+ {
+ public ImmutableDictionary<string, ConditionInfo> ConditionTypes;
+ public ImmutableDictionary<BaseCondition, ImmutableArray<TreeNode>> ConditionsToNodes;
+
+ public ExtensionContextSnapshot()
+ {
+ ConditionTypes = ImmutableDictionary<string, ConditionInfo>.Empty;
+ ConditionsToNodes = ImmutableDictionary<BaseCondition, ImmutableArray<TreeNode>>.Empty;
+ }
+
+ public virtual void CopyFrom(ExtensionContextSnapshot other)
+ {
+ ConditionTypes = other.ConditionTypes;
+ ConditionsToNodes = other.ConditionsToNodes;
+ }
+ }
+
/// <summary>
/// Extension change event.
/// </summary>
@@ -68,15 +91,31 @@ namespace Mono.Addins
/// it does not provide information about what changed. Hosts subscribing to
/// this event should get the new list of nodes using a query method such as
/// AddinManager.GetExtensionNodes() and then update whatever needs to be updated.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined. Events are
+ /// guaranteed to be raised sequentially for a given extension context.
/// </remarks>
public event ExtensionEventHandler ExtensionChanged;
internal void Initialize (AddinEngine addinEngine)
{
- fireEvents = false;
+ notificationQueue = new NotificationQueue(addinEngine);
+ SetSnapshot(CreateSnapshot());
tree = new ExtensionTree (addinEngine, this);
}
+ internal virtual ExtensionContextSnapshot CreateSnapshot()
+ {
+ return new ExtensionContextSnapshot();
+ }
+
+ internal virtual void SetSnapshot(ExtensionContextSnapshot newSnapshot)
+ {
+ currentSnapshot = newSnapshot;
+ }
+
+ internal ExtensionContextSnapshot CurrentSnapshot => currentSnapshot;
+
#pragma warning disable 1591
[ObsoleteAttribute]
protected void Clear ()
@@ -84,52 +123,81 @@ namespace Mono.Addins
}
#pragma warning restore 1591
+ internal void InvokeCallback(Action action, object source)
+ {
+ notificationQueue.Invoke(action, source);
+ }
internal void ClearContext ()
{
- conditionTypes.Clear ();
- conditionsToNodes.Clear ();
- childContexts = null;
+ SetSnapshot(CreateSnapshot());
+ childContexts = ImmutableArray<WeakReference>.Empty;
parentContext = null;
tree = null;
- runTimeEnabledAddins = null;
- runTimeDisabledAddins = null;
}
-
+
internal AddinEngine AddinEngine {
get { return tree.AddinEngine; }
}
void CleanDisposedChildContexts ()
{
- if (childContexts != null)
- childContexts.RemoveAll (w => w.Target == null);
+ var list = childContexts;
+ List<WeakReference> toRemove = null;
+
+ for (int n = 0; n < list.Length; n++) {
+ if (list [n].Target == null) {
+ // Create the list only if there is something to remove
+ if (toRemove == null)
+ toRemove = new List<WeakReference> ();
+ toRemove.Add (list [n]);
+ }
+ }
+ if (toRemove != null) {
+ // Removing the stale contexts is not urgent, so if the lock can't be acquired now
+ // it is ok to just skip the clean up and try later
+ if (Monitor.TryEnter(LocalLock)) {
+ try {
+ childContexts = childContexts.RemoveRange (toRemove);
+ } finally {
+ Monitor.Exit (LocalLock);
+ }
+ }
+ }
}
-
- internal virtual void ResetCachedData ()
+
+ internal void ResetCachedData(ExtensionContextTransaction transaction = null)
{
- tree.ResetCachedData ();
- if (childContexts != null) {
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.ResetCachedData ();
- }
+ var currentTransaction = transaction ?? BeginTransaction();
+ try
+ {
+ OnResetCachedData(currentTransaction);
}
+ finally
+ {
+ if (currentTransaction != transaction)
+ currentTransaction.Dispose();
+ }
+ }
+
+ internal virtual void OnResetCachedData (ExtensionContextTransaction transaction)
+ {
+ tree.ResetCachedData (transaction);
+
+ foreach (var ctx in GetActiveChildContexes())
+ ctx.ResetCachedData ();
}
internal ExtensionContext CreateChildContext ()
{
- lock (conditionTypes) {
- if (childContexts == null)
- childContexts = new List<WeakReference> ();
- else
- CleanDisposedChildContexts ();
- ExtensionContext ctx = new ExtensionContext ();
- ctx.Initialize (AddinEngine);
- ctx.parentContext = this;
- WeakReference wref = new WeakReference (ctx);
- childContexts.Add (wref);
+ ExtensionContext ctx = new ExtensionContext ();
+ ctx.Initialize (AddinEngine);
+ ctx.parentContext = this;
+ WeakReference wref = new WeakReference (ctx);
+
+ lock (LocalLock) {
+ CleanDisposedChildContexts ();
+ childContexts = childContexts.Add (wref);
return ctx;
}
}
@@ -150,13 +218,9 @@ namespace Mono.Addins
/// </remarks>
public void RegisterCondition (string id, ConditionType type)
{
+ using var transaction = BeginTransaction ();
type.Id = id;
- ConditionInfo info = CreateConditionInfo (id);
- ConditionType ot = info.CondType as ConditionType;
- if (ot != null)
- ot.Changed -= new EventHandler (OnConditionChanged);
- info.CondType = type;
- type.Changed += new EventHandler (OnConditionChanged);
+ GetOrCreateConditionInfo (transaction, id, type);
}
/// <summary>
@@ -174,63 +238,74 @@ namespace Mono.Addins
/// </remarks>
public void RegisterCondition (string id, Type type)
{
+ using var transaction = BeginTransaction ();
+
// Allows delayed creation of condition types
- ConditionInfo info = CreateConditionInfo (id);
- ConditionType ot = info.CondType as ConditionType;
- if (ot != null)
- ot.Changed -= new EventHandler (OnConditionChanged);
- info.CondType = type;
+ GetOrCreateConditionInfo (transaction, id, type);
}
- internal void RegisterCondition (string id, RuntimeAddin addin, string typeName)
+ internal void RegisterCondition (ExtensionContextTransaction transaction, string id, RuntimeAddin addin, string typeName)
{
// Allows delayed creation of condition types
- ConditionInfo info = CreateConditionInfo (id);
- ConditionType ot = info.CondType as ConditionType;
- if (ot != null)
- ot.Changed -= new EventHandler (OnConditionChanged);
- info.CondType = new ConditionTypeData {
+ GetOrCreateConditionInfo (transaction, id, new ConditionTypeData {
TypeName = typeName,
Addin = addin
- };
+ });
}
- ConditionInfo CreateConditionInfo (string id)
+ internal ConditionInfo GetOrCreateConditionInfo (ExtensionContextTransaction transaction, string id, object conditionTypeObject)
{
- ConditionInfo info = conditionTypes [id] as ConditionInfo;
- if (info == null) {
+ if (!transaction.Snapshot.ConditionTypes.TryGetValue (id, out var info)) {
info = new ConditionInfo ();
- conditionTypes [id] = info;
+ info.CondType = conditionTypeObject;
+ transaction.Snapshot.ConditionTypes = transaction.Snapshot.ConditionTypes.Add (id, info);
+ } else {
+ // If CondType is not changing, nothing else to do
+ if (conditionTypeObject == null)
+ return info;
+
+ // Replace the old CondType
+ var oldType = info.CondType as ConditionType;
+ info.CondType = conditionTypeObject;
+ if (oldType != null)
+ oldType.Changed -= new EventHandler (OnConditionChanged);
}
+ if (conditionTypeObject is ConditionType conditionType)
+ conditionType.Changed += new EventHandler (OnConditionChanged);
return info;
}
- internal bool FireEvents {
- get { return fireEvents; }
- }
-
internal ConditionType GetCondition (string id)
{
- ConditionInfo info = (ConditionInfo) conditionTypes [id];
-
- if (info != null) {
+ if (currentSnapshot.ConditionTypes.TryGetValue(id, out var info)) {
if (info.CondType is ConditionType condition) {
return condition;
}
else {
- // The condition was registered as a type, create an instance now
- Type type;
- if (info.CondType is ConditionTypeData data) {
- type = data.Addin.GetType (data.TypeName, true);
- } else
- type = info.CondType as Type;
-
- if (type != null) {
- var ct = (ConditionType)Activator.CreateInstance (type);
- ct.Id = id;
- ct.Changed += new EventHandler (OnConditionChanged);
- info.CondType = ct;
- return ct;
+ // The condition needs to be instantiated in a lock, to avoid duplicate
+ // creation if two threads are trying to get it
+
+ lock (LocalLock) {
+
+ // Check again from inside the lock (maybe another thread already created the condition)
+ if (info.CondType is ConditionType cond)
+ return cond;
+
+ // The condition was registered as a type, create an instance now
+
+ Type type;
+ if (info.CondType is ConditionTypeData data) {
+ type = data.Addin.GetType (data.TypeName, true);
+ } else
+ type = info.CondType as Type;
+
+ if (type != null) {
+ var ct = (ConditionType)Activator.CreateInstance (type);
+ ct.Id = id;
+ ct.Changed += new EventHandler (OnConditionChanged);
+ info.CondType = ct;
+ return ct;
+ }
}
}
}
@@ -240,47 +315,8 @@ namespace Mono.Addins
else
return null;
}
-
- internal void RegisterNodeCondition (TreeNode node, BaseCondition cond)
- {
- var list = (List<TreeNode>) conditionsToNodes [cond];
- if (list == null) {
- list = new List<TreeNode> ();
- conditionsToNodes [cond] = list;
- var conditionTypeIds = new List<string> ();
- cond.GetConditionTypes (conditionTypeIds);
-
- foreach (string cid in conditionTypeIds) {
-
- ConditionInfo info = CreateConditionInfo (cid);
- if (info.BoundConditions == null)
- info.BoundConditions = new List<BaseCondition> ();
-
- info.BoundConditions.Add (cond);
- }
- }
- list.Add (node);
- }
-
- internal void UnregisterNodeCondition (TreeNode node, BaseCondition cond)
- {
- var list = (List<TreeNode>) conditionsToNodes [cond];
- if (list == null)
- return;
-
- list.Remove (node);
- if (list.Count == 0) {
- conditionsToNodes.Remove (cond);
- var conditionTypeIds = new List<string> ();
- cond.GetConditionTypes (conditionTypeIds);
- foreach (string cid in conditionTypes.Keys) {
- ConditionInfo info = conditionTypes [cid] as ConditionInfo;
- if (info != null && info.BoundConditions != null)
- info.BoundConditions.Remove (cond);
- }
- }
- }
-
+
+
/// <summary>
/// Returns the extension node in a path
/// </summary>
@@ -388,7 +424,7 @@ namespace Mono.Addins
{
string path = AddinEngine.GetAutoTypeExtensionPoint (instanceType);
if (path == null)
- return new ExtensionNodeList (null);
+ return ExtensionNodeList.Empty;
return GetExtensionNodes (path, expectedNodeType);
}
@@ -434,10 +470,10 @@ namespace Mono.Addins
public ExtensionNodeList GetExtensionNodes (string path, Type expectedNodeType)
{
TreeNode node = GetNode (path);
- if (node == null || node.ExtensionNode == null)
+ if (node == null || !node.HasExtensionNode)
return ExtensionNodeList.Empty;
- ExtensionNodeList list = node.ExtensionNode.ChildNodes;
+ ExtensionNodeList list = node.ExtensionNode.GetChildNodes();
if (expectedNodeType != null) {
bool foundError = false;
@@ -772,251 +808,200 @@ namespace Mono.Addins
throw new InvalidOperationException ("Type '" + instanceType + "' not bound to an extension point.");
RemoveExtensionNodeHandler (path, handler);
}
+
+ internal virtual ExtensionContextTransaction BeginTransaction ()
+ {
+ return new ExtensionContextTransaction (this);
+ }
+
+ internal bool IsCurrentThreadInTransaction => Monitor.IsEntered (LocalLock);
void OnConditionChanged (object s, EventArgs a)
{
ConditionType cond = (ConditionType) s;
NotifyConditionChanged (cond);
}
-
- internal void NotifyConditionChanged (ConditionType cond)
+
+ void NotifyConditionChanged (ConditionType cond)
{
- try {
- fireEvents = true;
-
- ConditionInfo info = (ConditionInfo) conditionTypes [cond.Id];
- if (info != null && info.BoundConditions != null) {
- Hashtable parentsToNotify = new Hashtable ();
- foreach (BaseCondition c in info.BoundConditions) {
- var nodeList = (List<TreeNode>) conditionsToNodes [c];
- if (nodeList != null) {
- foreach (TreeNode node in nodeList)
- parentsToNotify [node.Parent] = null;
- }
- }
- foreach (TreeNode node in parentsToNotify.Keys) {
- if (node.NotifyChildrenChanged ())
- NotifyExtensionsChanged (new ExtensionEventArgs (node.GetPath ()));
+ HashSet<TreeNode> parentsToNotify = null;
+
+ var snapshot = currentSnapshot;
+
+ if (snapshot.ConditionTypes.TryGetValue (cond.Id, out var info) && info.BoundConditions != null) {
+ parentsToNotify = new HashSet<TreeNode> ();
+ foreach (BaseCondition c in info.BoundConditions) {
+ if (snapshot.ConditionsToNodes.TryGetValue(c, out var nodeList)) {
+ parentsToNotify.UnionWith (nodeList.Select (node => node.Parent));
}
}
}
- finally {
- fireEvents = false;
- }
- // Notify child contexts
- lock (conditionTypes) {
- if (childContexts != null) {
- CleanDisposedChildContexts ();
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.NotifyConditionChanged (cond);
- }
+ if (parentsToNotify != null) {
+ foreach (TreeNode node in parentsToNotify) {
+ if (node.NotifyChildrenChanged ())
+ NotifyExtensionsChanged (new ExtensionEventArgs (node.GetPath ()));
}
}
+
+ foreach (var ctx in GetActiveChildContexes ())
+ ctx.NotifyConditionChanged (cond);
}
-
- internal void NotifyExtensionsChanged (ExtensionEventArgs args)
+ IEnumerable<ExtensionContext> GetActiveChildContexes ()
{
- if (!fireEvents)
- return;
+ // Collect a list of child contexts that are still referenced
+ if (childContexts.Length > 0) {
+ CleanDisposedChildContexts ();
+ return childContexts.Select (t => (ExtensionContext)t.Target).Where (t => t != null);
+ } else
+ return Array.Empty<ExtensionContext> ();
+ }
+
+ internal void NotifyExtensionsChanged (ExtensionEventArgs args)
+ {
if (ExtensionChanged != null)
- ExtensionChanged (this, args);
+ {
+ notificationQueue.Invoke(() =>
+ {
+ ExtensionChanged?.Invoke(this, args);
+ }, null);
+ }
}
internal void NotifyAddinLoaded (RuntimeAddin ad)
{
tree.NotifyAddinLoaded (ad, true);
- lock (conditionTypes) {
- if (childContexts != null) {
- CleanDisposedChildContexts ();
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.NotifyAddinLoaded (ad);
- }
- }
- }
+ foreach (var ctx in GetActiveChildContexes())
+ ctx.NotifyAddinLoaded (ad);
}
- internal void CreateExtensionPoint (ExtensionPoint ep)
+ internal void CreateExtensionPoint (ExtensionContextTransaction transaction, ExtensionPoint ep)
{
- TreeNode node = tree.GetNode (ep.Path, true);
+ TreeNode node = tree.GetNode (ep.Path, true, transaction);
if (node.ExtensionPoint == null) {
node.ExtensionPoint = ep;
node.ExtensionNodeSet = ep.NodeSet;
}
}
- internal void ActivateAddinExtensions (string id)
+ internal void ActivateAddinExtensions (ExtensionContextTransaction transaction, string id)
{
// Looks for loaded extension points which are extended by the provided
// add-in, and adds the new nodes
- try {
- fireEvents = true;
-
- Addin addin = AddinEngine.Registry.GetAddin (id);
- if (addin == null) {
- AddinEngine.ReportError ("Required add-in not found", id, null, false);
- return;
- }
- // Take note that this add-in has been enabled at run-time
- // Needed because loaded add-in descriptions may not include this add-in.
- RegisterRuntimeEnabledAddin (id);
+ Addin addin = AddinEngine.Registry.GetAddin (id);
+ if (addin == null) {
+ AddinEngine.ReportError ("Required add-in not found", id, null, false);
+ return;
+ }
+ // Take note that this add-in has been enabled at run-time
+ // Needed because loaded add-in descriptions may not include this add-in.
+ RegisterRuntimeEnabledAddin (transaction, id);
- // Look for loaded extension points
- Hashtable eps = new Hashtable ();
- var newExtensions = new List<string> ();
- foreach (ModuleDescription mod in addin.Description.AllModules) {
- foreach (Extension ext in mod.Extensions) {
- if (!newExtensions.Contains (ext.Path))
- newExtensions.Add (ext.Path);
- ExtensionPoint ep = tree.FindLoadedExtensionPoint (ext.Path);
- if (ep != null && !eps.Contains (ep))
- eps.Add (ep, ep);
- }
+ // Look for loaded extension points
+ Hashtable eps = new Hashtable ();
+ foreach (ModuleDescription mod in addin.Description.AllModules) {
+ foreach (Extension ext in mod.Extensions) {
+ transaction.NotifyExtensionsChangedEvent (ext.Path);
+ ExtensionPoint ep = tree.FindLoadedExtensionPoint (ext.Path);
+ if (ep != null && !eps.Contains (ep))
+ eps.Add (ep, ep);
}
+ }
- // Add the new nodes
- var loadedNodes = new List<TreeNode> ();
- foreach (ExtensionPoint ep in eps.Keys) {
- ExtensionLoadData data = GetAddinExtensions (id, ep);
- if (data != null) {
- foreach (Extension ext in data.Extensions) {
- TreeNode node = GetNode (ext.Path);
- if (node != null && node.ExtensionNodeSet != null) {
- if (node.ChildrenLoaded)
- LoadModuleExtensionNodes (ext, data.AddinId, node.ExtensionNodeSet, loadedNodes);
- }
- else
- AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, id, null, false);
+ // Add the new nodes
+ foreach (ExtensionPoint ep in eps.Keys) {
+ ExtensionLoadData data = GetAddinExtensions (transaction, id, ep);
+ if (data != null) {
+ foreach (Extension ext in data.Extensions) {
+ TreeNode node = GetNode (ext.Path);
+ if (node != null && node.ExtensionNodeSet != null) {
+ if (node.ChildrenFromExtensionsLoaded)
+ LoadModuleExtensionNodes (transaction, node, ext, data.AddinId);
}
+ else
+ AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, id, null, false);
}
}
-
- // Call the OnAddinLoaded method on nodes, if the add-in is already loaded
- foreach (TreeNode nod in loadedNodes)
- nod.ExtensionNode.OnAddinLoaded ();
-
- // Global extension change event. Other events are fired by LoadModuleExtensionNodes.
- // The event is called for all extensions, even for those not loaded. This is for coherence,
- // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged
- // event without first getting the list of nodes that may change).
- foreach (string newExt in newExtensions)
- NotifyExtensionsChanged (new ExtensionEventArgs (newExt));
- }
- finally {
- fireEvents = false;
}
+
// Do the same in child contexts
-
- lock (conditionTypes) {
- if (childContexts != null) {
- CleanDisposedChildContexts ();
- foreach (WeakReference wref in childContexts) {
- ExtensionContext ctx = wref.Target as ExtensionContext;
- if (ctx != null)
- ctx.ActivateAddinExtensions (id);
- }
- }
- }
+
+ foreach (var ctx in GetActiveChildContexes ())
+ ctx.ActivateAddinExtensions (transaction, id);
}
-
- internal void RemoveAddinExtensions (string id)
+
+ internal void RemoveAddinExtensions (ExtensionContextTransaction transaction, string id)
{
- try {
- // Registers this add-in as disabled, so from now on extension from this
- // add-in will be ignored
- RegisterRuntimeDisabledAddin (id);
-
- fireEvents = true;
+ // Registers this add-in as disabled, so from now on extension from this
+ // add-in will be ignored
+ RegisterRuntimeDisabledAddin (transaction, id);
- // This method removes all extension nodes added by the add-in
- // Get all nodes created by the addin
- List<TreeNode> list = new List<TreeNode> ();
- tree.FindAddinNodes (id, list);
-
- // Remove each node and notify the change
- foreach (TreeNode node in list) {
- if (node.ExtensionNode == null) {
- // It's an extension point. Just remove it, no notifications are needed
- node.Remove ();
- }
- else {
- node.ExtensionNode.OnAddinUnloaded ();
- node.Remove ();
+ // This method removes all extension nodes added by the add-in
+ // Get all nodes created by the addin
+ List<TreeNode> list = new List<TreeNode> ();
+ tree.FindAddinNodes (id, list);
+
+ // Remove each node and notify the change
+ foreach (TreeNode node in list) {
+ node.NotifyAddinUnloaded ();
+ node.Parent?.RemoveChild (transaction, node);
+ }
+
+ // Notify global extension point changes.
+ // The event is called for all extensions, even for those not loaded. This is for coherence,
+ // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged
+ // 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.GetAddinEngineTransaction(), id);
+ if (addin != null) {
+ var paths = new List<string> ();
+ // Using addin.Module.ParentAddinDescription here because addin.Addin.Description may not
+ // have a valid reference (the description is lazy loaded and may already have been removed from the registry)
+ foreach (ModuleDescription mod in addin.Module.ParentAddinDescription.AllModules) {
+ foreach (Extension ext in mod.Extensions) {
+ if (!paths.Contains (ext.Path))
+ paths.Add (ext.Path);
}
}
-
- // Notify global extension point changes.
- // The event is called for all extensions, even for those not loaded. This is for coherence,
- // although that something that it doesn't make much sense to do (subscribing the ExtensionChanged
- // 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 (id);
- if (addin != null) {
- var paths = new List<string> ();
- // Using addin.Module.ParentAddinDescription here because addin.Addin.Description may not
- // have a valid reference (the description is lazy loaded and may already have been removed from the registry)
- foreach (ModuleDescription mod in addin.Module.ParentAddinDescription.AllModules) {
- foreach (Extension ext in mod.Extensions) {
- if (!paths.Contains (ext.Path))
- paths.Add (ext.Path);
- }
- }
- foreach (string path in paths)
- NotifyExtensionsChanged (new ExtensionEventArgs (path));
- }
- } finally {
- fireEvents = false;
+ foreach (string path in paths)
+ transaction.NotifyExtensionsChangedEvent (path);
}
}
- void RegisterRuntimeDisabledAddin (string addinId)
+ void RegisterRuntimeDisabledAddin (ExtensionContextTransaction transaction, string addinId)
{
- if (runTimeDisabledAddins == null)
- runTimeDisabledAddins = new List<string> ();
- if (!runTimeDisabledAddins.Contains (addinId))
- runTimeDisabledAddins.Add (addinId);
-
- if (runTimeEnabledAddins != null)
- runTimeEnabledAddins.Remove (addinId);
+ runTimeDisabledAddins.Add (addinId);
+ runTimeEnabledAddins.Remove (addinId);
}
- void RegisterRuntimeEnabledAddin (string addinId)
+ void RegisterRuntimeEnabledAddin (ExtensionContextTransaction transaction, string addinId)
{
- if (runTimeEnabledAddins == null)
- runTimeEnabledAddins = new List<string> ();
- if (!runTimeEnabledAddins.Contains (addinId))
- runTimeEnabledAddins.Add (addinId);
-
- if (runTimeDisabledAddins != null)
- runTimeDisabledAddins.Remove (addinId);
+ runTimeEnabledAddins.Add (addinId);
+ runTimeDisabledAddins.Remove (addinId);
}
- internal List<string> GetAddinsForPath (List<string> col)
+ List<string> GetAddinsForPath (ExtensionContextTransaction transaction, List<string> col)
{
List<string> newlist = null;
-
+
// Always consider add-ins which have been enabled at runtime since
// they may contain extension for this path.
// Ignore addins disabled at run-time.
-
- if (runTimeEnabledAddins != null && runTimeEnabledAddins.Count > 0) {
+
+ if (runTimeEnabledAddins.Count > 0) {
newlist = new List<string> ();
newlist.AddRange (col);
foreach (string s in runTimeEnabledAddins)
if (!newlist.Contains (s))
newlist.Add (s);
}
-
- if (runTimeDisabledAddins != null && runTimeDisabledAddins.Count > 0) {
+
+ if (runTimeDisabledAddins.Count > 0) {
if (newlist == null) {
newlist = new List<string> ();
newlist.AddRange (col);
@@ -1027,14 +1012,13 @@ namespace Mono.Addins
return newlist != null ? newlist : col;
}
-
+
// Load the extension nodes at the specified path. If the path
// contains extension nodes implemented in an add-in which is
// not loaded, the add-in will be automatically loaded
-
- internal void LoadExtensions (string requestedExtensionPath)
+
+ internal void LoadExtensions (ExtensionContextTransaction transaction, string requestedExtensionPath, TreeNode node)
{
- TreeNode node = GetNode (requestedExtensionPath);
if (node == null)
throw new InvalidOperationException ("Extension point not defined: " + requestedExtensionPath);
@@ -1045,16 +1029,16 @@ namespace Mono.Addins
// Collect extensions to be loaded from add-ins. Before loading the extensions,
// they must be sorted, that's why loading is split in two steps (collecting + loading).
- var addins = GetAddinsForPath (ep.Addins);
+ var addins = GetAddinsForPath (transaction, ep.Addins);
var loadData = new List<ExtensionLoadData> (addins.Count);
foreach (string addin in addins) {
- ExtensionLoadData ed = GetAddinExtensions (addin, ep);
+ ExtensionLoadData ed = GetAddinExtensions (transaction, addin, ep);
if (ed != null) {
// Insert the addin data taking into account dependencies.
// An add-in must be processed after all its dependencies.
bool added = false;
- for (int n=0; n<loadData.Count; n++) {
+ for (int n = 0; n < loadData.Count; n++) {
ExtensionLoadData other = loadData [n];
if (AddinEngine.Registry.AddinDependsOn (other.AddinName, ed.AddinName)) {
loadData.Insert (n, ed);
@@ -1066,33 +1050,33 @@ namespace Mono.Addins
loadData.Add (ed);
}
}
-
+
// Now load the extensions
-
+
var loadedNodes = new List<TreeNode> ();
foreach (ExtensionLoadData data in loadData) {
foreach (Extension ext in data.Extensions) {
TreeNode cnode = GetNode (ext.Path);
if (cnode != null && cnode.ExtensionNodeSet != null)
- LoadModuleExtensionNodes (ext, data.AddinId, cnode.ExtensionNodeSet, loadedNodes);
+ LoadModuleExtensionNodes (transaction, cnode, ext, data.AddinId);
else
AddinEngine.ReportError ("Extension node not found or not extensible: " + ext.Path, data.AddinId, null, false);
}
}
// Call the OnAddinLoaded method on nodes, if the add-in is already loaded
foreach (TreeNode nod in loadedNodes)
- nod.ExtensionNode.OnAddinLoaded ();
+ nod.ExtensionNode.NotifyAddinLoaded();
- NotifyExtensionsChanged (new ExtensionEventArgs (requestedExtensionPath));
- }
- }
-
- ExtensionLoadData GetAddinExtensions (string id, ExtensionPoint ep)
+ transaction.NotifyExtensionsChangedEvent(requestedExtensionPath);
+ }
+ }
+
+ ExtensionLoadData GetAddinExtensions (ExtensionContextTransaction transaction, string id, ExtensionPoint ep)
{
Addin pinfo = null;
// Root add-ins are not returned by GetInstalledAddin.
- RuntimeAddin addin = AddinEngine.GetAddin (id);
+ RuntimeAddin addin = AddinEngine.GetAddin (transaction.GetAddinEngineTransaction(), id);
if (addin != null)
pinfo = addin.Addin;
else
@@ -1138,18 +1122,18 @@ namespace Mono.Addins
}
}
- void LoadModuleExtensionNodes (Extension extension, string addinId, ExtensionNodeSet nset, List<TreeNode> loadedNodes)
+ void LoadModuleExtensionNodes (ExtensionContextTransaction transaction, TreeNode node, Extension extension, string addinId)
{
// Now load the extensions
var addedNodes = new List<TreeNode> ();
- tree.LoadExtension (addinId, extension, addedNodes);
+ tree.LoadExtension (transaction, node, addinId, extension, addedNodes);
- RuntimeAddin ad = AddinEngine.GetAddin (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.
- if (nod.ExtensionNode != null)
- loadedNodes.Add (nod);
+ if (nod.HasExtensionNode)
+ transaction.ReportLoadedNode (nod);
}
}
}
@@ -1173,11 +1157,13 @@ namespace Mono.Addins
TreeNode node = tree.GetNode (path);
if (node != null || parentContext == null)
return node;
-
+
TreeNode supNode = parentContext.tree.GetNode (path);
if (supNode == null)
return null;
-
+
+ // Node not found and the context has a parent context which has the node
+
if (path.StartsWith ("/"))
path = path.Substring (1);
@@ -1185,51 +1171,57 @@ namespace Mono.Addins
TreeNode srcNode = parentContext.tree;
TreeNode dstNode = tree;
- foreach (string part in parts) {
-
- // Look for the node in the source tree
-
- int i = srcNode.Children.IndexOfNode (part);
- if (i != -1)
- srcNode = srcNode.Children [i];
- else
- return null;
+ ExtensionContextTransaction transaction = null;
- // Now get the node in the target tree
-
- int j = dstNode.Children.IndexOfNode (part);
- if (j != -1) {
- dstNode = dstNode.Children [j];
- }
- else {
- // Create if not found
- TreeNode newNode = new TreeNode (AddinEngine, part);
- dstNode.AddChildNode (newNode);
- dstNode = newNode;
-
- // Copy extension data
- dstNode.ExtensionNodeSet = srcNode.ExtensionNodeSet;
- dstNode.ExtensionPoint = srcNode.ExtensionPoint;
- dstNode.Condition = srcNode.Condition;
-
- if (dstNode.Condition != null)
- RegisterNodeCondition (dstNode, dstNode.Condition);
+ try {
+ foreach (string part in parts) {
+
+ // Look for the node in the source tree (from parent context)
+
+ srcNode = srcNode.GetChildNode (part);
+ if (srcNode == null)
+ return null;
+
+ // Now get the node in the target tree
+
+ var dstNodeChild = dstNode.GetChildNode (part);
+ if (dstNodeChild != null) {
+ dstNode = dstNodeChild;
+ } else {
+ if (transaction == null)
+ transaction = BeginTransaction ();
+
+ // Check again just in case the node was created while taking the transaction
+ dstNodeChild = dstNode.GetChildNode (part);
+ if (dstNodeChild != null)
+ dstNode = dstNodeChild;
+ else {
+
+ // Create if not found
+ TreeNode newNode = new TreeNode (AddinEngine, part);
+
+ // Copy extension data
+ newNode.ExtensionNodeSet = srcNode.ExtensionNodeSet;
+ newNode.ExtensionPoint = srcNode.ExtensionPoint;
+ newNode.Condition = srcNode.Condition;
+
+ dstNode.AddChildNode (transaction, newNode);
+ dstNode = newNode;
+ }
+ }
}
+ } finally {
+ transaction?.Dispose ();
}
return dstNode;
}
-
- internal bool FindExtensionPathByType (IProgressStatus monitor, Type type, string nodeName, out string path, out string pathNodeName)
- {
- return tree.FindExtensionPathByType (monitor, type, nodeName, out path, out pathNodeName);
- }
}
class ConditionInfo
{
public object CondType;
- public List<BaseCondition> BoundConditions;
+ public ImmutableArray<BaseCondition> BoundConditions = ImmutableArray<BaseCondition>.Empty;
}
@@ -1379,4 +1371,82 @@ namespace Mono.Addins
public string TypeName { get; set; }
public RuntimeAddin Addin { get; set; }
}
+
+ /// <summary>
+ /// A queue that can be used to dispatch callbacks sequentially
+ /// </summary>
+ class NotificationQueue
+ {
+ readonly AddinEngine addinEngine;
+ readonly Queue<(Action Action,object Source)> notificationQueue = new Queue<(Action,object)>();
+
+ bool sending;
+
+ public NotificationQueue(AddinEngine addinEngine)
+ {
+ this.addinEngine = addinEngine;
+ }
+
+ internal void Invoke(Action action, object source)
+ {
+ lock (notificationQueue)
+ {
+ if (sending)
+ {
+ // Already sending, enqueue the action so whoever is sending will take it
+ notificationQueue.Enqueue((action,source));
+ return;
+ }
+ else
+ {
+ // Nobody is sending, do it now
+ sending = true;
+ }
+ }
+
+ SafeInvoke(action, source);
+
+ do
+ {
+ lock (notificationQueue)
+ {
+ if (notificationQueue.Count == 0)
+ {
+ sending = false;
+ return;
+ }
+ (action,source) = notificationQueue.Dequeue();
+ }
+ SafeInvoke(action, source);
+ }
+ while (true);
+ }
+
+ void SafeInvoke(Action action, object source)
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ RuntimeAddin addin = null;
+
+ if (source is ExtensionNode node)
+ {
+ try
+ {
+ addin = node.Addin;
+ }
+ catch (Exception addinException)
+ {
+ addinEngine.ReportError(null, null, addinException, false);
+ addin = null;
+ }
+ }
+
+ addinEngine.ReportError("Callback invocation failed", addin?.Id, ex, false);
+ }
+ }
+ }
}
diff --git a/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs b/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs
new file mode 100644
index 0000000..ba2a245
--- /dev/null
+++ b/Mono.Addins/Mono.Addins/ExtensionContextTransaction.cs
@@ -0,0 +1,409 @@
+//
+// ExtensionContext.cs
+//
+// Author:
+// Lluis Sanchez Gual
+//
+// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using static Mono.Addins.ExtensionContext;
+
+namespace Mono.Addins
+{
+ /// <summary>
+ /// Represents a mutation action of an extension context. It is necessary to acquire a transaction object
+ /// when doing changes in a context (methods that modify a context take a transaction as parameter).
+ /// Getting a transaction is a blocking operation: if a thread has started a transaction, other threads trying
+ /// to get one will be blocked until the current transaction is finished.
+ /// The transaction objects collects two kind of information:
+ /// 1) changes to be done in the context. For example, it collects conditions to be registered, and then all conditions
+ /// are added to the context at once when the transaction is completed (so the immutable collection that holds
+ /// the condition list can be updated with a single allocation).
+ /// 2) events to be fired once the transaction is completed. This is to avoid firing events while the context
+ /// lock is taken.
+ /// </summary>
+ class ExtensionContextTransaction : IDisposable
+ {
+ List<TreeNode> loadedNodes;
+ HashSet<TreeNode> childrenChanged;
+ HashSet<string> extensionsChanged;
+ List<(TreeNode Node, BaseCondition Condition)> nodeConditions;
+ List<(TreeNode Node, BaseCondition Condition)> nodeConditionUnregistrations;
+ List<TreeNode> treeNodeTransactions;
+ ExtensionContextSnapshot snapshot;
+ bool snaphotChanged;
+ AddinEngineTransaction nestedAddinEngineTransaction;
+
+ public ExtensionContextTransaction (ExtensionContext context)
+ {
+ Context = context;
+ Monitor.Enter (Context.LocalLock);
+ snapshot = context.CurrentSnapshot;
+ }
+
+ public ExtensionContext Context { get; }
+
+ public bool DisableEvents { get; set; }
+
+ public AddinEngine.AddinEngineSnapshot AddinEngineSnapshot => (AddinEngine.AddinEngineSnapshot)Snapshot;
+
+ public ExtensionContextSnapshot Snapshot => snapshot;
+
+ /// <summary>
+ /// Gets an add-in engine transaction, if there is one in progress. Returns null if there isn't one.
+ /// </summary>
+ public AddinEngineTransaction GetAddinEngineTransaction()
+ {
+ if (this is AddinEngineTransaction et)
+ return et;
+ if (nestedAddinEngineTransaction != null)
+ return nestedAddinEngineTransaction;
+ return null;
+ }
+
+ /// <summary>
+ /// Gets or creates an add-in engine transaction, which will be committed together with this transaction
+ /// </summary>
+ 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)
+ {
+ snaphotChanged = true;
+ var newSnapshot = Context.CreateSnapshot();
+ newSnapshot.CopyFrom(snapshot);
+ snapshot = newSnapshot;
+ }
+ }
+
+ public void Dispose ()
+ {
+ try
+ {
+ // Update the context
+
+ UpdateSnapshot();
+
+ if (snaphotChanged)
+ Context.SetSnapshot(snapshot);
+
+ } finally {
+ Monitor.Exit (Context.LocalLock);
+ }
+
+ // If there is a nested engine transaction, make sure it is disposed
+ using var _ = nestedAddinEngineTransaction;
+
+ // Do notifications outside the lock
+
+ DispatchNotifications();
+ }
+
+ protected virtual void UpdateSnapshot()
+ {
+ if (nodeConditions != null)
+ {
+ BulkRegisterNodeConditions(nodeConditions);
+ }
+
+ if (nodeConditionUnregistrations != null)
+ {
+ BulkUnregisterNodeConditions(nodeConditionUnregistrations);
+ }
+
+ // Commit tree node transactions
+ if (treeNodeTransactions != null)
+ {
+ foreach (var node in treeNodeTransactions)
+ node.CommitChildrenUpdateTransaction();
+ }
+ }
+
+ 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)
+ {
+ // We are going to do many changes, so create a builder for the dictionary
+ var dictBuilder = Snapshot.ConditionsToNodes.ToBuilder();
+ List<(string ConditionId, BaseCondition BoundCondition)> bindings = new();
+
+ // Group nodes by the conditions, so that all nodes for a conditions can be processed together
+
+ foreach (var group in nodeConditions.GroupBy(c => c.Condition))
+ {
+ var condition = group.Key;
+
+ if (!dictBuilder.TryGetValue(condition, out var list))
+ {
+
+ // Condition not yet registered, register it now
+
+ // Get a list of conditions on which this one depends
+ var conditionTypeIds = new List<string>();
+ condition.GetConditionTypes(conditionTypeIds);
+
+ foreach (string cid in conditionTypeIds)
+ {
+ // For each condition on which 'condition' depends, register the dependency
+ // so that it if the condition changes, the dependencies are notified
+ bindings.Add((cid, condition));
+ }
+ list = ImmutableArray<TreeNode>.Empty;
+ }
+
+ dictBuilder[condition] = list.AddRange(group.Select(item => item.Node));
+ }
+
+ foreach (var binding in bindings.GroupBy(b => b.ConditionId, b => b.BoundCondition))
+ {
+ ConditionInfo info = Context.GetOrCreateConditionInfo(this, binding.Key, null);
+ info.BoundConditions = info.BoundConditions.AddRange(binding);
+ }
+
+ Snapshot.ConditionsToNodes = dictBuilder.ToImmutable();
+ }
+
+ void BulkUnregisterNodeConditions(IEnumerable<(TreeNode Node, BaseCondition Condition)> nodeConditions)
+ {
+ ImmutableDictionary<BaseCondition, ImmutableArray<TreeNode>>.Builder dictBuilder = null;
+
+ foreach (var group in nodeConditions.GroupBy(c => c.Condition))
+ {
+ var condition = group.Key;
+ if (!Snapshot.ConditionsToNodes.TryGetValue(condition, out var list))
+ continue;
+
+ var newList = list.RemoveRange(group.Select(item => item.Node));
+
+ // If there are no changes, continue, no need to create the dictionary builder
+ if (newList == list)
+ continue;
+
+ if (dictBuilder == null)
+ dictBuilder = Snapshot.ConditionsToNodes.ToBuilder();
+
+ if (newList.Length == 0)
+ {
+
+ // The condition is not used anymore. Remove it from the dictionary
+ // and unregister it from any condition it was bound to
+
+ dictBuilder.Remove(condition);
+ var conditionTypeIds = new List<string>();
+ condition.GetConditionTypes(conditionTypeIds);
+ foreach (string cid in conditionTypeIds)
+ {
+ var info = Snapshot.ConditionTypes[cid];
+ if (info != null)
+ info.BoundConditions = info.BoundConditions.Remove(condition);
+ }
+ }
+ else
+ dictBuilder[condition] = newList;
+ }
+ if (dictBuilder != null)
+ Snapshot.ConditionsToNodes = dictBuilder.ToImmutable();
+ }
+
+ public void ReportLoadedNode (TreeNode node)
+ {
+ if (loadedNodes == null)
+ loadedNodes = new List<TreeNode> ();
+ loadedNodes.Add (node);
+ }
+
+ public void ReportChildrenChanged (TreeNode node)
+ {
+ if (!DisableEvents) {
+ if (childrenChanged == null)
+ childrenChanged = new HashSet<TreeNode> ();
+ childrenChanged.Add (node);
+ }
+ }
+
+ public void NotifyExtensionsChangedEvent (string path)
+ {
+ if (!DisableEvents) {
+ if (extensionsChanged == null)
+ extensionsChanged = new HashSet<string> ();
+ extensionsChanged.Add (path);
+ }
+ }
+
+ public void RegisterNodeCondition (TreeNode node, BaseCondition cond)
+ {
+ EnsureNewSnapshot();
+ if (nodeConditions == null)
+ nodeConditions = new List<(TreeNode Node, BaseCondition Condition)> ();
+ nodeConditions.Add ((node, cond));
+ }
+
+ public void UnregisterNodeCondition (TreeNode node, BaseCondition cond)
+ {
+ EnsureNewSnapshot();
+ if (nodeConditionUnregistrations == null)
+ nodeConditionUnregistrations = new List<(TreeNode Node, BaseCondition Condition)> ();
+ nodeConditionUnregistrations.Add ((node, cond));
+ if (nodeConditions != null)
+ nodeConditions.Remove ((node, cond));
+ }
+
+ public void RegisterChildrenUpdateTransaction (TreeNode node)
+ {
+ if (treeNodeTransactions == null)
+ treeNodeTransactions = new List<TreeNode> ();
+ treeNodeTransactions.Add (node);
+ }
+
+ }
+
+ class AddinEngineTransaction : ExtensionContextTransaction
+ {
+ List<KeyValuePair<string, string>> registeredAutoExtensionPoints;
+ List<string> unregisteredAutoExtensionPoints;
+ List<KeyValuePair<string, RuntimeAddin>> registeredAssemblyResolvePaths;
+ List<string> unregisteredAssemblyResolvePaths;
+ List<RuntimeAddin> addinLoadEvents;
+ List<string> addinUnloadEvents;
+
+ public AddinEngineTransaction (AddinEngine engine) : base (engine)
+ {
+ }
+
+ public void RegisterAssemblyResolvePaths(string assembly, RuntimeAddin addin)
+ {
+ EnsureNewSnapshot();
+ if (registeredAssemblyResolvePaths == null)
+ registeredAssemblyResolvePaths = new List<KeyValuePair<string, RuntimeAddin>>();
+ registeredAssemblyResolvePaths.Add(new KeyValuePair<string, RuntimeAddin>(assembly, addin));
+ }
+
+ public void UnregisterAssemblyResolvePaths(string assembly)
+ {
+ EnsureNewSnapshot();
+ if (unregisteredAssemblyResolvePaths == null)
+ unregisteredAssemblyResolvePaths = new List<string>();
+ unregisteredAssemblyResolvePaths.Add(assembly);
+ }
+
+ public void ReportAddinLoad(RuntimeAddin addin)
+ {
+ if (addinLoadEvents == null)
+ addinLoadEvents = new List<RuntimeAddin>();
+ addinLoadEvents.Add(addin);
+ }
+
+ public void ReportAddinUnload(string id)
+ {
+ if (addinUnloadEvents == null)
+ addinUnloadEvents = new List<string>();
+ addinUnloadEvents.Add(id);
+ }
+
+ public void RegisterAutoTypeExtensionPoint(string typeName, string path)
+ {
+ EnsureNewSnapshot();
+ if (registeredAutoExtensionPoints == null)
+ registeredAutoExtensionPoints = new List<KeyValuePair<string, string>>();
+ registeredAutoExtensionPoints.Add(new KeyValuePair<string, string>(typeName, path));
+ }
+
+ public void UnregisterAutoTypeExtensionPoint(string typeName)
+ {
+ EnsureNewSnapshot();
+ if (unregisteredAutoExtensionPoints == null)
+ unregisteredAutoExtensionPoints = new List<string>();
+ 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/ExtensionNode.cs b/Mono.Addins/Mono.Addins/ExtensionNode.cs
index 09dd0c9..36f227d 100644
--- a/Mono.Addins/Mono.Addins/ExtensionNode.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionNode.cs
@@ -33,6 +33,8 @@ using System.Collections.Generic;
using System.Xml;
using System.Reflection;
using Mono.Addins.Description;
+using System.Threading;
+using System.Linq;
namespace Mono.Addins
{
@@ -59,7 +61,8 @@ namespace Mono.Addins
ModuleDescription module;
AddinEngine addinEngine;
event ExtensionNodeEventHandler extensionNodeChanged;
-
+ internal readonly object localLock = new object ();
+
/// <summary>
/// Identifier of the node.
/// </summary>
@@ -118,11 +121,19 @@ namespace Mono.Addins
internal void SetTreeNode (TreeNode node)
{
+ // treeNode can only be set once (it's set during the node initialization)
+ if (treeNode != null)
+ throw new InvalidOperationException ();
+
treeNode = node;
}
internal void SetData (AddinEngine addinEngine, string plugid, ExtensionNodeType nodeType, ModuleDescription module)
{
+ // SetData can only be called once (it's set during the node initialization)
+ if (this.addinEngine != null)
+ throw new InvalidOperationException ();
+
this.addinEngine = addinEngine;
this.addinId = plugid;
this.nodeType = nodeType;
@@ -146,9 +157,7 @@ namespace Mono.Addins
public RuntimeAddin Addin {
get {
if (addin == null && addinId != null) {
- if (!addinEngine.IsAddinLoaded (addinId))
- addinEngine.LoadAddin (null, addinId, true);
- addin = addinEngine.GetAddin (addinId);
+ addin = addinEngine.GetOrLoadAddin(addinId, true);
if (addin != null)
addin = addin.GetModule (module);
}
@@ -157,80 +166,87 @@ namespace Mono.Addins
return addin;
}
}
-
+
/// <summary>
/// Notifies that a child node of this node has been added or removed.
/// </summary>
/// <remarks>
/// The first time the event is subscribed, the handler will be called for each existing node.
+ ///
+ /// Threading information: the thread on which the event is raised is undefined. Events are
+ /// guaranteed to be raised sequentially for a given extension context.
/// </remarks>
public event ExtensionNodeEventHandler ExtensionNodeChanged {
- add {
- extensionNodeChanged += value;
- foreach (ExtensionNode node in ChildNodes) {
- try {
- value (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
- } catch (Exception ex) {
- RuntimeAddin nodeAddin;
- try {
- nodeAddin = node.Addin;
- } catch (Exception addinException) {
- addinEngine.ReportError (null, null, addinException, false);
- nodeAddin = null;
- }
- addinEngine.ReportError (null, nodeAddin != null ? nodeAddin.Id : null, ex, false);
+ add
+ {
+ ExtensionContext.InvokeCallback(() =>
+ {
+ // The invocation here needs to be done right to make sure there are no duplicate or missing events.
+
+ // 1) Get the list of current nodes and store it in a variable. This is the list of nodes for which
+ // notifications will initially be sent.
+
+ var children = GetChildNodes();
+
+ // 2) Subscribe the real event. If nodes were added or removed since the above GetChildNodes
+ // call, callback invocations will now be queued (since we are inside InvokeCallback).
+
+ extensionNodeChanged += value;
+
+ // 3) Send the notifications for the events. Again, any change done after the above GetChildNodes
+ // call will have queued a notification.
+
+ foreach (ExtensionNode node in children)
+ {
+ var theNode = node;
+ value(this, new ExtensionNodeEventArgs(ExtensionChange.Add, theNode));
}
- }
+
+ // 4) We are done notifying the pre-existing nodes. If there was any further change in the children
+ // list, the corresponding events will be raised after this callback ends.
+
+ }, this);
}
remove {
- extensionNodeChanged -= value;
+ ExtensionContext.InvokeCallback(() =>
+ {
+ // This is done inside a InvokeCallback call for simetry with the 'add' block. Since the 'add'
+ // block runs on a callback that can be queued, doing the same here ensures that the unsubscription
+ // will always happen after the subscription
+ extensionNodeChanged -= value;
+ }, this);
}
}
-
+
/// <summary>
/// Child nodes of this extension node.
/// </summary>
- public ExtensionNodeList ChildNodes {
- get {
- if (childrenLoaded)
- return childNodes;
-
- try {
- if (treeNode.Children.Count == 0) {
- childNodes = ExtensionNodeList.Empty;
- return childNodes;
- }
- }
- catch (Exception ex) {
- addinEngine.ReportError (null, null, ex, false);
- childNodes = ExtensionNodeList.Empty;
- return childNodes;
- } finally {
- childrenLoaded = true;
- }
+ [Obsolete("Use GetChildNodes()")]
+ public ExtensionNodeList ChildNodes => GetChildNodes();
- List<ExtensionNode> list = new List<ExtensionNode> ();
- foreach (TreeNode cn in treeNode.Children) {
-
- // For each node check if it is visible for the current context.
- // If something fails while evaluating the condition, just ignore the node.
-
- try {
- if (cn.ExtensionNode != null && cn.IsEnabled)
- list.Add (cn.ExtensionNode);
- } catch (Exception ex) {
- addinEngine.ReportError (null, null, ex, false);
+ /// <summary>
+ /// Child nodes of this extension node.
+ ///
+ /// Threading information: the returned list is an snapshot of the current children list. The returned
+ /// list will not change if this node's children change as result of add-ins loading or conditions changing.
+ /// Successive calls to this method can return different list instances.
+ /// </summary>
+ public ExtensionNodeList GetChildNodes()
+ {
+ if (!childrenLoaded)
+ {
+ lock (localLock)
+ {
+ if (!childrenLoaded)
+ {
+ childNodes = CreateChildrenList();
+ childrenLoaded = true;
}
}
- if (list.Count > 0)
- childNodes = new ExtensionNodeList (list);
- else
- childNodes = ExtensionNodeList.Empty;
-
- return childNodes;
}
+ return childNodes;
}
-
+
/// <summary>
/// Returns the child objects of a node.
/// </summary>
@@ -349,10 +365,14 @@ namespace Mono.Addins
Array GetChildObjectsInternal (Type arrayElementType, bool reuseCachedInstance)
{
- ArrayList list = new ArrayList (ChildNodes.Count);
+ // The ChildNodes collection can't change, but it can be replaced by another collection,
+ // so we keep a local reference, which won't change
+ var children = GetChildNodes();
+
+ ArrayList list = new ArrayList (children.Count);
- for (int n=0; n<ChildNodes.Count; n++) {
- InstanceExtensionNode node = ChildNodes [n] as InstanceExtensionNode;
+ for (int n=0; n<children.Count; n++) {
+ var node = children [n] as InstanceExtensionNode;
if (node == null) {
addinEngine.ReportError ("Error while getting object for node in path '" + Path + "'. Extension node is not a subclass of InstanceExtensionNode.", null, null, false);
continue;
@@ -471,79 +491,181 @@ namespace Mono.Addins
return addinEngine.DefaultLocalizer;
}
-
+
+ ExtensionNodeList CreateChildrenList ()
+ {
+ var nodeChildren = treeNode.Children;
+
+ try {
+ if (nodeChildren.Count == 0) {
+ return ExtensionNodeList.Empty;
+ }
+ } catch (Exception ex) {
+ addinEngine.ReportError (null, null, ex, false);
+ return ExtensionNodeList.Empty;
+ }
+
+ List<ExtensionNode> list = new List<ExtensionNode> (nodeChildren.Count);
+ foreach (TreeNode cn in nodeChildren) {
+
+ // For each node check if it is visible for the current context.
+ // If something fails while evaluating the condition, just ignore the node.
+
+ try {
+ if (cn.IsEnabled && cn.HasExtensionNode)
+ list.Add (cn.ExtensionNode);
+ } catch (Exception ex) {
+ addinEngine.ReportError (null, null, ex, false);
+ }
+ }
+
+ if (list.Count > 0)
+ return new ExtensionNodeList (list);
+ else
+ return ExtensionNodeList.Empty;
+ }
+
internal bool NotifyChildChanged ()
{
+ // If children are not loaded, there isn't anything to update
if (!childrenLoaded)
return false;
- ExtensionNodeList oldList = childNodes;
- childrenLoaded = false;
-
- bool changed = false;
-
- foreach (ExtensionNode nod in oldList) {
- if (ChildNodes [nod.Id] == null) {
- changed = true;
- OnChildNodeRemoved (nod);
+ List<(ExtensionNode Node, bool Added)> changes = null;
+
+ lock (localLock) {
+ var oldList = childNodes;
+ var newList = CreateChildrenList ();
+ childNodes = newList;
+
+ // Compare old list and new list to find out which nodes have
+ // been added or removed, since the changes need to be notified.
+
+ foreach (ExtensionNode nod in oldList) {
+ if (newList [nod.Id] == null) {
+ if (changes == null)
+ changes = new ();
+ changes.Add ((nod, false));
+ }
}
- }
- foreach (ExtensionNode nod in ChildNodes) {
- if (oldList [nod.Id] == null) {
- changed = true;
- OnChildNodeAdded (nod);
+ foreach (ExtensionNode nod in newList) {
+ if (oldList [nod.Id] == null) {
+ if (changes == null)
+ changes = new ();
+ changes.Add ((nod, true));
+ }
}
}
- if (changed)
- OnChildrenChanged ();
- return changed;
+
+ // Notify the changes outside the lock
+
+ if (changes != null) {
+ foreach (var change in changes) {
+ var node = change.Node;
+ if (change.Added)
+ {
+ ExtensionContext.InvokeCallback(() =>
+ {
+ OnChildNodeAdded(node);
+ }, this);
+ }
+ else
+ {
+ ExtensionContext.InvokeCallback(() =>
+ {
+ OnChildNodeRemoved(node);
+ }, this);
+ }
+ }
+ ExtensionContext.InvokeCallback(OnChildrenChanged, this);
+ return true;
+ } else
+ return false;
}
-
+
+ internal void NotifyAddinLoaded()
+ {
+ ExtensionContext.InvokeCallback(OnAddinLoaded, this);
+ }
+
+ internal void NotifyAddinUnloaded()
+ {
+ ExtensionContext.InvokeCallback(OnAddinUnloaded, this);
+ }
+
/// <summary>
/// Called when the add-in that defined this extension node is actually loaded in memory.
/// </summary>
- internal protected virtual void OnAddinLoaded ()
+ /// <remarks>
+ /// Threading information: the thread on which the method is invoked is undefined.
+ /// Invocations to the virtual methods in this class are guaranteed to be done sequentially.
+ /// For example, OnAddinLoaded will always be called before OnAddinUnloaded, and never
+ /// concurrently.
+ /// </remarks>
+ protected virtual void OnAddinLoaded ()
{
}
-
+
/// <summary>
/// Called when the add-in that defined this extension node is being
/// unloaded from memory.
/// </summary>
- internal protected virtual void OnAddinUnloaded ()
+ /// <remarks>
+ /// Threading information: the thread on which the method is invoked is undefined.
+ /// Invocations to the virtual methods in this class are guaranteed to be done sequentially.
+ /// For example, OnAddinLoaded will always be called before OnAddinUnloaded, and never
+ /// concurrently.
+ /// </remarks>
+ protected virtual void OnAddinUnloaded ()
{
}
-
+
/// <summary>
/// Called when the children list of this node has changed. It may be due to add-ins
/// being loaded/unloaded, or to conditions being changed.
/// </summary>
+ /// <remarks>
+ /// Threading information: the thread on which the method is invoked is undefined.
+ /// Invocations to the virtual methods in this class are guaranteed to be done sequentially.
+ /// For example, OnChildNodeAdded will always be called before OnChildNodeRemoved for a given
+ /// child, and never concurrently.
+ /// </remarks>
protected virtual void OnChildrenChanged ()
{
}
-
+
/// <summary>
/// Called when a child node is added
/// </summary>
/// <param name="node">
/// Added node.
/// </param>
+ /// <remarks>
+ /// Threading information: the thread on which the method is invoked is undefined.
+ /// Invocations to the virtual methods in this class are guaranteed to be done sequentially.
+ /// For example, OnChildNodeAdded will always be called before OnChildNodeRemoved for a given
+ /// child, and never concurrently.
+ /// </remarks>
protected virtual void OnChildNodeAdded (ExtensionNode node)
{
- if (extensionNodeChanged != null)
- extensionNodeChanged (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
+ extensionNodeChanged?.Invoke (this, new ExtensionNodeEventArgs (ExtensionChange.Add, node));
}
-
+
/// <summary>
/// Called when a child node is removed
/// </summary>
/// <param name="node">
/// Removed node.
/// </param>
+ /// <remarks>
+ /// Threading information: the thread on which the method is invoked is undefined.
+ /// Invocations to the virtual methods in this class are guaranteed to be done sequentially.
+ /// For example, OnChildNodeAdded will always be called before OnChildNodeRemoved for a given
+ /// child, and never concurrently.
+ /// </remarks>
protected virtual void OnChildNodeRemoved (ExtensionNode node)
{
- if (extensionNodeChanged != null)
- extensionNodeChanged (this, new ExtensionNodeEventArgs (ExtensionChange.Remove, node));
+ extensionNodeChanged?.Invoke (this, new ExtensionNodeEventArgs (ExtensionChange.Remove, node));
}
}
diff --git a/Mono.Addins/Mono.Addins/ExtensionNodeList.cs b/Mono.Addins/Mono.Addins/ExtensionNodeList.cs
index 729b5ae..fc90fc8 100644
--- a/Mono.Addins/Mono.Addins/ExtensionNodeList.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionNodeList.cs
@@ -36,7 +36,7 @@ namespace Mono.Addins
/// <summary>
/// A list of extension nodes.
/// </summary>
- public class ExtensionNodeList: IEnumerable
+ public class ExtensionNodeList: IEnumerable, IEnumerable<ExtensionNode>
{
internal List<ExtensionNode> list;
@@ -55,10 +55,7 @@ namespace Mono.Addins
/// </param>
public ExtensionNode this [int n] {
get {
- if (list == null)
- throw new System.IndexOutOfRangeException ();
- else
- return (ExtensionNode) list [n];
+ return (ExtensionNode) list [n];
}
}
@@ -70,14 +67,10 @@ namespace Mono.Addins
/// </param>
public ExtensionNode this [string id] {
get {
- if (list == null)
- return null;
- else {
- for (int n = list.Count - 1; n >= 0; n--)
- if (((ExtensionNode) list [n]).Id == id)
- return (ExtensionNode) list [n];
- return null;
- }
+ for (int n = list.Count - 1; n >= 0; n--)
+ if (list [n].Id == id)
+ return list [n];
+ return null;
}
}
@@ -86,8 +79,6 @@ namespace Mono.Addins
/// </summary>
public IEnumerator GetEnumerator ()
{
- if (list == null)
- return ((IList)Type.EmptyTypes).GetEnumerator ();
return list.GetEnumerator ();
}
@@ -95,7 +86,7 @@ namespace Mono.Addins
/// Number of nodes of the collection.
/// </summary>
public int Count {
- get { return list == null ? 0 : list.Count; }
+ get { return list.Count; }
}
/// <summary>
@@ -109,8 +100,12 @@ namespace Mono.Addins
/// </param>
public void CopyTo (ExtensionNode[] array, int index)
{
- if (list != null)
- list.CopyTo (array, index);
+ list.CopyTo (array, index);
+ }
+
+ IEnumerator<ExtensionNode> IEnumerable<ExtensionNode>.GetEnumerator ()
+ {
+ return list.GetEnumerator ();
}
}
diff --git a/Mono.Addins/Mono.Addins/ExtensionTree.cs b/Mono.Addins/Mono.Addins/ExtensionTree.cs
index ab49492..117eafe 100644
--- a/Mono.Addins/Mono.Addins/ExtensionTree.cs
+++ b/Mono.Addins/Mono.Addins/ExtensionTree.cs
@@ -41,7 +41,7 @@ namespace Mono.Addins
{
int internalId;
internal const string AutoIdPrefix = "__nid_";
- ExtensionContext context;
+ readonly ExtensionContext context;
public ExtensionTree (AddinEngine addinEngine, ExtensionContext context): base (addinEngine, "")
{
@@ -53,22 +53,21 @@ namespace Mono.Addins
}
- public void LoadExtension (string addin, Extension extension, List<TreeNode> addedNodes)
+ public void LoadExtension (ExtensionContextTransaction transaction, TreeNode tnode, string addin, Extension extension, List<TreeNode> addedNodes)
{
- TreeNode tnode = GetNode (extension.Path);
if (tnode == null) {
addinEngine.ReportError ("Can't load extensions for path '" + extension.Path + "'. Extension point not defined.", addin, null, false);
return;
}
int curPos = -1;
- LoadExtensionElement (tnode, addin, extension.ExtensionNodes, (ModuleDescription) extension.Parent, ref curPos, tnode.Condition, false, addedNodes);
+ LoadExtensionElement (transaction, tnode, addin, extension.ExtensionNodes, (ModuleDescription) extension.Parent, ref curPos, tnode.Condition, false, addedNodes);
}
- void LoadExtensionElement (TreeNode tnode, string addin, ExtensionNodeDescriptionCollection extension, ModuleDescription module, ref int curPos, BaseCondition parentCondition, bool inComplextCondition, List<TreeNode> addedNodes)
+ void LoadExtensionElement (ExtensionContextTransaction transaction, TreeNode parentNode, string addin, ExtensionNodeDescriptionCollection extension, ModuleDescription module, ref int curPos, BaseCondition parentCondition, bool inComplextCondition, List<TreeNode> addedNodes)
{
foreach (ExtensionNodeDescription elem in extension) {
-
+
if (inComplextCondition) {
parentCondition = ReadComplexCondition (elem, parentCondition);
inComplextCondition = false;
@@ -76,78 +75,76 @@ namespace Mono.Addins
}
if (elem.NodeName == "ComplexCondition") {
- LoadExtensionElement (tnode, addin, elem.ChildNodes, module, ref curPos, parentCondition, true, addedNodes);
+ LoadExtensionElement (transaction, parentNode, addin, elem.ChildNodes, module, ref curPos, parentCondition, true, addedNodes);
continue;
}
-
+
if (elem.NodeName == "Condition") {
Condition cond = new Condition (AddinEngine, elem, parentCondition);
- LoadExtensionElement (tnode, addin, elem.ChildNodes, module, ref curPos, cond, false, addedNodes);
+ LoadExtensionElement (transaction, parentNode, addin, elem.ChildNodes, module, ref curPos, cond, false, addedNodes);
continue;
}
- var pnode = tnode;
+ var pnode = parentNode;
ExtensionPoint extensionPoint = null;
while (pnode != null && (extensionPoint = pnode.ExtensionPoint) == null)
pnode = pnode.Parent;
-
+
string after = elem.GetAttribute ("insertafter");
if (after.Length == 0 && extensionPoint != null && curPos == -1)
after = extensionPoint.DefaultInsertAfter;
if (after.Length > 0) {
- int i = tnode.Children.IndexOfNode (after);
+ int i = parentNode.IndexOfChild (after);
if (i != -1)
- curPos = i+1;
+ curPos = i + 1;
}
string before = elem.GetAttribute ("insertbefore");
if (before.Length == 0 && extensionPoint != null && curPos == -1)
before = extensionPoint.DefaultInsertBefore;
if (before.Length > 0) {
- int i = tnode.Children.IndexOfNode (before);
+ int i = parentNode.IndexOfChild (before);
if (i != -1)
curPos = i;
}
// If node position is not explicitly set, add the node at the end
if (curPos == -1)
- curPos = tnode.Children.Count;
-
+ curPos = parentNode.Children.Count;
+
// Find the type of the node in this extension
- ExtensionNodeType ntype = addinEngine.FindType (tnode.ExtensionNodeSet, elem.NodeName, addin);
-
+ ExtensionNodeType ntype = addinEngine.FindType (transaction, parentNode.ExtensionNodeSet, elem.NodeName, addin);
+
if (ntype == null) {
- addinEngine.ReportError ("Node '" + elem.NodeName + "' not allowed in extension: " + tnode.GetPath (), addin, null, false);
+ addinEngine.ReportError ("Node '" + elem.NodeName + "' not allowed in extension: " + parentNode.GetPath (), addin, null, false);
continue;
}
-
+
string id = elem.GetAttribute ("id");
if (id.Length == 0)
id = AutoIdPrefix + (++internalId);
- TreeNode cnode = new TreeNode (addinEngine, id);
-
- ExtensionNode enode = ReadNode (cnode, addin, ntype, elem, module);
+ TreeNode childNode = new TreeNode (addinEngine, id);
+
+ ExtensionNode enode = ReadNode (childNode, addin, ntype, elem, module, transaction);
if (enode == null)
continue;
- cnode.Condition = parentCondition;
- cnode.ExtensionNodeSet = ntype;
- tnode.InsertChildNode (curPos, cnode);
- addedNodes.Add (cnode);
-
- if (cnode.Condition != null)
- Context.RegisterNodeCondition (cnode, cnode.Condition);
+ // Enables bulk update of children
+ parentNode.BeginChildrenUpdateTransaction (transaction);
+
+ childNode.Condition = parentCondition;
+ childNode.ExtensionNodeSet = ntype;
+ parentNode.InsertChild (transaction, curPos, childNode);
+ addedNodes.Add (childNode);
// Load children
if (elem.ChildNodes.Count > 0) {
int cp = 0;
- LoadExtensionElement (cnode, addin, elem.ChildNodes, module, ref cp, parentCondition, false, addedNodes);
+ LoadExtensionElement (transaction, childNode, addin, elem.ChildNodes, module, ref cp, parentCondition, false, addedNodes);
}
curPos++;
}
- if (Context.FireEvents)
- tnode.NotifyChildrenChanged ();
}
BaseCondition ReadComplexCondition (ExtensionNodeDescription elem, BaseCondition parentCondition)
@@ -176,11 +173,11 @@ namespace Mono.Addins
return new NullCondition ();
}
- public ExtensionNode ReadNode (TreeNode tnode, string addin, ExtensionNodeType ntype, ExtensionNodeDescription elem, ModuleDescription module)
+ public ExtensionNode ReadNode (TreeNode tnode, string addin, ExtensionNodeType ntype, ExtensionNodeDescription elem, ModuleDescription module, ExtensionContextTransaction transaction)
{
try {
if (ntype.Type == null) {
- if (!InitializeNodeType (ntype))
+ if (!InitializeNodeType (ntype, transaction))
return null;
}
@@ -202,19 +199,18 @@ namespace Mono.Addins
}
}
- bool InitializeNodeType (ExtensionNodeType ntype)
+ bool InitializeNodeType (ExtensionNodeType ntype, ExtensionContextTransaction transaction)
{
- RuntimeAddin p = addinEngine.GetAddin (ntype.AddinId);
- if (p == null) {
- if (!addinEngine.IsAddinLoaded (ntype.AddinId)) {
- if (!addinEngine.LoadAddin (null, ntype.AddinId, false))
- return false;
- p = addinEngine.GetAddin (ntype.AddinId);
- if (p == null) {
- addinEngine.ReportError ("Add-in not found", ntype.AddinId, null, false);
- return false;
- }
+ RuntimeAddin p = addinEngine.GetAddin(transaction.GetAddinEngineTransaction(), ntype.AddinId);
+ if (p == null)
+ {
+ 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(engineTransaction, ntype.AddinId);
}
// If no type name is provided, use TypeExtensionNode by default
diff --git a/Mono.Addins/Mono.Addins/InstanceExtensionNode.cs b/Mono.Addins/Mono.Addins/InstanceExtensionNode.cs
index 3d6640d..37e2791 100644
--- a/Mono.Addins/Mono.Addins/InstanceExtensionNode.cs
+++ b/Mono.Addins/Mono.Addins/InstanceExtensionNode.cs
@@ -69,8 +69,13 @@ namespace Mono.Addins
/// </remarks>
public object GetInstance ()
{
- if (cachedInstance == null)
- cachedInstance = CreateInstance ();
+ if (cachedInstance == null) {
+ lock (localLock) {
+ // Use locking here to avoid creating more than one instance per ExtensionNode
+ if (cachedInstance == null)
+ cachedInstance = CreateInstance ();
+ }
+ }
return cachedInstance;
}
diff --git a/Mono.Addins/Mono.Addins/RuntimeAddin.cs b/Mono.Addins/Mono.Addins/RuntimeAddin.cs
index 9b05ba2..abbbf1f 100644
--- a/Mono.Addins/Mono.Addins/RuntimeAddin.cs
+++ b/Mono.Addins/Mono.Addins/RuntimeAddin.cs
@@ -49,27 +49,37 @@ namespace Mono.Addins
/// </summary>
public class RuntimeAddin
{
- string id;
- string baseDirectory;
+ readonly string id;
+ readonly string baseDirectory;
+ readonly Addin ainfo;
+ readonly RuntimeAddin parentAddin;
+ readonly AddinEngine addinEngine;
+ readonly ModuleDescription module;
+
string privatePath;
- Addin ainfo;
- RuntimeAddin parentAddin;
Dictionary<string, Assembly> loadedAssemblies = new Dictionary<string, Assembly>();
bool fullyLoadedAssemblies;
-
- RuntimeAddin[] depAddins;
- ResourceManager[] resourceManagers;
+
+ RuntimeAddin [] depAddins;
+ ResourceManager [] resourceManagers;
AddinLocalizer localizer;
- ModuleDescription module;
- AddinEngine addinEngine;
ExtensionNodeDescription localizerDescription;
- internal RuntimeAddin (AddinEngine addinEngine)
+ internal RuntimeAddin (AddinEngine addinEngine, Addin iad)
{
this.addinEngine = addinEngine;
+
+ ainfo = iad;
+
+ AddinDescription description = iad.Description;
+ id = description.AddinId;
+ baseDirectory = description.BasePath;
+ module = description.MainModule;
+ module.RuntimeAddin = this;
+ localizerDescription = description.Localizer;
}
-
+
internal RuntimeAddin (AddinEngine addinEngine, RuntimeAddin parentAddin, ModuleDescription module)
{
this.addinEngine = addinEngine;
@@ -345,7 +355,7 @@ namespace Mono.Addins
foreach (var kvp in loadedAssemblies) {
var assembly = kvp.Value;
- if (string.IsNullOrEmpty (assemblyName) || assembly.FullName == assemblyName) {
+ if (string.IsNullOrEmpty (assemblyName) || assembly.GetName().Name == assemblyName) {
Type type = assembly.GetType (typeName, false);
if (type != null)
return type;
@@ -619,19 +629,6 @@ namespace Mono.Addins
return addin;
}
- internal AddinDescription Load (Addin iad)
- {
- ainfo = iad;
-
- AddinDescription description = iad.Description;
- id = description.AddinId;
- baseDirectory = description.BasePath;
- module = description.MainModule;
- module.RuntimeAddin = this;
- localizerDescription = description.Localizer;
- return description;
- }
-
AddinLocalizer LoadLocalizer ()
{
if (localizerDescription != null) {
@@ -723,9 +720,9 @@ namespace Mono.Addins
}
}
- internal void UnloadExtensions ()
+ internal void UnloadExtensions (ExtensionContextTransaction transaction)
{
- addinEngine.UnregisterAddinNodeSets (id);
+ addinEngine.UnregisterAddinNodeSets (transaction, id);
}
bool CheckAddinDependencies (ModuleDescription module, bool forceLoadAssemblies)
@@ -734,10 +731,11 @@ namespace Mono.Addins
AddinDependency pdep = dep as AddinDependency;
if (pdep == null)
continue;
- if (!addinEngine.IsAddinLoaded (pdep.FullAddinId))
+ var addin = addinEngine.GetAddin(pdep.FullAddinId);
+ if (addin == null)
return false;
if (forceLoadAssemblies)
- addinEngine.GetAddin (pdep.FullAddinId).EnsureAssembliesLoaded ();
+ addin.EnsureAssembliesLoaded ();
}
return true;
}
diff --git a/Mono.Addins/Mono.Addins/TreeNode.cs b/Mono.Addins/Mono.Addins/TreeNode.cs
index e7fa478..6beab23 100644
--- a/Mono.Addins/Mono.Addins/TreeNode.cs
+++ b/Mono.Addins/Mono.Addins/TreeNode.cs
@@ -32,15 +32,20 @@ using System.Text;
using System.Collections;
using Mono.Addins.Description;
using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading;
namespace Mono.Addins
{
class TreeNode
{
- List<TreeNode> childrenList;
- TreeNodeCollection children;
+ ImmutableArray<TreeNode> children;
+
+ ImmutableArray<TreeNode>.Builder childrenBuilder;
+ ExtensionContextTransaction buildTransaction;
+
ExtensionNode extensionNode;
- bool childrenLoaded;
+ bool childrenFromExtensionsLoaded;
string id;
TreeNode parent;
ExtensionNodeSet nodeTypes;
@@ -52,10 +57,12 @@ namespace Mono.Addins
{
this.id = id;
this.addinEngine = addinEngine;
-
+
+ children = ImmutableArray<TreeNode>.Empty;
+
// Root node
if (id.Length == 0)
- childrenLoaded = true;
+ childrenFromExtensionsLoaded = true;
}
public AddinEngine AddinEngine {
@@ -64,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);
}
@@ -76,14 +87,36 @@ 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;
}
}
-
+
+ public void NotifyAddinUnloaded ()
+ {
+ extensionNode?.NotifyAddinUnloaded ();
+ }
+
+ public void NotifyAddinLoaded ()
+ {
+ extensionNode?.NotifyAddinLoaded ();
+ }
+
+ public bool HasExtensionNode {
+ get {
+ return extensionNode != null || extensionPoint != null;
+ }
+ }
+
+ public string AddinId {
+ get {
+ return extensionNode != null ? extensionNode.AddinId : extensionPoint?.RootAddin;
+ }
+ }
+
public ExtensionPoint ExtensionPoint {
get { return extensionPoint; }
set { extensionPoint = value; }
@@ -126,33 +159,91 @@ namespace Mono.Addins
}
}
- public bool ChildrenLoaded {
- get { return childrenLoaded; }
+ public bool ChildrenFromExtensionsLoaded {
+ get { return childrenFromExtensionsLoaded; }
}
- public void AddChildNode (TreeNode node)
+ public void AddChildNode (ExtensionContextTransaction transaction, TreeNode node)
{
- node.parent = this;
- if (childrenList == null)
- childrenList = new List<TreeNode> ();
- childrenList.Add (node);
+ node.SetParent(transaction, this);
+
+ if (childrenBuilder != null)
+ childrenBuilder.Add (node);
+ else
+ children = children.Add (node);
+
+ transaction.ReportChildrenChanged (this);
}
-
- public void InsertChildNode (int n, TreeNode node)
+
+ public void InsertChild (ExtensionContextTransaction transaction, int n, TreeNode node)
{
- node.parent = this;
- if (childrenList == null)
- childrenList = new List<TreeNode> ();
- childrenList.Insert (n, node);
-
- // Dont call NotifyChildrenChanged here. It is called by ExtensionTree,
- // after inserting all children of the node.
+ node.SetParent (transaction, this);
+
+ if (childrenBuilder != null)
+ childrenBuilder.Insert (n, node);
+ else
+ children = children.Insert (n, node);
+
+ transaction.ReportChildrenChanged (this);
}
-
- internal int ChildCount {
- get { return childrenList == null ? 0 : childrenList.Count; }
+
+ public void RemoveChild (ExtensionContextTransaction transaction, TreeNode node)
+ {
+ node.SetParent (transaction, null);
+
+ if (childrenBuilder != null)
+ childrenBuilder.Remove (node);
+ else
+ children = children.Remove (node);
+
+ transaction.ReportChildrenChanged (this);
}
-
+
+ void SetParent (ExtensionContextTransaction transaction, TreeNode newParent)
+ {
+ if (parent == newParent)
+ return;
+
+ if (this.parent != null && newParent != null)
+ throw new InvalidOperationException ("Node already has a parent");
+
+ var currentCtx = Context;
+ if (currentCtx != null && currentCtx != transaction.Context)
+ throw new InvalidOperationException ("Invalid context");
+
+ this.parent = newParent;
+
+ if (newParent != null) {
+ if (newParent.Context != null)
+ OnAttachedToContext (transaction);
+ } else {
+ if (currentCtx != null)
+ OnDetachedFromContext (transaction);
+ }
+ }
+
+ void OnAttachedToContext (ExtensionContextTransaction transaction)
+ {
+ // Once the node is part of a context, let's register the condition
+ if (Condition != null)
+ transaction.RegisterNodeCondition (this, Condition);
+
+ // Propagate event to children
+ foreach (var child in GetLoadedChildren ())
+ child.OnAttachedToContext (transaction);
+ }
+
+ void OnDetachedFromContext (ExtensionContextTransaction transaction)
+ {
+ // Node being removed, unregister from context
+ if (Condition != null)
+ transaction.UnregisterNodeCondition (this, Condition);
+
+ // Propagate event to children
+ foreach (var child in GetLoadedChildren ())
+ child.OnDetachedFromContext (transaction);
+ }
+
public ExtensionNode GetExtensionNode (string path, string childId)
{
TreeNode node = GetNode (path, childId);
@@ -177,8 +268,13 @@ namespace Mono.Addins
{
return GetNode (path, false);
}
-
- public TreeNode GetNode (string path, bool buildPath)
+
+ public TreeNode GetNode(string path, bool buildPath)
+ {
+ return GetNode(path, buildPath, null);
+ }
+
+ public TreeNode GetNode (string path, bool buildPath, ExtensionContextTransaction transaction)
{
if (path.StartsWith ("/"))
path = path.Substring (1);
@@ -187,38 +283,153 @@ namespace Mono.Addins
TreeNode curNode = this;
foreach (string part in parts) {
- int i = curNode.Children.IndexOfNode (part);
- if (i != -1) {
- curNode = curNode.Children [i];
+ curNode.EnsureChildrenLoaded(transaction);
+ var node = curNode.GetChildNode (part);
+ if (node != null) {
+ curNode = node;
continue;
}
if (buildPath) {
- TreeNode newNode = new TreeNode (addinEngine, part);
- curNode.AddChildNode (newNode);
- curNode = newNode;
+ transaction = BeginContextTransaction (transaction, out var dispose);
+ try {
+ // Check again inside the lock, just in case
+ curNode.EnsureChildrenLoaded(transaction);
+ node = curNode.GetChildNode (part);
+ if (node != null) {
+ curNode = node;
+ continue;
+ }
+
+ TreeNode newNode = new TreeNode (addinEngine, part);
+ curNode.AddChildNode (transaction, newNode);
+ curNode = newNode;
+ } finally {
+ if (dispose)
+ transaction.Dispose ();
+ }
} else
return null;
}
return curNode;
}
-
- public TreeNodeCollection Children {
+
+ public TreeNode GetChildNode (string id)
+ {
+ var childrenList = Children;
+ foreach (var node in childrenList) {
+ if (node.Id == id)
+ return node;
+ }
+ return null;
+ }
+
+ public int IndexOfChild (string id)
+ {
+ var childrenList = Children;
+ for (int n = 0; n < childrenList.Count; n++) {
+ if (childrenList [n].Id == id)
+ return n;
+ }
+ return -1;
+ }
+
+ public IReadOnlyList<TreeNode> Children {
get {
- if (!childrenLoaded) {
- childrenLoaded = true;
- if (extensionPoint != null)
- Context.LoadExtensions (GetPath ());
- // We have to keep the relation info, since add-ins may be loaded/unloaded
+ if (IsInChildrenUpdateTransaction) {
+ return childrenBuilder;
}
- if (childrenList == null)
- return TreeNodeCollection.Empty;
- if (children == null)
- children = new TreeNodeCollection (childrenList);
+ EnsureChildrenLoaded(null);
+ return (IReadOnlyList<TreeNode>)childrenBuilder ?? (IReadOnlyList<TreeNode>)children;
+ }
+ }
+
+ void EnsureChildrenLoaded(ExtensionContextTransaction transaction)
+ {
+ if (IsInChildrenUpdateTransaction)
+ return;
+
+ if (!childrenFromExtensionsLoaded)
+ {
+ transaction = BeginContextTransaction(transaction, out var disposeTransaction);
+ try
+ {
+ if (!childrenFromExtensionsLoaded)
+ {
+ if (extensionPoint != null)
+ {
+ BeginChildrenUpdateTransaction(transaction);
+ Context.LoadExtensions(transaction, GetPath(), this);
+ // We have to keep the reference to the extension point, since add-ins may be loaded/unloaded
+ }
+ }
+ }
+ finally
+ {
+ childrenFromExtensionsLoaded = true;
+ if (disposeTransaction)
+ transaction.Dispose();
+ }
+ }
+ }
+
+ IReadOnlyList<TreeNode> GetLoadedChildren ()
+ {
+ if (IsInChildrenUpdateTransaction)
+ return childrenBuilder;
+ else
return children;
+ }
+
+ /// <summary>
+ /// Returns true if the tree node has a child update transaction in progress,
+ /// and the current thread is the one that created the transaction.
+ /// </summary>
+ bool IsInChildrenUpdateTransaction => childrenBuilder != null && Context.IsCurrentThreadInTransaction;
+
+ ExtensionContextTransaction BeginContextTransaction (ExtensionContextTransaction currentTransaction, out bool dispose)
+ {
+ if (currentTransaction != null)
+ {
+ dispose = false;
+ return currentTransaction;
+ }
+ else if (IsInChildrenUpdateTransaction) {
+ dispose = false;
+ return buildTransaction;
+ } else {
+ dispose = true;
+ return Context.BeginTransaction ();
}
}
-
+
+ public void BeginChildrenUpdateTransaction (ExtensionContextTransaction transaction)
+ {
+ // If a transaction already started, just reuse it
+ if (buildTransaction != null)
+ return;
+
+ childrenBuilder = children.ToBuilder ();
+ this.buildTransaction = transaction;
+
+ transaction.RegisterChildrenUpdateTransaction (this);
+ }
+
+ internal void CommitChildrenUpdateTransaction ()
+ {
+ if (buildTransaction == null)
+ throw new InvalidOperationException ("No transaction started");
+
+ children = childrenBuilder.ToImmutable ();
+
+ var transaction = buildTransaction;
+
+ childrenBuilder = null;
+ buildTransaction = null;
+
+ transaction.ReportChildrenChanged (this);
+ }
+
public string GetPath ()
{
int num=0;
@@ -241,9 +452,9 @@ namespace Mono.Addins
public void NotifyAddinLoaded (RuntimeAddin ad, bool recursive)
{
if (extensionNode != null && extensionNode.AddinId == ad.Addin.Id)
- extensionNode.OnAddinLoaded ();
- if (recursive && childrenLoaded) {
- foreach (TreeNode node in Children.Clone ())
+ extensionNode.NotifyAddinLoaded ();
+ if (recursive && childrenFromExtensionsLoaded) {
+ foreach (TreeNode node in Children)
node.NotifyAddinLoaded (ad, true);
}
}
@@ -257,10 +468,10 @@ namespace Mono.Addins
TreeNode curNode = this;
foreach (string part in parts) {
- int i = curNode.Children.IndexOfNode (part);
- if (i != -1) {
- curNode = curNode.Children [i];
- if (!curNode.ChildrenLoaded)
+ var node = curNode.GetChildNode (part);
+ if (node != null) {
+ curNode = node;
+ if (!curNode.ChildrenFromExtensionsLoaded)
return null;
if (curNode.ExtensionPoint != null)
return curNode.ExtensionPoint;
@@ -279,56 +490,16 @@ namespace Mono.Addins
id = null;
}
- if (childrenLoaded) {
+ if (childrenFromExtensionsLoaded) {
// Deep-first search, to make sure children are removed before the parent.
foreach (TreeNode node in Children)
node.FindAddinNodes (id, nodes);
}
- if (id == null || (ExtensionNode != null && ExtensionNode.AddinId == id))
+ if (id == null || AddinId == id)
nodes.Add (this);
}
- public bool FindExtensionPathByType (IProgressStatus monitor, Type type, string nodeName, out string path, out string pathNodeName)
- {
- if (extensionPoint != null) {
- foreach (ExtensionNodeType nt in extensionPoint.NodeSet.NodeTypes) {
- if (nt.ObjectTypeName.Length > 0 && (nodeName.Length == 0 || nodeName == nt.Id)) {
- RuntimeAddin addin = addinEngine.GetAddin (extensionPoint.RootAddin);
- Type ot = addin.GetType (nt.ObjectTypeName, true);
- if (ot != null) {
- if (ot.IsAssignableFrom (type)) {
- path = extensionPoint.Path;
- pathNodeName = nt.Id;
- return true;
- }
- }
- else
- monitor.ReportError ("Type '" + nt.ObjectTypeName + "' not found in add-in '" + Id + "'", null);
- }
- }
- }
- else {
- foreach (TreeNode node in Children) {
- if (node.FindExtensionPathByType (monitor, type, nodeName, out path, out pathNodeName))
- return true;
- }
- }
- path = null;
- pathNodeName = null;
- return false;
- }
-
- public void Remove ()
- {
- if (parent != null) {
- if (Condition != null)
- Context.UnregisterNodeCondition (this, Condition);
- parent.childrenList.Remove (this);
- parent.NotifyChildrenChanged ();
- }
- }
-
public bool NotifyChildrenChanged ()
{
if (extensionNode != null)
@@ -337,18 +508,21 @@ namespace Mono.Addins
return false;
}
- public void ResetCachedData ()
+ public void ResetCachedData (ExtensionContextTransaction transaction)
{
if (extensionPoint != null) {
string aid = Addin.GetIdName (extensionPoint.ParentAddinDescription.AddinId);
- RuntimeAddin ad = addinEngine.GetAddin (aid);
+ RuntimeAddin ad = addinEngine.GetAddin (transaction.GetAddinEngineTransaction(), aid);
if (ad != null)
extensionPoint = ad.Addin.Description.ExtensionPoints [GetPath ()];
}
- if (childrenList != null) {
- foreach (TreeNode cn in childrenList)
- cn.ResetCachedData ();
+ if (childrenBuilder != null) {
+ foreach (TreeNode cn in childrenBuilder)
+ cn.ResetCachedData (transaction);
}
+
+ foreach (TreeNode cn in children)
+ cn.ResetCachedData (transaction);
}
}
}
diff --git a/Mono.Addins/Mono.Addins/TreeNodeCollection.cs b/Mono.Addins/Mono.Addins/TreeNodeCollection.cs
deleted file mode 100644
index 9da13b7..0000000
--- a/Mono.Addins/Mono.Addins/TreeNodeCollection.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-// TreeNodeCollection.cs
-//
-// Author:
-// Lluis Sanchez Gual
-//
-// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Mono.Addins
-{
- class TreeNodeCollection: IEnumerable
- {
- List<TreeNode> list;
-
- internal static TreeNodeCollection Empty = new TreeNodeCollection (null);
-
- public TreeNodeCollection (List<TreeNode> list)
- {
- this.list = list;
- }
-
- public IEnumerator GetEnumerator ()
- {
- if (list != null)
- return list.GetEnumerator ();
- else
- return Type.EmptyTypes.GetEnumerator ();
- }
-
- public TreeNode this [int n] {
- get {
- if (list != null)
- return list [n];
- else
- throw new System.IndexOutOfRangeException ();
- }
- }
-
- public int IndexOfNode (string id)
- {
- for (int n=0; n<Count; n++) {
- if (this [n].Id == id)
- return n;
- }
- return -1;
- }
-
- public int Count {
- get { return list != null ? list.Count : 0; }
- }
-
- public TreeNodeCollection Clone ()
- {
- if (list != null)
- return new TreeNodeCollection (list.ToList ());
- else
- return Empty;
- }
- }
-}
diff --git a/Test/UnitTests/TestMultithreading.cs b/Test/UnitTests/TestMultithreading.cs
new file mode 100644
index 0000000..646cdef
--- /dev/null
+++ b/Test/UnitTests/TestMultithreading.cs
@@ -0,0 +1,243 @@
+
+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 = "";
+
+ // Threads check the number of nodes in /SimpleApp/ConditionedWriters, which changes
+ // from 0 to 1 as the GlobalInfoCondition condition changes
+
+ testData.StartThreads (RunChildConditionAttributeTest);
+
+ for (int n = 0; n < 20; n++) {
+ Console.WriteLine ("Step " + n);
+ testData.CheckCounters (0, 10000);
+
+ GlobalInfoCondition.Value = "foo";
+
+ testData.CheckCounters (1, 10000);
+
+ GlobalInfoCondition.Value = "";
+ }
+ }
+
+ void RunChildConditionAttributeTest (int index, TestData data)
+ {
+ while (!data.Stopped) {
+ var writers = AddinManager.GetExtensionObjects<IWriter> ("/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<IWriter> ("/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, 10000);
+
+ var ainfo2 = AddinManager.Registry.GetAddin ("SimpleApp.FileContentExtension");
+ ainfo2.Enabled = false;
+
+ testData.CheckCounters (2, 10000);
+
+ ainfo1.Enabled = true;
+ ainfo2.Enabled = true;
+ }
+ }
+
+ [Test]
+ public void EventsMultithread()
+ {
+ int threads = 50;
+
+ int totalAdded = 0;
+ int totalRemoved = 0;
+ int nodesCount = 0;
+ int minCount = 0;
+ int maxCount = 0;
+
+ var node = AddinManager.GetExtensionNode("/SimpleApp/Writers");
+
+ nodesCount = 0;
+
+ node.ExtensionNodeChanged += (s, args) =>
+ {
+ if (args.Change == ExtensionChange.Add)
+ {
+ nodesCount++;
+ totalAdded++;
+ }
+ else
+ {
+ nodesCount--;
+ totalRemoved++;
+ }
+
+ if (nodesCount < minCount)
+ minCount = nodesCount;
+ if (nodesCount > maxCount)
+ maxCount = nodesCount;
+ };
+
+ Assert.AreEqual(4, nodesCount);
+
+ minCount = 4;
+ maxCount = 4;
+ totalAdded = 0;
+ totalRemoved = 0;
+
+ var ainfo1 = AddinManager.Registry.GetAddin("SimpleApp.HelloWorldExtension");
+ var ainfo2 = AddinManager.Registry.GetAddin("SimpleApp.FileContentExtension");
+
+ using var enablers = new TestData(threads);
+
+ enablers.StartThreads((index, data) =>
+ {
+ var random = new Random(10000 + index);
+ int iterations = 100;
+ while (--iterations > 0)
+ {
+ 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;
+ }
+ }
+ data.Counters[index] = 1;
+ });
+
+ // Wait for the threads to do the work. 5 seconds should be enough
+ enablers.CheckCounters(1, 20000);
+
+ // Go back to the initial status
+
+ ainfo1.Enabled = true;
+ ainfo2.Enabled = true;
+
+ // If all events have been sent correctly, the node count should have never gone below 2 and over 4.
+
+ 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]
+ 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, 10000);
+
+ 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.GetChildNodes().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 ("Expected " + expectedResult);
+ }
+
+ public void Dispose ()
+ {
+ Stopped = true;
+ }
+
+ public void StartThreads (Action<int, TestData> 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 263f64d..5579b6a 100644
--- a/Test/UnitTests/UnitTests.csproj
+++ b/Test/UnitTests/UnitTests.csproj
@@ -9,7 +9,6 @@
<AssemblyOriginatorKeyFile>..\..\mono-addins.snk</AssemblyOriginatorKeyFile>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
- <DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
diff --git a/Version.props b/Version.props
index 6bbc3db..8714b4a 100644
--- a/Version.props
+++ b/Version.props
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
- <PackageVersion>1.3.14</PackageVersion>
+ <PackageVersion>1.3.15</PackageVersion>
<Authors>Microsoft</Authors>
<Owners>microsoft, xamarin</Owners>
<PackageLicenseUrl>https://github.com/mono/mono-addins/blob/main/COPYING</PackageLicenseUrl>