GCNotification
Provides a way to receive notifications from Garbage Collector asynchronously.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace DotNext.Runtime
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public abstract class GCNotification
{
private sealed class MemoryThresholdFilter : GCNotification
{
private readonly double threshold;
internal MemoryThresholdFilter(double threshold)
{
this.threshold = threshold;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return (double)info.MemoryLoadBytes <= (double)info.HighMemoryLoadThresholdBytes * threshold;
}
}
private sealed class HeapCompactionFilter : GCNotification
{
internal static readonly HeapCompactionFilter Instance = new HeapCompactionFilter();
private HeapCompactionFilter()
{
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return info.Compacted;
}
}
private sealed class GCEvent : GCNotification
{
internal static readonly GCEvent Instance = new GCEvent();
private GCEvent()
{
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return true;
}
public override GCNotification And(GCNotification right)
{
return right;
}
public override GCNotification Or(GCNotification right)
{
return this;
}
}
private sealed class GenerationFilter : GCNotification
{
private readonly int generation;
internal GenerationFilter(int generation)
{
this.generation = generation;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return info.Generation == generation;
}
}
private sealed class HeapFragmentationThresholdFilter : GCNotification
{
private readonly double fragmentationPercentage;
internal HeapFragmentationThresholdFilter(double fragmentation)
{
fragmentationPercentage = fragmentation;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return (double)info.FragmentedBytes / (double)info.HeapSizeBytes >= fragmentationPercentage;
}
}
private sealed class AndFilter : GCNotification
{
private readonly GCNotification left;
private readonly GCNotification right;
internal AndFilter(GCNotification left, GCNotification right)
{
this.left = left;
this.right = right;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
if (left.Test(ref info))
return right.Test(ref info);
return false;
}
}
private sealed class GCOrFilter : GCNotification
{
private readonly GCNotification left;
private readonly GCNotification right;
internal GCOrFilter(GCNotification left, GCNotification right)
{
this.left = left;
this.right = right;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
if (!left.Test(ref info))
return right.Test(ref info);
return true;
}
}
private sealed class GCXorFilter : GCNotification
{
private readonly GCNotification left;
private readonly GCNotification right;
internal GCXorFilter(GCNotification left, GCNotification right)
{
this.left = left;
this.right = right;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return left.Test(ref info) ^ right.Test(ref info);
}
}
private sealed class GCNotFilter : GCNotification
{
private readonly GCNotification filter;
internal GCNotFilter(GCNotification filter)
{
this.filter = filter;
}
internal override bool Test([In] [IsReadOnly] GCMemoryInfo info)
{
return !filter.Test(ref info);
}
public override GCNotification Negate()
{
return filter;
}
}
private sealed class Tracker : TaskCompletionSource<GCMemoryInfo>, IGCCallback
{
private readonly GCNotification filter;
internal Tracker(GCNotification filter)
: base(TaskCreationOptions.RunContinuationsAsynchronously)
{
this.filter = filter;
}
~Tracker()
{
GCMemoryInfo info = GC.GetGCMemoryInfo();
if (filter.Test(ref info))
TrySetResult(info);
else
GC.ReRegisterForFinalize(this);
}
}
private abstract class GCCallback<T>
{
private readonly Action<T, GCMemoryInfo> callback;
private readonly T state;
internal GCMemoryInfo MemoryInfo;
private protected GCCallback(Action<T, GCMemoryInfo> callback, T state)
{
this.callback = callback;
this.state = state;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected void Execute()
{
callback(state, MemoryInfo);
}
internal abstract void Enqueue();
[MethodImpl(MethodImplOptions.NoInlining)]
private protected static void UnsafeExecute(object state)
{
Unsafe.As<GCCallback<T>>(state).Execute();
}
}
private sealed class UnsafeCallback<T> : GCCallback<T>, IThreadPoolWorkItem
{
internal UnsafeCallback(Action<T, GCMemoryInfo> callback, T state)
: base(callback, state)
{
}
void IThreadPoolWorkItem.Execute()
{
Execute();
}
internal override void Enqueue()
{
ThreadPool.UnsafeQueueUserWorkItem(this, false);
}
}
private sealed class SynchronizationContextBoundCallback<T> : GCCallback<T>
{
private static readonly SendOrPostCallback CallbackInvoker = GCCallback<T>.UnsafeExecute;
private readonly SynchronizationContext context;
internal SynchronizationContextBoundCallback(Action<T, GCMemoryInfo> callback, T state, SynchronizationContext context)
: base(callback, state)
{
this.context = context;
}
internal override void Enqueue()
{
context.Post(CallbackInvoker, this);
}
}
private sealed class TaskSchedulerBoundCallback<T> : GCCallback<T>
{
private static readonly Action<object> TaskInvoker = GCCallback<T>.UnsafeExecute;
private readonly Task task;
private readonly TaskScheduler scheduler;
internal TaskSchedulerBoundCallback(Action<T, GCMemoryInfo> callback, T state, TaskScheduler scheduler)
: base(callback, state)
{
this.scheduler = scheduler;
task = CreateCallbackInvocationTask(this);
}
internal static Task CreateCallbackInvocationTask(GCCallback<T> callback)
{
return new Task(TaskInvoker, callback, CancellationToken.None, TaskCreationOptions.DenyChildAttach);
}
internal override void Enqueue()
{
task.Start(scheduler);
}
}
private abstract class ExecutionContextBoundCallback<T> : GCCallback<T>
{
private readonly ExecutionContext context;
private protected abstract ContextCallback Callback { get; }
private protected ExecutionContextBoundCallback(Action<T, GCMemoryInfo> callback, T state, ExecutionContext context)
: base(callback, state)
{
this.context = context;
}
internal sealed override void Enqueue()
{
ExecutionContext.Run(context, Callback, this);
}
}
private sealed class SafeCallback<T> : ExecutionContextBoundCallback<T>
{
private static readonly Action<object> WorkItem;
private static readonly ContextCallback ContextCallback;
private protected override ContextCallback Callback => ContextCallback;
static SafeCallback()
{
WorkItem = GCCallback<T>.UnsafeExecute;
ContextCallback = delegate(object callback) {
ThreadPool.QueueUserWorkItem<object>(WorkItem, callback, false);
};
}
internal SafeCallback(Action<T, GCMemoryInfo> callback, T state, ExecutionContext context)
: base(callback, state, context)
{
}
}
private sealed class ExecutionAndSynchronizationContextBoundCallback<T> : ExecutionContextBoundCallback<T>
{
private static readonly SendOrPostCallback CallbackInvoker;
private static readonly ContextCallback ContextCallback;
private readonly SynchronizationContext context;
private protected override ContextCallback Callback => ContextCallback;
static ExecutionAndSynchronizationContextBoundCallback()
{
CallbackInvoker = GCCallback<T>.UnsafeExecute;
ContextCallback = delegate(object callback) {
Unsafe.As<ExecutionAndSynchronizationContextBoundCallback<T>>(callback).Post();
};
}
internal ExecutionAndSynchronizationContextBoundCallback(Action<T, GCMemoryInfo> callback, T state, ExecutionContext context, SynchronizationContext syncContext)
: base(callback, state, context)
{
this.context = syncContext;
}
private void Post()
{
context.Post(CallbackInvoker, this);
}
}
private sealed class ExecutionContextAndTaskSchedulerBoundCallback<T> : ExecutionContextBoundCallback<T>
{
private static readonly ContextCallback ContextCallback;
private readonly TaskScheduler scheduler;
private readonly Task task;
private protected override ContextCallback Callback => ContextCallback;
static ExecutionContextAndTaskSchedulerBoundCallback()
{
ContextCallback = delegate(object callback) {
Unsafe.As<ExecutionContextAndTaskSchedulerBoundCallback<T>>(callback).Start();
};
}
internal ExecutionContextAndTaskSchedulerBoundCallback(Action<T, GCMemoryInfo> callback, T state, ExecutionContext context, TaskScheduler scheduler)
: base(callback, state, context)
{
this.scheduler = scheduler;
task = TaskSchedulerBoundCallback<T>.CreateCallbackInvocationTask(this);
}
private void Start()
{
task.Start(scheduler);
}
}
private sealed class Tracker<T> : IGCCallback
{
private readonly GCNotification filter;
private readonly GCCallback<T> callback;
internal Tracker(GCNotification filter, T state, Action<T, GCMemoryInfo> callback, bool continueOnCapturedContext)
{
this.filter = filter;
ExecutionContext executionContext = ExecutionContext.Capture();
if (!continueOnCapturedContext)
this.callback = ((executionContext == null) ? ((GCCallback<T>)new UnsafeCallback<T>(callback, state)) : ((GCCallback<T>)new SafeCallback<T>(callback, state, executionContext)));
else {
SynchronizationContext current = SynchronizationContext.Current;
if (current != null && current.GetType() != typeof(SynchronizationContext))
this.callback = ((executionContext == null) ? ((GCCallback<T>)new SynchronizationContextBoundCallback<T>(callback, state, current)) : ((GCCallback<T>)new ExecutionAndSynchronizationContextBoundCallback<T>(callback, state, executionContext, current)));
else
this.callback = ((executionContext == null) ? ((GCCallback<T>)new TaskSchedulerBoundCallback<T>(callback, state, TaskScheduler.Current)) : ((GCCallback<T>)new ExecutionContextAndTaskSchedulerBoundCallback<T>(callback, state, executionContext, TaskScheduler.Current)));
}
}
~Tracker()
{
GCMemoryInfo info = GC.GetGCMemoryInfo();
if (filter.Test(ref info)) {
callback.MemoryInfo = info;
callback.Enqueue();
} else
GC.ReRegisterForFinalize(this);
}
}
[StructLayout(LayoutKind.Auto)]
[System.Runtime.CompilerServices.NullableContext(0)]
public readonly struct Registration : IDisposable
{
private readonly GCIntermediateReference reference;
internal Registration(IGCCallback callback)
{
reference = new GCIntermediateReference(callback);
}
public void Dispose()
{
reference?.Clear();
}
}
public Registration Register<[System.Runtime.CompilerServices.Nullable(2)] T>(Action<T, GCMemoryInfo> callback, T state, bool captureContext = false)
{
ArgumentNullException.ThrowIfNull((object)callback, "callback");
return new Registration(new Tracker<T>(this, state, callback, captureContext));
}
public Task<GCMemoryInfo> WaitAsync(TimeSpan timeout, CancellationToken token = default(CancellationToken))
{
Task<GCMemoryInfo> task;
if (token.IsCancellationRequested)
task = Task.FromCanceled<GCMemoryInfo>(token);
else {
Tracker tracker = new Tracker(this);
task = tracker.Task.WaitAsync(timeout, token);
task.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(new GCIntermediateReference(tracker).Clear);
GC.KeepAlive(tracker);
}
return task;
}
public Task<GCMemoryInfo> WaitAsync(CancellationToken token = default(CancellationToken))
{
return WaitAsync(Timeout.InfiniteTimeSpan, token);
}
public static GCNotification HeapCompaction()
{
return HeapCompactionFilter.Instance;
}
public static GCNotification GCTriggered()
{
return GCEvent.Instance;
}
public static GCNotification GCTriggered(int generation)
{
if (generation < 0 || generation > GC.MaxGeneration)
throw new ArgumentOutOfRangeException("generation");
return new GenerationFilter(generation);
}
public static GCNotification HeapFragmentation(double threshold)
{
if (!double.IsFinite(threshold) || !threshold.IsBetween(0, 1, BoundType.RightClosed))
throw new ArgumentOutOfRangeException("threshold");
return new HeapFragmentationThresholdFilter(threshold);
}
public static GCNotification MemoryThreshold(double threshold)
{
if (!double.IsFinite(threshold) || !threshold.IsBetween(0, 1, BoundType.RightClosed))
throw new ArgumentOutOfRangeException("threshold");
return new MemoryThresholdFilter(threshold);
}
private protected GCNotification()
{
}
internal abstract bool Test([In] [IsReadOnly] GCMemoryInfo info);
public virtual GCNotification And(GCNotification right)
{
ArgumentNullException.ThrowIfNull((object)right, "right");
return new AndFilter(this, right);
}
public virtual GCNotification Or(GCNotification right)
{
ArgumentNullException.ThrowIfNull((object)right, "right");
return new GCOrFilter(this, right);
}
public virtual GCNotification ExclusiveOr(GCNotification right)
{
ArgumentNullException.ThrowIfNull((object)right, "right");
return new GCXorFilter(this, right);
}
public virtual GCNotification Negate()
{
return new GCNotFilter(this);
}
public static GCNotification operator &(GCNotification left, GCNotification right)
{
return left.And(right);
}
public static GCNotification operator |(GCNotification left, GCNotification right)
{
return left.Or(right);
}
public static GCNotification operator ^(GCNotification left, GCNotification right)
{
return left.ExclusiveOr(right);
}
public static GCNotification operator !(GCNotification filter)
{
return filter.Negate();
}
}
}