|
// 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;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages
{
internal sealed class ForceLowMemoryMode
{
public static readonly Option2<bool> Enabled = new("ForceLowMemoryMode_Enabled", defaultValue: false);
public static readonly Option2<int> SizeInMegabytes = new("ForceLowMemoryMode_Enabled", defaultValue: 500);
private readonly IGlobalOptionService _globalOptions;
private MemoryHogger? _hogger;
public ForceLowMemoryMode(IGlobalOptionService globalOptions)
{
_globalOptions = globalOptions;
globalOptions.AddOptionChangedHandler(this, Options_OptionChanged);
RefreshFromSettings();
}
private void Options_OptionChanged(object sender, object target, OptionChangedEventArgs e)
{
if (e.HasOption(static option => option.Equals(Enabled) || option.Equals(SizeInMegabytes)))
{
RefreshFromSettings();
}
}
private void RefreshFromSettings()
{
var enabled = _globalOptions.GetOption(Enabled);
if (_hogger != null)
{
_hogger.Cancel();
_hogger = null;
}
if (enabled)
{
_hogger = new MemoryHogger();
_ = _hogger.PopulateAndMonitorAsync(_globalOptions.GetOption(SizeInMegabytes));
}
}
private class MemoryHogger
{
private const int BlockSize = 1024 * 1024; // megabyte blocks
private const int MonitorDelay = 10000; // 10 seconds
private readonly List<byte[]> _blocks = [];
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
public MemoryHogger()
{
}
public int Count
{
get { return _blocks.Count; }
}
public void Cancel()
{
_cancellationTokenSource.Cancel();
}
public Task PopulateAndMonitorAsync(int size)
{
// run on background thread
return Task.Factory.StartNew(() => this.PopulateAndMonitorWorkerAsync(size), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
}
private async Task PopulateAndMonitorWorkerAsync(int size)
{
try
{
try
{
for (var n = 0; n < size; n++)
{
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
var block = new byte[BlockSize];
// initialize block bits (so the memory actually gets allocated.. silly runtime!)
for (var i = 0; i < BlockSize; i++)
{
block[i] = 0xFF;
}
_blocks.Add(block);
// don't hog the thread
await Task.Yield();
}
}
catch (OutOfMemoryException)
{
}
// monitor memory to keep it paged in
while (true)
{
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
try
{
// access all block bytes
for (var b = 0; b < _blocks.Count; b++)
{
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
var block = _blocks[b];
byte tmp;
for (var i = 0; i < block.Length; i++)
{
tmp = block[i];
}
// don't hog the thread
await Task.Yield();
}
}
catch (OutOfMemoryException)
{
}
await Task.Delay(MonitorDelay, _cancellationTokenSource.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
_blocks.Clear();
// force garbage collection
for (var i = 0; i < 5; i++)
{
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
}
}
}
}
}
}
|