You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider replacing SynchronizedDictionary with ConcurrentDictionary #743
I think the BCL ConcurrentDictionary should be able to perform much better than the current SynchronizedDictionary implementation.
Using Moq, which depends on Castle.Core, I observe threadpool starvation in a parallelized test run. The threadpool threads are mostly in stack traces similar to this one:
Using ConcurrentDictionary might lock less. I guess it creates some buckets per the hash code, and the different buckets get different locks, which allows for more concurrent access without breaking the type's thread safety.
Or, if you have other approaches where you can eliminate the need to lock altogether, that also might be a good option.
I'm open to switch to a standard FCL type, but I think it would be good to have some kind of before/after benchmark showing that ConcurrentDictionary actually does remove a significant bottleneck. Could you provide something along that line? (E.g. a small BenchmarkDotNet project that we can drop into the Castle.Core solution file and run against DynamicProxy both before and after the change?)
See the last comment on #744. Somehow my changes actually broke the thread-safety guarantees currently on master. I don't think I'll get into investigating this deeper, but generally speaking, I think there is some sort of threadpool starvation under parallel loads that needs to be somehow addressed.
This TypeCache.GetOrAdd must be exclusively locking, i. e. only one thread at a time may go ahead and generate its type. Almost all of DynamicProxy's code generation is kicked off here. Most other code may then silently assume that there is no other concurrent code generation going on, because we guarantee that at the above site. This is why you see those {GetOrAdd,AddOrUpdate}WithoutTakingLock (emphasis on "without taking lock") method calls almost everywhere else.
If I remember correctly (but I could be wrong), ConcurrentDictionary.GetOrAdd has slightly different semantics: several factory lambdas may execute concurrently (at the same time) which, if true, would break DynamicProxy.
Activity
Youssef1313 commentedon May 2, 2026
FYI @kzu as well
stakx commentedon May 2, 2026
Hi @Youssef1313, thanks for looking into it.
I'm open to switch to a standard FCL type, but I think it would be good to have some kind of before/after benchmark showing that
ConcurrentDictionaryactually does remove a significant bottleneck. Could you provide something along that line? (E.g. a small BenchmarkDotNet project that we can drop into the Castle.Core solution file and run against DynamicProxy both before and after the change?)Youssef1313 commentedon May 4, 2026
See the last comment on #744. Somehow my changes actually broke the thread-safety guarantees currently on master. I don't think I'll get into investigating this deeper, but generally speaking, I think there is some sort of threadpool starvation under parallel loads that needs to be somehow addressed.
stakx commentedon May 4, 2026
@Youssef1313, when it comes to DynamicProxy thread safety, this here is quite possibly the most important code location:
Core/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs
Lines 77 to 86 in 9631074
This
TypeCache.GetOrAddmust be exclusively locking, i. e. only one thread at a time may go ahead and generate its type. Almost all of DynamicProxy's code generation is kicked off here. Most other code may then silently assume that there is no other concurrent code generation going on, because we guarantee that at the above site. This is why you see those{GetOrAdd,AddOrUpdate}WithoutTakingLock(emphasis on "without taking lock") method calls almost everywhere else.If I remember correctly (but I could be wrong),
ConcurrentDictionary.GetOrAddhas slightly different semantics: several factory lambdas may execute concurrently (at the same time) which, if true, would break DynamicProxy.