summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>2018-04-10 22:25:49 -0700
committerGitHub <noreply@github.com>2018-04-10 22:25:49 -0700
commita2508f048ccd41d5893275697b73fef39c2497f3 (patch)
tree4005ba40dccb750b03bea8decc56d4c3ef21c9b9 /src
parent76620c6dd5b340b2ac2c4e01b855b93ce18bf28a (diff)
downloadcoreclr-a2508f048ccd41d5893275697b73fef39c2497f3.tar.gz
coreclr-a2508f048ccd41d5893275697b73fef39c2497f3.tar.bz2
coreclr-a2508f048ccd41d5893275697b73fef39c2497f3.zip
Simple trim of ArrayPool buffers (#17078)
* Simple trim of ArrayPool buffers Trim ArrayPool buffers on Gen2 GC if the buffer stack hasn't been emptied for awhile. If you haven't pulled all of the buffers in the past 10 seconds, let loose the top buffer on the stack and give the stack another 2 seconds of potential life. When the stack gets it's bottom bufferr returned the clock resets. * Collect thread locals as well * Add event * Incorporate memory pressure into trimming. Idea is that we normally give buckets a minute of age time unless the pressure starts to ramp up. As it ramps up we'll trim more off the stacks. If it gets really high we'll consider stale to be 10s instead of 1 min. * Add implementation back for PinnableBufferCacheEventSource. * Remove security attribute. Fix GetMemoryInfo signature * Always use Tls* for shared pools Add environment variable switch * Add guid to PinnableBufferCacheEventSource * Address feedback - move setting code to CLRConfig - add constructor to PBCES - trim large arrays more aggressively - tweak names (ticks to ms, etc.) - interlock creating the cleanup callback - fix project file Rent/return perf numbers are unchanged * Remove static constructor Inline Unsafe.SizeOf * Fix spacing issue * Trim all thread locals when memory pressure is high. Move constants inline. * Undo formatting changes * Add back the internal call * Put the right bits back *sigh* * Missing the line feed * Add event for trim polling * Undo PinnableBufferCacheEventSource reimplementation
Diffstat (limited to 'src')
-rw-r--r--src/mscorlib/System.Private.CoreLib.csproj12
-rw-r--r--src/mscorlib/shared/System.Private.CoreLib.Shared.projitems2
-rw-r--r--src/mscorlib/shared/System/Buffers/ArrayPool.cs4
-rw-r--r--src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs12
-rw-r--r--src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs201
-rw-r--r--src/mscorlib/shared/System/Gen2GcCallback.cs76
-rw-r--r--src/mscorlib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs (renamed from src/mscorlib/src/System/Runtime/Reliability/CriticalFinalizerObject.cs)0
-rw-r--r--src/mscorlib/src/System/CLRConfig.cs41
-rw-r--r--src/mscorlib/src/System/PinnableBufferCache.cs (renamed from src/mscorlib/src/System/Threading/PinnableBufferCache.cs)99
-rw-r--r--src/mscorlib/src/System/PinnableBufferCacheEventSource.cs36
10 files changed, 362 insertions, 121 deletions
diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj
index 9e36d51052..3a0c6b6265 100644
--- a/src/mscorlib/System.Private.CoreLib.csproj
+++ b/src/mscorlib/System.Private.CoreLib.csproj
@@ -127,9 +127,6 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\TaskAwaiter.cs" />
</ItemGroup>
<ItemGroup>
- <Compile Include="$(BclSourcesRoot)\System\Runtime\Reliability\CriticalFinalizerObject.cs" />
- </ItemGroup>
- <ItemGroup>
<Compile Include="$(BclSourcesRoot)\System\Runtime\MemoryFailPoint.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\GcSettings.cs" />
</ItemGroup>
@@ -353,6 +350,7 @@
<Compile Include="$(BclSourcesRoot)\System\MissingFieldException.cs" />
<Compile Include="$(BclSourcesRoot)\System\MissingMemberException.cs" />
<Compile Include="$(BclSourcesRoot)\System\Number.CoreCLR.cs" />
+ <Compile Include="$(BclSourcesRoot)\System\PinnableBufferCache.cs" />
<Compile Include="$(BclSourcesRoot)\System\RtType.cs" />
<Compile Include="$(BclSourcesRoot)\System\RuntimeArgumentHandle.cs" />
<Compile Include="$(BclSourcesRoot)\System\RuntimeHandles.cs" />
@@ -473,7 +471,6 @@
<Compile Include="$(BclSourcesRoot)\System\Threading\Monitor.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Mutex.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Overlapped.cs" />
- <Compile Include="$(BclSourcesRoot)\System\Threading\PinnableBufferCache.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Semaphore.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\Thread.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ThreadInterruptedException.cs" />
@@ -631,6 +628,7 @@
<Compile Include="$(BclSourcesRoot)\mscorlib.Friends.cs" Condition="'$(FeatureCominterop)' == 'true'" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="src\System\PinnableBufferCacheEventSource.cs" />
<Compile Include="src\System\Runtime\RuntimeImports.cs" />
</ItemGroup>
<Import Project="shared\System.Private.CoreLib.Shared.projitems" />
@@ -666,14 +664,10 @@
<!-- Use a different nativeresource file to avoid conflicts with mscorlib-->
<Win32Resource Condition="'$(GenerateNativeVersionInfo)'=='true'">$(IntermediateOutputPath)\System.Private.CoreLib.res</Win32Resource>
</PropertyGroup>
-
<Import Project="CreateRuntimeRootILLinkDescriptorFile.targets" />
-
<ItemGroup>
<EmbeddedResource Include="$(_ILLinkRuntimeRootDescriptorFilePath)" />
</ItemGroup>
-
<Import Project="ILLink.targets" />
-
<Import Project="GenerateCompilerResponseFile.targets" />
-</Project>
+</Project> \ No newline at end of file
diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
index 542c91e2f6..20990e5f1f 100644
--- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
@@ -131,6 +131,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\FlagsAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\FormatException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\FormattableString.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Gen2GcCallback.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\BidiCategory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\Calendar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarAlgorithmType.cs" />
@@ -425,6 +426,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\YieldAwaitable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\Cer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\Consistency.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\CriticalFinalizerObject.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ConstrainedExecution\ReliabilityContractAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ExceptionServices\ExceptionNotification.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ExceptionServices\HandleProcessCorruptedStateExceptionsAttribute.cs" />
diff --git a/src/mscorlib/shared/System/Buffers/ArrayPool.cs b/src/mscorlib/shared/System/Buffers/ArrayPool.cs
index 77a07f7fa5..22ad7821f0 100644
--- a/src/mscorlib/shared/System/Buffers/ArrayPool.cs
+++ b/src/mscorlib/shared/System/Buffers/ArrayPool.cs
@@ -33,9 +33,7 @@ namespace System.Buffers
/// optimized for very fast access speeds, at the expense of more memory consumption.
/// The shared pool instance is created lazily on first access.
/// </remarks>
- public static ArrayPool<T> Shared { get; } =
- typeof(T) == typeof(byte) || typeof(T) == typeof(char) ? new TlsOverPerCoreLockedStacksArrayPool<T>() :
- Create();
+ public static ArrayPool<T> Shared { get; } = new TlsOverPerCoreLockedStacksArrayPool<T>();
/// <summary>
/// Creates a new <see cref="ArrayPool{T}"/> instance using default configuration options.
diff --git a/src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs b/src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs
index b2d0dbd32d..0e6d64ed47 100644
--- a/src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs
+++ b/src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs
@@ -86,5 +86,17 @@ namespace System.Buffers
/// </summary>
[Event(3, Level = EventLevel.Verbose)]
internal void BufferReturned(int bufferId, int bufferSize, int poolId) => WriteEvent(3, bufferId, bufferSize, poolId);
+
+ /// <summary>
+ /// Event raised when we free a buffer due to inactivity or memory pressure.
+ /// </summary>
+ [Event(4, Level = EventLevel.Informational)]
+ internal void BufferTrimmed(int bufferId, int bufferSize, int poolId) => WriteEvent(4, bufferId, bufferSize, poolId);
+
+ /// <summary>
+ /// Event raised when we check to trim buffers.
+ /// </summary>
+ [Event(5, Level = EventLevel.Informational)]
+ internal void BufferTrimPoll(int milliseconds, int pressure) => WriteEvent(5, milliseconds, pressure);
}
}
diff --git a/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
index f6affca0db..cac38bf686 100644
--- a/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
+++ b/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
-
using Internal.Runtime.Augments;
+using Internal.Runtime.CompilerServices;
namespace System.Buffers
{
@@ -24,7 +25,6 @@ namespace System.Buffers
{
// TODO: #7747: "Investigate optimizing ArrayPool heuristics"
// - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue.
- // - Explore dumping stale buffers from the global queue, similar to PinnableBufferCache (maybe merging them).
// - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size.
// - Explore changing number of buckets and what sizes of arrays are cached.
// - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array.
@@ -48,6 +48,15 @@ namespace System.Buffers
[ThreadStatic]
private static T[][] t_tlsBuckets;
+ private int _callbackCreated;
+
+ private readonly static bool s_TrimBuffers = GetTrimBuffers();
+
+ /// <summary>
+ /// Used to keep track of all thread local buckets for trimming if needed
+ /// </summary>
+ private static readonly ConditionalWeakTable<T[][], object> s_AllTlsBuckets = s_TrimBuffers ? new ConditionalWeakTable<T[][], object>() : null;
+
/// <summary>Initialize the pool.</summary>
public TlsOverPerCoreLockedStacksArrayPool()
{
@@ -182,15 +191,24 @@ namespace System.Buffers
{
t_tlsBuckets = tlsBuckets = new T[NumBuckets][];
tlsBuckets[bucketIndex] = array;
+ if (s_TrimBuffers)
+ {
+ s_AllTlsBuckets.Add(tlsBuckets, null);
+ if (Interlocked.Exchange(ref _callbackCreated, 1) != 1)
+ {
+ Gen2GcCallback.Register(Gen2GcCallbackFunc, this);
+ }
+ }
}
else
{
T[] prev = tlsBuckets[bucketIndex];
tlsBuckets[bucketIndex] = array;
+
if (prev != null)
{
- PerCoreLockedStacks bucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex);
- bucket.TryPush(prev);
+ PerCoreLockedStacks stackBucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex);
+ stackBucket.TryPush(prev);
}
}
}
@@ -203,6 +221,92 @@ namespace System.Buffers
}
}
+ public bool Trim()
+ {
+ int milliseconds = Environment.TickCount;
+ MemoryPressure pressure = GetMemoryPressure();
+
+ ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+ if (log.IsEnabled())
+ log.BufferTrimPoll(milliseconds, (int)pressure);
+
+ foreach (PerCoreLockedStacks bucket in _buckets)
+ {
+ bucket?.Trim((uint)milliseconds, Id, pressure, _bucketArraySizes);
+ }
+
+ if (pressure == MemoryPressure.High)
+ {
+ // Under high pressure, release all thread locals
+ foreach (KeyValuePair<T[][], object> tlsBuckets in s_AllTlsBuckets)
+ {
+ T[][] buckets = tlsBuckets.Key;
+ for (int i = 0; i < NumBuckets; i++)
+ {
+ T[] buffer = buckets[i];
+ buckets[i] = null;
+
+ if (log.IsEnabled() && buffer != null)
+ {
+ log.BufferTrimmed(buffer.GetHashCode(), buffer.Length, Id);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// This is the static function that is called from the gen2 GC callback.
+ /// The input object is the instance we want the callback on.
+ /// </summary>
+ /// <remarks>
+ /// The reason that we make this function static and take the instance as a parameter is that
+ /// we would otherwise root the instance to the Gen2GcCallback object, leaking the instance even when
+ /// the application no longer needs it.
+ /// </remarks>
+ private static bool Gen2GcCallbackFunc(object target)
+ {
+ return ((TlsOverPerCoreLockedStacksArrayPool<T>)(target)).Trim();
+ }
+
+ private enum MemoryPressure
+ {
+ Low,
+ Medium,
+ High
+ }
+
+ private static MemoryPressure GetMemoryPressure()
+ {
+ const double HighPressureThreshold = .90; // Percent of GC memory pressure threshold we consider "high"
+ const double MediumPressureThreshold = .70; // Percent of GC memory pressure threshold we consider "medium"
+
+ GC.GetMemoryInfo(out uint threshold, out _, out uint lastLoad, out _, out _);
+ if (lastLoad >= threshold * HighPressureThreshold)
+ {
+ return MemoryPressure.High;
+ }
+ else if (lastLoad >= threshold * MediumPressureThreshold)
+ {
+ return MemoryPressure.Medium;
+ }
+ return MemoryPressure.Low;
+ }
+
+ private static bool GetTrimBuffers()
+ {
+ // Environment uses ArrayPool, so we have to hit the API directly.
+#if !CORECLR
+ // P/Invokes are different for CoreCLR/RT- for RT we'll not allow
+ // enabling/disabling for now.
+ return true;
+#else
+ return CLRConfig.GetBoolValueWithFallbacks("System.Buffers.TrimBuffers", "DOTNET_SYSTEM_BUFFERS_TRIMBUFFERS", defaultValue: true);
+#endif
+ }
+
/// <summary>
/// Stores a set of stacks of arrays, with one stack per core.
/// </summary>
@@ -254,6 +358,16 @@ namespace System.Buffers
}
return null;
}
+
+ public bool Trim(uint tickCount, int id, MemoryPressure pressure, int[] bucketSizes)
+ {
+ LockedStack[] stacks = _perCoreStacks;
+ for (int i = 0; i < stacks.Length; i++)
+ {
+ stacks[i].Trim(tickCount, id, pressure, bucketSizes[i]);
+ }
+ return true;
+ }
}
/// <summary>Provides a simple stack of arrays, protected by a lock.</summary>
@@ -261,6 +375,7 @@ namespace System.Buffers
{
private readonly T[][] _arrays = new T[MaxBuffersPerArraySizePerCore][];
private int _count;
+ private uint _firstStackItemMS;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T[] array)
@@ -269,6 +384,12 @@ namespace System.Buffers
Monitor.Enter(this);
if (_count < MaxBuffersPerArraySizePerCore)
{
+ if (s_TrimBuffers && _count == 0)
+ {
+ // Stash the time the bottom of the stack was filled
+ _firstStackItemMS = (uint)Environment.TickCount;
+ }
+
_arrays[_count++] = array;
enqueued = true;
}
@@ -289,6 +410,76 @@ namespace System.Buffers
Monitor.Exit(this);
return arr;
}
+
+ public void Trim(uint tickCount, int id, MemoryPressure pressure, int bucketSize)
+ {
+ const uint StackTrimAfterMS = 60 * 1000; // Trim after 60 seconds for low/moderate pressure
+ const uint StackHighTrimAfterMS = 10 * 1000; // Trim after 10 seconds for high pressure
+ const uint StackRefreshMS = StackTrimAfterMS / 4; // Time bump after trimming (1/4 trim time)
+ const int StackLowTrimCount = 1; // Trim one item when pressure is low
+ const int StackMediumTrimCount = 2; // Trim two items when pressure is moderate
+ const int StackHighTrimCount = MaxBuffersPerArraySizePerCore; // Trim all items when pressure is high
+ const int StackLargeBucket = 16384; // If the bucket is larger than this we'll trim an extra when under high pressure
+ const int StackModerateTypeSize = 16; // If T is larger than this we'll trim an extra when under high pressure
+ const int StackLargeTypeSize = 32; // If T is larger than this we'll trim an extra (additional) when under high pressure
+
+ if (_count == 0)
+ return;
+
+ lock (this)
+ {
+ uint trimTicks = pressure == MemoryPressure.High ? StackHighTrimAfterMS : StackTrimAfterMS;
+ if (_count > 0 && _firstStackItemMS > tickCount || (tickCount - _firstStackItemMS) > trimTicks)
+ {
+ // We've wrapped the tick count or elapsed enough time since the
+ // first item went into the stack. Drop the top item so it can
+ // be collected and make the stack look a little newer.
+
+ ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+ int trimCount = StackLowTrimCount;
+ switch (pressure)
+ {
+ case MemoryPressure.High:
+ trimCount = StackHighTrimCount;
+
+ // When pressure is high, aggressively trim larger arrays.
+ if (bucketSize > StackLargeBucket)
+ {
+ trimCount++;
+ }
+ if (Unsafe.SizeOf<T>() > StackModerateTypeSize)
+ {
+ trimCount++;
+ }
+ if (Unsafe.SizeOf<T>() > StackLargeTypeSize)
+ {
+ trimCount++;
+ }
+ break;
+ case MemoryPressure.Medium:
+ trimCount = StackMediumTrimCount;
+ break;
+ }
+
+ while (_count > 0 && trimCount-- > 0)
+ {
+ T[] array = _arrays[--_count];
+ _arrays[_count] = null;
+
+ if (log.IsEnabled())
+ {
+ log.BufferTrimmed(array.GetHashCode(), array.Length, id);
+ }
+ }
+
+ if (_count > 0 && _firstStackItemMS < uint.MaxValue - StackRefreshMS)
+ {
+ // Give the remaining items a bit more time
+ _firstStackItemMS += StackRefreshMS;
+ }
+ }
+ }
+ }
}
}
}
diff --git a/src/mscorlib/shared/System/Gen2GcCallback.cs b/src/mscorlib/shared/System/Gen2GcCallback.cs
new file mode 100644
index 0000000000..904fde72e9
--- /dev/null
+++ b/src/mscorlib/shared/System/Gen2GcCallback.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ /// <summary>
+ /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
+ /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
+ /// </summary>
+ internal sealed class Gen2GcCallback : CriticalFinalizerObject
+ {
+ private Gen2GcCallback()
+ : base()
+ {
+ }
+
+ /// <summary>
+ /// Schedule 'callback' to be called in the next GC. If the callback returns true it is
+ /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
+ ///
+ /// NOTE: This callback will be kept alive until either the callback function returns false,
+ /// or the target object dies.
+ /// </summary>
+ public static void Register(Func<object, bool> callback, object targetObj)
+ {
+ // Create a unreachable object that remembers the callback function and target object.
+ Gen2GcCallback gcCallback = new Gen2GcCallback();
+ gcCallback.Setup(callback, targetObj);
+ }
+
+ private Func<object, bool> _callback;
+ private GCHandle _weakTargetObj;
+
+ private void Setup(Func<object, bool> callback, object targetObj)
+ {
+ _callback = callback;
+ _weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
+ }
+
+ ~Gen2GcCallback()
+ {
+ // Check to see if the target object is still alive.
+ object targetObj = _weakTargetObj.Target;
+ if (targetObj == null)
+ {
+ // The target object is dead, so this callback object is no longer needed.
+ _weakTargetObj.Free();
+ return;
+ }
+
+ // Execute the callback method.
+ try
+ {
+ if (!_callback(targetObj))
+ {
+ // If the callback returns false, this callback object is no longer needed.
+ return;
+ }
+ }
+ catch
+ {
+ // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
+ }
+
+ // Resurrect ourselves by re-registering for finalization.
+ if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())
+ {
+ GC.ReRegisterForFinalize(this);
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/Runtime/Reliability/CriticalFinalizerObject.cs b/src/mscorlib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs
index cbb9562148..cbb9562148 100644
--- a/src/mscorlib/src/System/Runtime/Reliability/CriticalFinalizerObject.cs
+++ b/src/mscorlib/shared/System/Runtime/ConstrainedExecution/CriticalFinalizerObject.cs
diff --git a/src/mscorlib/src/System/CLRConfig.cs b/src/mscorlib/src/System/CLRConfig.cs
index b9f0084c3d..08bf95dc99 100644
--- a/src/mscorlib/src/System/CLRConfig.cs
+++ b/src/mscorlib/src/System/CLRConfig.cs
@@ -3,9 +3,8 @@
// See the LICENSE file in the project root for more information.
using System.Runtime.CompilerServices;
-using System.Runtime.Versioning;
using System.Runtime.InteropServices;
-using System.Security;
+using Microsoft.Win32;
namespace System
{
@@ -20,9 +19,41 @@ namespace System
return GetConfigBoolValue(switchName, out exist);
}
+ internal static bool GetBoolValueWithFallbacks(string switchName, string environmentName, bool defaultValue)
+ {
+ bool value = GetBoolValue(switchName, out bool exists);
+
+ if (exists)
+ return value;
+
+ // Calls to this API can be very early- we want to avoid using higher-level
+ // abstractions where reasonably possible.
+
+ Span<char> buffer = stackalloc char[32];
+
+ int length = Win32Native.GetEnvironmentVariable(environmentName, buffer);
+ switch (length)
+ {
+ case 1:
+ if (buffer[0] == '0')
+ return false;
+ if (buffer[0] == '1')
+ return true;
+ break;
+ case 4:
+ if ("true".AsSpan().EqualsOrdinalIgnoreCase(buffer.Slice(0, 4)))
+ return true;
+ break;
+ case 5:
+ if ("false".AsSpan().EqualsOrdinalIgnoreCase(buffer.Slice(0, 5)))
+ return false;
+ break;
+ }
+
+ return defaultValue;
+ }
+
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern bool GetConfigBoolValue(string configSwitchName, out bool exist);
}
-} // namespace System
-
-// file CLRConfig
+}
diff --git a/src/mscorlib/src/System/Threading/PinnableBufferCache.cs b/src/mscorlib/src/System/PinnableBufferCache.cs
index 48af6e6349..1bfd2cf07b 100644
--- a/src/mscorlib/src/System/Threading/PinnableBufferCache.cs
+++ b/src/mscorlib/src/System/PinnableBufferCache.cs
@@ -442,103 +442,4 @@ namespace System
#endregion
}
-
- /// <summary>
- /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
- /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
- /// </summary>
- internal sealed class Gen2GcCallback : CriticalFinalizerObject
- {
- public Gen2GcCallback()
- : base()
- {
- }
-
- /// <summary>
- /// Schedule 'callback' to be called in the next GC. If the callback returns true it is
- /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
- ///
- /// NOTE: This callback will be kept alive until either the callback function returns false,
- /// or the target object dies.
- /// </summary>
- public static void Register(Func<object, bool> callback, object targetObj)
- {
- // Create a unreachable object that remembers the callback function and target object.
- Gen2GcCallback gcCallback = new Gen2GcCallback();
- gcCallback.Setup(callback, targetObj);
- }
-
- #region Private
-
- private Func<object, bool> m_callback;
- private GCHandle m_weakTargetObj;
-
- private void Setup(Func<object, bool> callback, object targetObj)
- {
- m_callback = callback;
- m_weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
- }
-
- ~Gen2GcCallback()
- {
- // Check to see if the target object is still alive.
- object targetObj = m_weakTargetObj.Target;
- if (targetObj == null)
- {
- // The target object is dead, so this callback object is no longer needed.
- m_weakTargetObj.Free();
- return;
- }
-
- // Execute the callback method.
- try
- {
- if (!m_callback(targetObj))
- {
- // If the callback returns false, this callback object is no longer needed.
- return;
- }
- }
- catch
- {
- // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
- }
-
- // Resurrect ourselves by re-registering for finalization.
- if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())
- {
- GC.ReRegisterForFinalize(this);
- }
- }
-
- #endregion
- }
-
- internal sealed class PinnableBufferCacheEventSource
- {
- public static readonly PinnableBufferCacheEventSource Log = new PinnableBufferCacheEventSource();
-
- public bool IsEnabled() { return false; }
- public void DebugMessage(string message) { }
- public void Create(string cacheName) { }
- public void AllocateBuffer(string cacheName, ulong objectId, int objectHash, int objectGen, int freeCountAfter) { }
- public void AllocateBufferFromNotGen2(string cacheName, int notGen2CountAfter) { }
- public void AllocateBufferCreatingNewBuffers(string cacheName, int totalBuffsBefore, int objectCount) { }
- public void AllocateBufferAged(string cacheName, int agedCount) { }
- public void AllocateBufferFreeListEmpty(string cacheName, int notGen2CountBefore) { }
- public void FreeBuffer(string cacheName, ulong objectId, int objectHash, int freeCountBefore) { }
- public void FreeBufferStillTooYoung(string cacheName, int notGen2CountBefore) { }
- public void TrimCheck(string cacheName, int totalBuffs, bool neededMoreThanFreeList, int deltaMSec) { }
- public void TrimFree(string cacheName, int totalBuffs, int freeListCount, int toBeFreed) { }
- public void TrimExperiment(string cacheName, int totalBuffs, int freeListCount, int numTrimTrial) { }
- public void TrimFreeSizeOK(string cacheName, int totalBuffs, int freeListCount) { }
- public void TrimFlush(string cacheName, int totalBuffs, int freeListCount, int notGen2CountBefore) { }
- public void AgePendingBuffersResults(string cacheName, int promotedToFreeListCount, int heldBackCount) { }
- public void WalkFreeListResult(string cacheName, int freeListCount, int gen0BuffersInFreeList) { }
-
- internal static ulong AddressOf(object obj)
- {
- return 0;
- }
- }
}
diff --git a/src/mscorlib/src/System/PinnableBufferCacheEventSource.cs b/src/mscorlib/src/System/PinnableBufferCacheEventSource.cs
new file mode 100644
index 0000000000..7d382f3c12
--- /dev/null
+++ b/src/mscorlib/src/System/PinnableBufferCacheEventSource.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+
+namespace System
+{
+ internal sealed class PinnableBufferCacheEventSource
+ {
+ public static readonly PinnableBufferCacheEventSource Log = new PinnableBufferCacheEventSource();
+
+ public bool IsEnabled() { return false; }
+ public void DebugMessage(string message) { }
+ public void Create(string cacheName) { }
+ public void AllocateBuffer(string cacheName, ulong objectId, int objectHash, int objectGen, int freeCountAfter) { }
+ public void AllocateBufferFromNotGen2(string cacheName, int notGen2CountAfter) { }
+ public void AllocateBufferCreatingNewBuffers(string cacheName, int totalBuffsBefore, int objectCount) { }
+ public void AllocateBufferAged(string cacheName, int agedCount) { }
+ public void AllocateBufferFreeListEmpty(string cacheName, int notGen2CountBefore) { }
+ public void FreeBuffer(string cacheName, ulong objectId, int objectHash, int freeCountBefore) { }
+ public void FreeBufferStillTooYoung(string cacheName, int notGen2CountBefore) { }
+ public void TrimCheck(string cacheName, int totalBuffs, bool neededMoreThanFreeList, int deltaMSec) { }
+ public void TrimFree(string cacheName, int totalBuffs, int freeListCount, int toBeFreed) { }
+ public void TrimExperiment(string cacheName, int totalBuffs, int freeListCount, int numTrimTrial) { }
+ public void TrimFreeSizeOK(string cacheName, int totalBuffs, int freeListCount) { }
+ public void TrimFlush(string cacheName, int totalBuffs, int freeListCount, int notGen2CountBefore) { }
+ public void AgePendingBuffersResults(string cacheName, int promotedToFreeListCount, int heldBackCount) { }
+ public void WalkFreeListResult(string cacheName, int freeListCount, int gen0BuffersInFreeList) { }
+
+ internal static ulong AddressOf(object obj)
+ {
+ return 0;
+ }
+ }
+}