Skip to content

Commit f8a40c5

Browse files
committed
Fixed #125 Ensure settings are thread safe
1 parent d3fe4f4 commit f8a40c5

File tree

4 files changed

+156
-16
lines changed

4 files changed

+156
-16
lines changed

samples/Exceptionless.SampleConsole/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Diagnostics;
33
using System.IO;
4-
using System.Reflection;
54
using System.Threading;
65
using System.Threading.Tasks;
76
using Exceptionless.Configuration;

src/Exceptionless/Models/Collections/ObservableDictionary.cs

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,90 @@
11
using System;
22
using System.Collections;
3+
#if !PORTABLE
4+
using System.Collections.Concurrent;
5+
#endif
36
using System.Collections.Generic;
47

58
namespace Exceptionless.Models.Collections {
69
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
10+
#if !PORTABLE
11+
private readonly ConcurrentDictionary<TKey, TValue> _dictionary;
12+
#else
713
private readonly IDictionary<TKey, TValue> _dictionary;
14+
#endif
815

916
public ObservableDictionary() {
17+
#if !PORTABLE
18+
_dictionary = new ConcurrentDictionary<TKey, TValue>();
19+
#else
1020
_dictionary = new Dictionary<TKey, TValue>();
21+
#endif
1122
}
1223

1324
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
25+
#if !PORTABLE
26+
_dictionary = new ConcurrentDictionary<TKey, TValue>(dictionary);
27+
#else
1428
_dictionary = new Dictionary<TKey, TValue>(dictionary);
29+
#endif
1530
}
1631

1732
public ObservableDictionary(IEqualityComparer<TKey> comparer) {
33+
#if !PORTABLE
34+
_dictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
35+
#else
1836
_dictionary = new Dictionary<TKey, TValue>(comparer);
37+
#endif
1938
}
2039

2140
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
41+
#if !PORTABLE
42+
_dictionary = new ConcurrentDictionary<TKey, TValue>(dictionary, comparer);
43+
#else
2244
_dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
45+
#endif
2346
}
2447

2548
public void Add(TKey key, TValue value) {
49+
#if !PORTABLE
50+
if (_dictionary.TryAdd(key, value))
51+
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(new KeyValuePair<TKey, TValue>(key, value), ChangedAction.Add));
52+
#else
2653
_dictionary.Add(key, value);
27-
2854
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(new KeyValuePair<TKey, TValue>(key, value), ChangedAction.Add));
55+
#endif
2956
}
3057

3158
public void Add(KeyValuePair<TKey, TValue> item) {
59+
#if !PORTABLE
60+
if (_dictionary.TryAdd(item.Key, item.Value))
61+
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(item, ChangedAction.Add));
62+
#else
3263
_dictionary.Add(item);
33-
3464
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(item, ChangedAction.Add));
65+
#endif
3566
}
3667

3768
public bool Remove(TKey key) {
69+
TValue value = default(TValue);
70+
#if !PORTABLE
71+
bool success = _dictionary.TryRemove(key, out value);
72+
#else
3873
bool success = _dictionary.Remove(key);
39-
74+
#endif
4075
if (success)
41-
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(new KeyValuePair<TKey, TValue>(key, default(TValue)), ChangedAction.Remove));
76+
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(new KeyValuePair<TKey, TValue>(key, value), ChangedAction.Remove));
4277

4378
return success;
4479
}
4580

4681
public bool Remove(KeyValuePair<TKey, TValue> item) {
82+
#if !PORTABLE
83+
TValue value;
84+
bool success = _dictionary.TryRemove(item.Key, out value);
85+
#else
4786
bool success = _dictionary.Remove(item);
48-
87+
#endif
4988
if (success)
5089
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(item, ChangedAction.Remove));
5190

@@ -54,7 +93,6 @@ public bool Remove(KeyValuePair<TKey, TValue> item) {
5493

5594
public void Clear() {
5695
_dictionary.Clear();
57-
5896
OnChanged(new ChangedEventArgs<KeyValuePair<TKey, TValue>>(new KeyValuePair<TKey, TValue>(), ChangedAction.Clear));
5997
}
6098

@@ -63,15 +101,19 @@ public bool ContainsKey(TKey key) {
63101
}
64102

65103
public bool Contains(KeyValuePair<TKey, TValue> item) {
104+
#if !PORTABLE
105+
return _dictionary.ContainsKey(item.Key);
106+
#else
66107
return _dictionary.Contains(item);
108+
#endif
67109
}
68110

69111
public bool TryGetValue(TKey key, out TValue value) {
70112
return _dictionary.TryGetValue(key, out value);
71113
}
72114

73115
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
74-
_dictionary.CopyTo(array, arrayIndex);
116+
((IDictionary)_dictionary).CopyTo(array, arrayIndex);
75117
}
76118

