File: OptionPages\ForceLowMemoryMode.cs
Web Access
Project: src\src\VisualStudio\VisualStudioDiagnosticsToolWindow\Roslyn.VisualStudio.DiagnosticsWindow.csproj (Roslyn.VisualStudio.DiagnosticsWindow)
// 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 sealed 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();
                }
            }
        }
    }
}