Skip to content

Commit e406ef8

Browse files
committed
Fixed #62 A concurrency issue when adding or removing plugins
1 parent 2996f3d commit e406ef8

File tree

6 files changed

+77
-232
lines changed

6 files changed

+77
-232
lines changed

src/Exceptionless.Signed/project.json

Lines changed: 8 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -73,38 +73,14 @@
7373
]
7474
},
7575
"dependencies": {
76-
"System.Collections": "4.0.11",
77-
"System.Collections.Concurrent": "4.0.12",
76+
"NETStandard.Library": "1.6.0",
77+
7878
"System.ComponentModel.TypeConverter": "4.1.0",
7979
"System.Data.Common": "4.1.0",
80-
"System.Diagnostics.Debug": "4.0.11",
8180
"System.Dynamic.Runtime": "4.0.11",
82-
"System.Globalization": "4.0.11",
83-
"System.IO": "4.1.0",
84-
"System.IO.Compression": "4.1.0",
85-
"System.Linq": "4.1.0",
86-
"System.Linq.Expressions": "4.1.0",
87-
"System.Net.Http": "4.1.0",
88-
"System.Net.Primitives": "4.0.11",
89-
"System.ObjectModel": "4.0.12",
90-
"System.Reflection": "4.1.0",
9181
"System.Reflection.Emit.ILGeneration": "4.0.1",
9282
"System.Reflection.Emit.Lightweight": "4.0.1",
93-
"System.Reflection.Extensions": "4.0.1",
94-
"System.Resources.ResourceManager": "4.0.1",
95-
"System.Runtime": "4.1.0",
96-
"System.Runtime.Extensions": "4.1.0",
97-
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
98-
"System.Runtime.Numerics": "4.0.1",
9983
"System.Runtime.Serialization.Primitives": "4.1.1",
100-
"System.Text.Encoding": "4.0.11",
101-
"System.Text.Encoding.Extensions": "4.0.11",
102-
"System.Text.RegularExpressions": "4.1.0",
103-
"System.Threading": "4.0.11",
104-
"System.Threading.Tasks": "4.0.11",
105-
"System.Threading.Timer": "4.0.1",
106-
"System.Xml.ReaderWriter": "4.0.11",
107-
"System.Xml.XDocument": "4.0.11",
10884
"System.Xml.XmlSerializer": "4.0.11"
10985
}
11086
},
@@ -119,50 +95,23 @@
11995
]
12096
},
12197
"dependencies": {
98+
"NETStandard.Library": "1.6.0",
99+
122100
"Microsoft.Extensions.PlatformAbstractions": "1.0.0",
123101
"System.AppDomain": "2.0.11",
124-
"System.Collections": "4.0.11",
125-
"System.Collections.Concurrent": "4.0.12",
126102
"System.ComponentModel.TypeConverter": "4.1.0",
127103
"System.Data.Common": "4.1.0",
128-
"System.Diagnostics.Debug": "4.0.11",
129104
"System.Diagnostics.Process": "4.1.0",
130105
"System.Diagnostics.StackTrace": "4.0.1",
131-
"System.Diagnostics.Tools": "4.0.1",
132106
"System.Diagnostics.TraceSource": "4.0.0",
133107
"System.Dynamic.Runtime": "4.0.11",
134-
"System.Globalization": "4.0.11",
135-
"System.IO": "4.1.0",
136-
"System.IO.Compression": "4.1.0",
137-
"System.IO.FileSystem": "4.0.1",
138-
"System.Linq": "4.1.0",
139-
"System.Linq.Expressions": "4.1.0",
140-
"System.Net.Http": "4.1.0",
141108
"System.Net.NameResolution": "4.0.0",
142-
"System.Net.Primitives": "4.0.11",
143-
"System.ObjectModel": "4.0.12",
144-
"System.Reflection": "4.1.0",
145109
"System.Reflection.Emit.ILGeneration": "4.0.1",
146110
"System.Reflection.Emit.Lightweight": "4.0.1",
147-
"System.Reflection.Extensions": "4.0.1",
148-
"System.Reflection.TypeExtensions": "4.1.0",
149-
"System.Resources.ResourceManager": "4.0.1",
150-
"System.Runtime": "4.1.0",
151-
"System.Runtime.Extensions": "4.1.0",
152-
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
153-
"System.Runtime.Numerics": "4.0.1",
154111
"System.Runtime.Serialization.Primitives": "4.1.1",
155112
"System.Security.AccessControl": "4.0.0",
156-
"System.Text.Encoding": "4.0.11",
157-
"System.Text.Encoding.Extensions": "4.0.11",
158-
"System.Text.RegularExpressions": "4.1.0",
159-
"System.Threading": "4.0.11",
160113
"System.Threading.AccessControl": "4.0.0",
161-
"System.Threading.Tasks": "4.0.11",
162114
"System.Threading.Thread": "4.0.0",
163-
"System.Threading.Timer": "4.0.1",
164-
"System.Xml.ReaderWriter": "4.0.11",
165-
"System.Xml.XDocument": "4.0.11",
166115
"System.Xml.XmlDocument": "4.0.1",
167116
"System.Xml.XmlSerializer": "4.0.11"
168117
}
@@ -178,51 +127,24 @@
178127
]
179128
},
180129
"dependencies": {
130+
"NETStandard.Library": "1.6.0",
131+
181132
"Microsoft.Extensions.PlatformAbstractions": "1.0.0",
182133
"System.AppDomain": "2.0.11",
183-
"System.Collections": "4.0.11",
184-
"System.Collections.Concurrent": "4.0.12",
185134
"System.ComponentModel.TypeConverter": "4.1.0",
186135
"System.Data.Common": "4.1.0",
187-
"System.Diagnostics.Debug": "4.0.11",
188136
"System.Diagnostics.Process": "4.1.0",
189137
"System.Diagnostics.StackTrace": "4.0.1",
190-
"System.Diagnostics.Tools": "4.0.1",
191138
"System.Diagnostics.TraceSource": "4.0.0",
192139
"System.Dynamic.Runtime": "4.0.11",
193-
"System.Globalization": "4.0.11",
194-
"System.IO": "4.1.0",
195-
"System.IO.Compression": "4.1.0",
196-
"System.IO.FileSystem": "4.0.1",
197140
"System.IO.IsolatedStorage": "4.0.1",
198-
"System.Linq": "4.1.0",
199-
"System.Linq.Expressions": "4.1.0",
200-
"System.Net.Http": "4.1.0",
201141
"System.Net.NameResolution": "4.0.0",
202-
"System.Net.Primitives": "4.0.11",
203-
"System.ObjectModel": "4.0.12",
204-
"System.Reflection": "4.1.0",
205142
"System.Reflection.Emit.ILGeneration": "4.0.1",
206143
"System.Reflection.Emit.Lightweight": "4.0.1",
207-
"System.Reflection.Extensions": "4.0.1",
208-
"System.Reflection.TypeExtensions": "4.1.0",
209-
"System.Resources.ResourceManager": "4.0.1",
210-
"System.Runtime": "4.1.0",
211-
"System.Runtime.Extensions": "4.1.0",
212-
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
213-
"System.Runtime.Numerics": "4.0.1",
214144
"System.Runtime.Serialization.Primitives": "4.1.1",
215145
"System.Security.AccessControl": "4.0.0",
216-
"System.Text.Encoding": "4.0.11",
217-
"System.Text.Encoding.Extensions": "4.0.11",
218-
"System.Text.RegularExpressions": "4.1.0",
219-
"System.Threading": "4.0.11",
220146
"System.Threading.AccessControl": "4.0.0",
221-
"System.Threading.Tasks": "4.0.11",
222147
"System.Threading.Thread": "4.0.0",
223-
"System.Threading.Timer": "4.0.1",
224-
"System.Xml.ReaderWriter": "4.0.11",
225-
"System.Xml.XDocument": "4.0.11",
226148
"System.Xml.XmlDocument": "4.0.1",
227149
"System.Xml.XmlSerializer": "4.0.11"
228150
}
@@ -238,51 +160,24 @@
238160
]
239161
},
240162
"dependencies": {
163+
"NETStandard.Library": "1.6.0",
164+
241165
"Microsoft.Extensions.PlatformAbstractions": "1.0.0",
242166
"System.AppDomain": "2.0.11",
243-
"System.Collections": "4.0.11",
244-
"System.Collections.Concurrent": "4.0.12",
245167
"System.ComponentModel.TypeConverter": "4.1.0",
246168
"System.Data.Common": "4.1.0",
247-
"System.Diagnostics.Debug": "4.0.11",
248169
"System.Diagnostics.Process": "4.1.0",
249170
"System.Diagnostics.StackTrace": "4.0.1",
250-
"System.Diagnostics.Tools": "4.0.1",
251171
"System.Diagnostics.TraceSource": "4.0.0",
252172
"System.Dynamic.Runtime": "4.0.11",
253-
"System.Globalization": "4.0.11",
254-
"System.IO": "4.1.0",
255-
"System.IO.Compression": "4.1.0",
256-
"System.IO.FileSystem": "4.0.1",
257173
"System.IO.IsolatedStorage": "4.0.1",
258-
"System.Linq": "4.1.0",
259-
"System.Linq.Expressions": "4.1.0",
260-
"System.Net.Http": "4.1.0",
261174
"System.Net.NameResolution": "4.0.0",
262-
"System.Net.Primitives": "4.0.11",
263-
"System.ObjectModel": "4.0.12",
264-
"System.Reflection": "4.1.0",
265175
"System.Reflection.Emit.ILGeneration": "4.0.1",
266176
"System.Reflection.Emit.Lightweight": "4.0.1",
267-
"System.Reflection.Extensions": "4.0.1",
268-
"System.Reflection.TypeExtensions": "4.1.0",
269-
"System.Resources.ResourceManager": "4.0.1",
270-
"System.Runtime": "4.1.0",
271-
"System.Runtime.Extensions": "4.1.0",
272-
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
273-
"System.Runtime.Numerics": "4.0.1",
274177
"System.Runtime.Serialization.Primitives": "4.1.1",
275178
"System.Security.AccessControl": "4.0.0",
276-
"System.Text.Encoding": "4.0.11",
277-
"System.Text.Encoding.Extensions": "4.0.11",
278-
"System.Text.RegularExpressions": "4.1.0",
279-
"System.Threading": "4.0.11",
280179
"System.Threading.AccessControl": "4.0.0",
281-
"System.Threading.Tasks": "4.0.11",
282-
"System.Threading.Timer": "4.0.1",
283180
"System.Threading.Thread": "4.0.0",
284-
"System.Xml.ReaderWriter": "4.0.11",
285-
"System.Xml.XDocument": "4.0.11",
286181
"System.Xml.XmlDocument": "4.0.1",
287182
"System.Xml.XmlSerializer": "4.0.11"
288183
}

src/Exceptionless.Tests/Plugins/PluginTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,23 @@ public void ErrorPlugin_IgnoredProperties() {
460460
Assert.Equal("{\"RandomValue\":\"Test\"}", json);
461461
}
462462
}
463+
464+
[Fact]
465+
public void CanAddPluginConcurrently() {
466+
var client = CreateClient();
467+
foreach (var plugin in client.Configuration.Plugins)
468+
client.Configuration.RemovePlugin(plugin.Key);
469+
470+
Assert.Equal(0, client.Configuration.Plugins.Count());
471+
472+
Parallel.For(0, 1000, i => {
473+
client.Configuration.AddPlugin<EnvironmentInfoPlugin>();
474+
});
475+
476+
Assert.Equal(1, client.Configuration.Plugins.Count());
477+
client.Configuration.RemovePlugin<EnvironmentInfoPlugin>();
478+
Assert.Equal(0, client.Configuration.Plugins.Count());
479+
}
463480

464481
[Fact]
465482
public void EnvironmentInfo_CanRunInParallel() {

src/Exceptionless/Configuration/ExceptionlessConfiguration.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.Linq;
@@ -275,13 +276,17 @@ public void AddEventExclusion(Func<Event, bool> eventExclusionCallback) {
275276

276277
#region Plugins
277278

279+
#if !PORTABLE
280+
private readonly ConcurrentDictionary<string, PluginRegistration> _plugins = new ConcurrentDictionary<string, PluginRegistration>();
281+
#else
278282
private readonly Dictionary<string, PluginRegistration> _plugins = new Dictionary<string, PluginRegistration>();
283+
#endif
279284

280285
/// <summary>
281286
/// The list of plugins that will be used in this configuration.
282287
/// </summary>
283288
public IEnumerable<PluginRegistration> Plugins {
284-
get { return _plugins.Values.OrderBy(e => e.Priority).ToList(); }
289+
get { return _plugins.Values.ToList().OrderBy(e => e.Priority); }
285290
}
286291

287292
/// <summary>
@@ -292,7 +297,13 @@ public IEnumerable<PluginRegistration> Plugins {
292297
public void AddPlugin<T>(T plugin) where T : IEventPlugin {
293298
string key = typeof(T).FullName;
294299
RemovePlugin(key);
300+
301+
#if !PORTABLE
302+
if (!_plugins.TryAdd(key, new PluginRegistration(key, GetPriority(typeof(T)), new Lazy<IEventPlugin>(() => plugin))))
303+
Resolver.GetLog().Error(String.Format("Unable to add plugin: {0}", key));
304+
#else
295305
_plugins[key] = new PluginRegistration(key, GetPriority(typeof(T)), new Lazy<IEventPlugin>(() => plugin));
306+
#endif
296307
}
297308

298309
/// <summary>
@@ -310,7 +321,14 @@ public void AddPlugin<T>() where T : IEventPlugin {
310321
/// <param name="pluginType">The plugin type to be added.</param>
311322
public void AddPlugin(string key, Type pluginType) {
312323
RemovePlugin(key);
313-
_plugins[key] = new PluginRegistration(key, GetPriority(pluginType), new Lazy<IEventPlugin>(() => Resolver.Resolve(pluginType) as IEventPlugin));
324+
325+
var plugin = new PluginRegistration(key, GetPriority(pluginType), new Lazy<IEventPlugin>(() => Resolver.Resolve(pluginType) as IEventPlugin));
326+
#if !PORTABLE
327+
if (!_plugins.TryAdd(key, plugin))
328+
Resolver.GetLog().Error(String.Format("Unable to add plugin: {0}", key));
329+
#else
330+
_plugins[key] = plugin;
331+
#endif
314332
}
315333

316334
/// <summary>
@@ -330,7 +348,14 @@ public void AddPlugin(string key, Func<ExceptionlessConfiguration, IEventPlugin>
330348
/// <param name="factory">A factory method to create the plugin.</param>
331349
public void AddPlugin(string key, int priority, Func<ExceptionlessConfiguration, IEventPlugin> factory) {
332350
RemovePlugin(key);
333-
_plugins[key] = new PluginRegistration(key, priority, new Lazy<IEventPlugin>(() => factory(this)));
351+
352+
var plugin = new PluginRegistration(key, priority, new Lazy<IEventPlugin>(() => factory(this)));
353+
#if !PORTABLE
354+
if (!_plugins.TryAdd(key, plugin))
355+
Resolver.GetLog().Error(String.Format("Unable to add plugin: {0}", key));
356+
#else
357+
_plugins[key] = plugin;
358+
#endif
334359
}
335360

336361
/// <summary>
@@ -358,7 +383,14 @@ public void AddPlugin(string key, Action<EventPluginContext> pluginAction) {
358383
/// <param name="pluginAction">The plugin action to run.</param>
359384
public void AddPlugin(string key, int priority, Action<EventPluginContext> pluginAction) {
360385
RemovePlugin(key);
361-
_plugins[key] = new PluginRegistration(key, priority, new Lazy<IEventPlugin>(() => new ActionPlugin(pluginAction)));
386+
387+
var plugin = new PluginRegistration(key, priority, new Lazy<IEventPlugin>(() => new ActionPlugin(pluginAction)));
388+
#if !PORTABLE
389+
if (!_plugins.TryAdd(key, plugin))
390+
Resolver.GetLog().Error(String.Format("Unable to add plugin: {0}", key));
391+
#else
392+
_plugins[key] = plugin;
393+
#endif
362394
}
363395

364396
/// <summary>
@@ -374,10 +406,16 @@ public void RemovePlugin<T>() where T : IEventPlugin {
374406
/// </summary>
375407
/// <param name="key">The key for the plugin to be removed.</param>
376408
public void RemovePlugin(string key) {
409+
#if !PORTABLE
410+
PluginRegistration plugin;
411+
if (_plugins.TryRemove(key, out plugin))
412+
plugin.Dispose();
413+
#else
377414
if (_plugins.ContainsKey(key)) {
378415
_plugins[key].Dispose();
379416
_plugins.Remove(key);
380417
}
418+
#endif
381419
}
382420

383421
private int GetPriority(Type type) {

src/Exceptionless/Plugins/EventPluginManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public static class EventPluginManager {
1010
/// </summary>
1111
/// <param name="context">Context information.</param>
1212
public static void Run(EventPluginContext context) {
13-
foreach (IEventPlugin plugin in context.Client.Configuration.Plugins.Select(e => e.Plugin).ToList()) {
13+
foreach (IEventPlugin plugin in context.Client.Configuration.Plugins.Select(e => e.Plugin)) {
1414
try {
1515
plugin.Run(context);
1616
if (context.Cancel) {

0 commit comments

Comments
 (0)