77119
public ICollection<TKey> Keys {
@@ -87,7 +129,7 @@ public int Count {
87129
}
88130

89131
public bool IsReadOnly {
90-
get { return _dictionary.IsReadOnly; }
132+
get { return ((IDictionary)_dictionary).IsReadOnly; }
91133
}
92134

93135
public TValue this[TKey key] {
@@ -132,4 +174,4 @@ public enum ChangedAction {
132174
Clear,
133175
Update
134176
}
135-
}
177+
}

src/Exceptionless/Models/Collections/SettingsDictionary.cs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System;
2+
#if !PORTABLE
3+
using System.Collections.Concurrent;
4+
#endif
25
using System.Collections.Generic;
36
using System.Linq;
47
using Exceptionless.Extensions;
@@ -161,12 +164,22 @@ protected override void OnChanged(ChangedEventArgs<KeyValuePair<string, string>>
161164
logLevelKeysToRemove.Add(key);
162165
}
163166

164-
foreach (var logger in logLevelKeysToRemove)
167+
foreach (var logger in logLevelKeysToRemove) {
168+
#if !PORTABLE
169+
LogLevel value;
170+
_minLogLevels.TryRemove(logger, out value);
171+
#else
165172
_minLogLevels.Remove(logger);
166-
}
173+
#endif
174+
}
175+
}
167176

168177
foreach (var eventType in _eventTypes) {
178+
#if !PORTABLE
179+
ConcurrentDictionary<string, bool> sourceDictionary;
180+
#else
169181
Dictionary<string, bool> sourceDictionary;
182+
#endif
170183
if (eventType.Key == null || !_typeSourceEnabled.TryGetValue(eventType.Key, out sourceDictionary))
171184
continue;
172185

@@ -179,14 +192,24 @@ protected override void OnChanged(ChangedEventArgs<KeyValuePair<string, string>>
179192
sourceKeysToRemove.Add(key);
180193
}
181194

182-
foreach (var logger in sourceKeysToRemove)
195+
foreach (var logger in sourceKeysToRemove) {
196+
#if !PORTABLE
197+
bool value;
198+
sourceDictionary.TryRemove(logger, out value);
199+
#else
183200
sourceDictionary.Remove(logger);
201+
#endif
202+
}
184203
}
185204

186205
base.OnChanged(args);
187206
}
188207

