Fix perf regression with lots of objects in a ConditionalWeakTable
authorStephen Toub <stoub@microsoft.com>
Wed, 7 Dec 2016 21:35:03 +0000 (16:35 -0500)
committerStephen Toub <stoub@microsoft.com>
Fri, 9 Dec 2016 03:30:58 +0000 (22:30 -0500)
commit5548429858e7d248f08f77b343a2ad60133cb4c0
treebbc13ff9f3e591fbad2b71340b037dffc9a890eb
parent854532c4136a86c06af184dc1234bbcc0df4411a
Fix perf regression with lots of objects in a ConditionalWeakTable

The CoreRT implementation of ConditionalWeakTable that was ported back to CoreCLR uses a special scheme to make reads lock-free.  When the container needs to grow, it allocates new arrays and duplicates all of the dependency handles from the previous array, rather than just copying them.  This avoids issues stemming from a thread getting a dependency handle in an operation on the container, then having that handle destroyed, and then trying to use it; the handle won't be destroyed as long as the container is referenced.

However, this also leads to a significant cost in a certain situation.  Every time the container grows, it allocates another N dependency handles where N is the current size of the container.  So, for example, with an initial size of 8, if 64 objects are added to the container, it'll allocate 8 dependency handles, then another 16, then another 32, and then another 64, resulting in significantly more handles than in the old implementation, which would only allocate 64 handles total.

This commit fixes that by changing the scheme slightly.  A container still frees its handles in its finalizer.  However, rather than duplicating all handles, that responsibility for freeing is transferred from one container to the next.  Then to avoid issues where, for example, the second container is released while the first is still in use, a reference is maintained from the first to the second, so that the second can't be finalized while the first is still in use.

The commit also fixes a race condition with resurrection and finalization, whereby dependency handles could be used while or after they're being freed by the finalizer. It's addressed by only freeing handles in a second finalization after clearing out state in the first finalization to guarantee no possible usage during the second.

Commit migrated from https://github.com/dotnet/coreclr/commit/03525a46e5d9b33e13a992d89dda3fbf36f43888
src/coreclr/src/mscorlib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs