internal static void Release(ReuseableStringBuilder returning) { #if DEBUG && ASSERT_BALANCE // Please define ASSERT_BALANCE if you need to analyze where we have cross thread competing usage of ReuseableStringBuilder int balance = Interlocked.Decrement(ref s_getVsReleaseBalance); Debug.Assert(balance == 0, "Unbalanced Get vs Release. Either forgotten Release or used from multiple threads concurrently."); #endif FrameworkErrorUtilities.VerifyThrowInternalNull(returning._borrowedBuilder, nameof(returning._borrowedBuilder)); StringBuilder returningBuilder = returning._borrowedBuilder !; int returningLength = returningBuilder.Length; // It's possible for someone to cause the builder to // enlarge to such an extent that this static field // would be a leak. To avoid that, only accept // the builder if it's no more than a certain size. // // If some code has a bug and forgets to return their builder // (or we refuse it here because it's too big) the next user will // get given a new one, and then return it soon after. // So the shared builder will be "replaced". if (returningBuilder.Capacity > MaxBuilderSizeCapacity) { // In order to free memory usage by huge string builder, do not pool this one and let it be collected. #if DEBUG MSBuildEventSource.Log.ReusableStringBuilderFactoryStop(hash: returningBuilder.GetHashCode(), returningCapacity: returningBuilder.Capacity, returningLength: returningLength, type: "discard"); #endif } else { if (returningBuilder.Capacity != returning._borrowedWithCapacity) { Debug.Assert(returningBuilder.Capacity > returning._borrowedWithCapacity, "Capacity can only increase"); // This builder used more than pre-allocated capacity bracket. // Let this builder be collected and put new builder, with reflecting bracket capacity, into the pool. // If we would just return this builder into pool as is, it would allocated new array[capacity] anyway (current implementation of returningBuilder.Clear() does it) // and that could lead to unpredictable amount of LOH allocations and eventual LOH fragmentation. // Below implementation has predictable max Log2(MaxBuilderSizeBytes) string builder array re-allocations during whole process lifetime - unless MaxBuilderSizeCapacity is reached frequently. int newCapacity = SelectBracketedCapacity(returningBuilder.Capacity); returningBuilder = new StringBuilder(newCapacity); } returningBuilder.Clear(); // Clear before pooling var oldSharedBuilder = Interlocked.Exchange(ref s_sharedBuilder, returningBuilder); if (oldSharedBuilder != null) { // This can identify improper usage from multiple thread or bug in code - Get was reentered before Release. // User of ReuseableStringBuilder has to make sure that calling method call stacks do not also use ReuseableStringBuilder. // Look at stack traces of ETW events which contains reported string builder hashes. MSBuildEventSource.Log.ReusableStringBuilderFactoryUnbalanced(oldHash: oldSharedBuilder.GetHashCode(), newHash: returningBuilder.GetHashCode()); } #if DEBUG MSBuildEventSource.Log.ReusableStringBuilderFactoryStop(hash: returningBuilder.GetHashCode(), returningCapacity: returningBuilder.Capacity, returningLength: returningLength, type: returning._borrowedBuilder != returningBuilder ? "return-new" : "return"); #endif } // Ensure ReuseableStringBuilder can no longer use _borrowedBuilder returning._borrowedBuilder = null; }
private void LazyPrepare() { if (_borrowedBuilder == null) { FrameworkErrorUtilities.VerifyThrow(_capacity != -1, "Reusing after dispose"); _borrowedBuilder = ReuseableStringBuilderFactory.Get(_capacity); _borrowedWithCapacity = _borrowedBuilder.Capacity; } }