208+
#if !PORTABLE
209+
private readonly ConcurrentDictionary<string, LogLevel> _minLogLevels = new ConcurrentDictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase);
210+
#else
189211
private readonly Dictionary<string, LogLevel> _minLogLevels = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase);
212+
#endif
190213
public LogLevel GetMinLogLevel(string loggerName) {
191214
if (String.IsNullOrEmpty(loggerName))
192215
loggerName = "*";
@@ -197,44 +220,77 @@ public LogLevel GetMinLogLevel(string loggerName) {
197220

198221
var setting = GetTypeAndSourceSetting("log", loggerName, "Trace");
199222
if (setting == null) {
223+
#if !PORTABLE
224+
_minLogLevels.AddOrUpdate(loggerName, LogLevel.Trace, (logName, level) => LogLevel.Trace);
225+
#else
200226
_minLogLevels[loggerName] = LogLevel.Trace;
227+
#endif
201228
return LogLevel.Trace;
202229
}
203230

204231
minLogLevel = LogLevel.FromString(setting);
232+
#if !PORTABLE
233+
_minLogLevels.AddOrUpdate(loggerName, minLogLevel, (logName, level) => minLogLevel);
234+
#else
205235
_minLogLevels[loggerName] = minLogLevel;
236+
#endif
206237
return minLogLevel;
207238
}
208239

240+
#if !PORTABLE
241+
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, bool>> _typeSourceEnabled = new ConcurrentDictionary<string, ConcurrentDictionary<string, bool>>(StringComparer.OrdinalIgnoreCase);
242+
#else
209243
private readonly Dictionary<string, Dictionary<string, bool>> _typeSourceEnabled = new Dictionary<string, Dictionary<string, bool>>(StringComparer.OrdinalIgnoreCase);
244+
#endif
210245
public bool GetTypeAndSourceEnabled(string type, string source) {
211246
if (type == null)
212247
return true;
213248

249+
#if !PORTABLE
250+
ConcurrentDictionary<string, bool> sourceDictionary;
251+
#else
214252
Dictionary<string, bool> sourceDictionary;
215-
bool sourceEnabled;
253+
#endif
216254
if (source != null && _typeSourceEnabled.TryGetValue(type, out sourceDictionary)) {
255+
bool sourceEnabled;
217256
if (sourceDictionary.TryGetValue(source, out sourceEnabled))
218257
return sourceEnabled;
219258
}
220259

221260
return GetTypeAndSourceSetting(type, source, "true").ToBoolean(true);
222261
}
223262

263+
#if !PORTABLE
264+
private readonly ConcurrentDictionary<string, string> _eventTypes = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
265+
#else
224266
private readonly Dictionary<string, string> _eventTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
267+
#endif
225268
private string GetTypeAndSourceSetting(string type, string source, string defaultValue) {
226269
if (type == null)
227270
return defaultValue;
228271

272+
#if !PORTABLE
273+
ConcurrentDictionary<string, bool> sourceDictionary;
274+
#else
229275
Dictionary<string, bool> sourceDictionary;
276+
#endif
230277
string sourcePrefix;
231278
if (!_typeSourceEnabled.TryGetValue(type, out sourceDictionary)) {
279+
#if !PORTABLE
280+
sourceDictionary = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
281+
_typeSourceEnabled.TryAdd(type, sourceDictionary);
282+
#else
232283
sourceDictionary = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
233284
_typeSourceEnabled.Add(type, sourceDictionary);
234-
285+
#endif
235286
sourcePrefix = "@@" + type + ":";
236-
if (!_eventTypes.ContainsKey(type))
287+
if (!_eventTypes.ContainsKey(type)) {
288+
#if !PORTABLE
289+
_eventTypes.TryAdd(type, sourcePrefix);
290+
#else
237291
_eventTypes.Add(type, sourcePrefix);
292+
#endif
293+
}
238294
} else {
239295
sourcePrefix = _eventTypes[type];
240296
}

tests/Exceptionless.Tests/Configuration/ConfigurationTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Reflection;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57
using Exceptionless.Configuration;
68
using Exceptionless.Dependency;
79
using Exceptionless.Models;
@@ -10,11 +12,17 @@
1012
using Exceptionless.Tests.Utility;
1113
using Moq;
1214
using Xunit;
15+
using Xunit.Abstractions;
1316

1417
[assembly: Exceptionless("LhhP1C9gijpSKCslHHCvwdSIz298twx271n1l6xw", ServerUrl = "http://localhost:45000")]
1518
[assembly: ExceptionlessSetting("testing", "configuration")]
1619
namespace Exceptionless.Tests.Configuration {
1720
public class ConfigurationTests {
21+
private readonly TestOutputWriter _writer;
22+
public ConfigurationTests(ITestOutputHelper output) {
23+
_writer = new TestOutputWriter(output);
24+
}
25+
1826
[Fact]
1927
public void CanConfigureApiKeyFromClientConstructor() {
2028
var client = new ExceptionlessClient("LhhP1C9gijpSKCslHHCvwdSIz298twx271n1l6xw");
@@ -98,5 +106,40 @@ public void CanUpdateSettingsFromServer() {
98106
Assert.Equal("2", client.Configuration.Settings["LocalSettingToOverride"]);
99107
Assert.Equal(2, client.Configuration.Settings.Count);
100108
}
109+
110+
[Fact]
111+
public void CanGetSettingsMultithreaded() {
112+
var settings = new SettingsDictionary();
113+
var result = Parallel.For(0, 20, index => {
114+
for (int i = 0; i < 10; i++) {
115+
string key = $"setting-{i}";
116+
if (!settings.ContainsKey(key))
117+
settings.Add(key, (index * i).ToString());
118+
else
119+
settings[key] = (index * i).ToString();
120+
}
121+
});
122+
123+
while (!result.IsCompleted)
124+
Thread.Sleep(1);
125+
}
126+
127+
[Fact]
128+
public void CanGetLogSettingsMultithreaded() {
129+
var settings = new SettingsDictionary();
130+
settings.Add("@@log:*", "Info");
131+
settings.Add("@@log:Source1", "Trace");
132+
settings.Add("@@log:Source2", "Debug");
133+
settings.Add("@@log:Source3", "Info");
134+
settings.Add("@@log:Source4", "Info");
135+
136+
var result = Parallel.For(0, 100, index => {
137+
var level = settings.GetMinLogLevel("Source1");
138+
_writer.WriteLine("Source1 log level: {0}", level);
139+
});
140+
141+
while (!result.IsCompleted)
142+
Thread.Sleep(1);
143+
}
101144
}
102145
}

0 commit comments

Comments
 (0)