Skip to content

Commit 5757104

Browse files
committed
MultiValueDictionary internal HashSet is not threadsafe #354
1 parent 8838df6 commit 5757104

3 files changed

Lines changed: 111 additions & 13 deletions

File tree

Xpand/Xpand.Persistent/Xpand.Persistent.Base/General/SequenceGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ public void Attach(XpandModuleBase xpandModuleBase) {
474474

475475
void ApplicationOnLoggedOff(object sender, EventArgs eventArgs) {
476476
((XafApplication)sender).LoggedOff -= ApplicationOnLoggedOff;
477-
XpandModuleBase.CallMonitor.Remove(new KeyValuePair<string, ApplicationModulesManager>(SequenceGeneratorHelperName, _xpandModuleBase.ModuleManager));
477+
XpandModuleBase.CallMonitor.TryRemove(new KeyValuePair<string, ApplicationModulesManager>(SequenceGeneratorHelperName, _xpandModuleBase.ModuleManager),out _);
478478
}
479479
}
480480
}

Xpand/Xpand.Persistent/Xpand.Persistent.Base/General/XpandModuleBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -848,8 +848,8 @@ public void UpdateNode(IModelMemberEx node, IModelApplication application) {
848848
node.ClearValue(ex => ex.IsCalculated);
849849
}
850850

851-
public static void RemoveCall(string name, ApplicationModulesManager applicationModulesManager){
852-
CallMonitor?.Remove(new KeyValuePair<string, ApplicationModulesManager>(name, applicationModulesManager));
851+
public static void RemoveCall(string name, ApplicationModulesManager applicationModulesManager) {
852+
CallMonitor?.TryRemove(new KeyValuePair<string, ApplicationModulesManager>(name, applicationModulesManager),out _);
853853
}
854854
}
855855

Xpand/Xpand.Utils/GeneralDataStructures/MultiValueDictionary.cs

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,110 @@
1-
using System.Collections;
1+
using System;
2+
using System.Collections;
23
using System.Collections.Concurrent;
34
using System.Collections.Generic;
45
using System.Linq;
6+
using System.Threading;
57

68
namespace Xpand.Utils.GeneralDataStructures {
7-
public class MultiValueDictionary<TKey, TValue> : ConcurrentDictionary<TKey, HashSet<TValue>>, ILookup<TKey, TValue> {
9+
public class ConcurrentHashSet<T> : IDisposable, IEnumerable<T> {
10+
private readonly HashSet<T> _hashSet = new HashSet<T>();
11+
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
12+
13+
public int Count {
14+
get {
15+
_lock.EnterReadLock();
16+
17+
try {
18+
return _hashSet.Count;
19+
}
20+
finally {
21+
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
22+
}
23+
}
24+
}
25+
26+
public void Dispose() {
27+
Dispose(true);
28+
GC.SuppressFinalize(this);
29+
}
30+
31+
public IEnumerator<T> GetEnumerator() {
32+
_lock.EnterWriteLock();
33+
34+
try {
35+
return _hashSet.GetEnumerator();
36+
}
37+
finally {
38+
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
39+
}
40+
}
41+
42+
IEnumerator IEnumerable.GetEnumerator() {
43+
return GetEnumerator();
44+
}
45+
46+
public bool TryAdd(T item) {
47+
_lock.EnterWriteLock();
48+
49+
try {
50+
return _hashSet.Add(item);
51+
}
52+
finally {
53+
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
54+
}
55+
}
56+
57+
public void Clear() {
58+
_lock.EnterWriteLock();
59+
60+
try {
61+
_hashSet.Clear();
62+
}
63+
finally {
64+
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
65+
}
66+
}
67+
68+
public bool Contains(T item) {
69+
_lock.EnterReadLock();
70+
71+
try {
72+
return _hashSet.Contains(item);
73+
}
74+
finally {
75+
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
76+
}
77+
}
78+
79+
public bool TryRemove(T item) {
80+
_lock.EnterWriteLock();
81+
82+
try {
83+
return _hashSet.Remove(item);
84+
}
85+
finally {
86+
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
87+
}
88+
}
89+
90+
public T FirstOrDefault(Func<T, bool> predicate) {
91+
_lock.EnterReadLock();
92+
93+
try {
94+
return _hashSet.FirstOrDefault(predicate);
95+
}
96+
finally {
97+
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
98+
}
99+
}
100+
101+
protected virtual void Dispose(bool disposing) {
102+
if (disposing) _lock?.Dispose();
103+
}
104+
}
105+
106+
public class
107+
MultiValueDictionary<TKey, TValue> : ConcurrentDictionary<TKey, ConcurrentHashSet<TValue>>, ILookup<TKey, TValue> {
8108
bool ILookup<TKey, TValue>.Contains(TKey key) {
9109
return ContainsKey(key);
10110
}
@@ -23,11 +123,11 @@ IEnumerator IEnumerable.GetEnumerator() {
23123

24124
public void Add(TKey key, TValue value) {
25125
if (!TryGetValue(key, out var container)) {
26-
container = new HashSet<TValue>();
126+
container = new ConcurrentHashSet<TValue>();
27127
TryAdd(key, container);
28128
}
29129

30-
container.Add(value);
130+
container.TryAdd(value);
31131
}
32132

33133
public void AddRange(TKey key, IEnumerable<TValue> values) {
@@ -44,10 +144,8 @@ public bool ContainsValue(TKey key, TValue value) {
44144

45145
public void Remove(TKey key, TValue value) {
46146
if (TryGetValue(key, out var container)) {
47-
container.Remove(value);
48-
if (container.Count <= 0) {
49-
TryRemove(key, out container);
50-
}
147+
container.TryRemove(value);
148+
if (container.Count <= 0) TryRemove(key, out container);
51149
}
52150
}
53151

@@ -59,8 +157,8 @@ public void Merge(MultiValueDictionary<TKey, TValue> toMergeWith) {
59157
Add(pair.Key, value);
60158
}
61159

62-
public HashSet<TValue> GetValues(TKey key, bool returnEmptySet) {
63-
if (!TryGetValue(key, out var toReturn) && returnEmptySet) toReturn = new HashSet<TValue>();
160+
public ConcurrentHashSet<TValue> GetValues(TKey key, bool returnEmptySet) {
161+
if (!TryGetValue(key, out var toReturn) && returnEmptySet) toReturn = new ConcurrentHashSet<TValue>();
64162
return toReturn;
65163
}
66164
}

0 commit comments

Comments
 (0)