diff --git a/src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs b/src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs new file mode 100644 index 0000000000000..d17bf7db8ebd8 --- /dev/null +++ b/src/VisualStudio/Core/Impl/Options/AbstractLocalUserRegistryOptionSerializer.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.Win32; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options +{ + internal abstract class AbstractLocalUserRegistryOptionSerializer : ForegroundThreadAffinitizedObject, IOptionSerializer + { + /// + /// An object to gate access to . + /// + private readonly object _gate = new object(); + private readonly RegistryKey _registryKey; + + protected abstract string GetCollectionPathForOption(OptionKey key); + + public AbstractLocalUserRegistryOptionSerializer(IServiceProvider serviceProvider) + : base(assertIsForeground: true) // The VSRegistry.RegistryRoot call requires being on the UI thread or else it will marshal and risk deadlock + { + this._registryKey = VSRegistry.RegistryRoot(serviceProvider, __VsLocalRegistryType.RegType_UserSettings, writable: true); + } + + bool IOptionSerializer.TryFetch(OptionKey optionKey, out object value) + { + var collectionPath = GetCollectionPathForOption(optionKey); + if (collectionPath == null) + { + value = null; + return false; + } + + lock (_gate) + { + using (var subKey = this._registryKey.OpenSubKey(collectionPath)) + { + if (subKey == null) + { + value = null; + return false; + } + + // Options that are of type bool have to be serialized as integers + if (optionKey.Option.Type == typeof(bool)) + { + value = subKey.GetValue(optionKey.Option.Name, defaultValue: (bool)optionKey.Option.DefaultValue ? 1 : 0).Equals(1); + return true; + } + else + { + // Otherwise we can just store normally + value = subKey.GetValue(optionKey.Option.Name, defaultValue: optionKey.Option.DefaultValue); + return true; + } + } + } + } + + bool IOptionSerializer.TryPersist(OptionKey optionKey, object value) + { + if (this._registryKey == null) + { + throw new InvalidOperationException(); + } + + var collectionPath = GetCollectionPathForOption(optionKey); + if (collectionPath == null) + { + return false; + } + + lock (_gate) + { + using (var subKey = this._registryKey.CreateSubKey(collectionPath)) + { + // Options that are of type bool have to be serialized as integers + if (optionKey.Option.Type == typeof(bool)) + { + subKey.SetValue(optionKey.Option.Name, (bool)value ? 1 : 0, RegistryValueKind.DWord); + return true; + } + else + { + subKey.SetValue(optionKey.Option.Name, value); + return true; + } + } + } + } + } +} diff --git a/src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs b/src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs deleted file mode 100644 index bcd282f6fe838..0000000000000 --- a/src/VisualStudio/Core/Impl/Options/AbstractSettingStoreOptionSerializer.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.Win32; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options -{ - internal abstract class AbstractSettingStoreOptionSerializer : ForegroundThreadAffinitizedObject, IOptionSerializer - { - // The gate object guards the RegistryKey - protected readonly object Gate = new object(); - protected readonly RegistryKey RegistryKey; - - protected abstract Tuple GetCollectionPathAndPropertyNameForOption(IOption key, string languageName); - - public AbstractSettingStoreOptionSerializer(IServiceProvider serviceProvider) - : base(assertIsForeground: true) // The VSRegistry.RegistryRoot call requires being on the UI thread or else it will marshal and risk deadlock - { - this.RegistryKey = VSRegistry.RegistryRoot(serviceProvider, __VsLocalRegistryType.RegType_UserSettings, writable: true); - } - - public virtual bool TryFetch(OptionKey optionKey, out object value) - { - return TryFetch(optionKey, (r, k, o) => r.GetValue(k, defaultValue: (bool)o.DefaultValue ? 1 : 0).Equals(1), out value); - } - - public virtual bool TryPersist(OptionKey optionKey, object value) - { - return TryPersist(optionKey, value, (r, k, o, v) => r.SetValue(k, (bool)v ? 1 : 0, RegistryValueKind.DWord)); - } - - protected bool TryFetch(OptionKey optionKey, Func valueGetter, out object value) - { - if (this.RegistryKey == null) - { - throw new InvalidOperationException(); - } - - var collectionPathAndPropertyName = GetCollectionPathAndPropertyNameForOption(optionKey.Option, optionKey.Language); - if (collectionPathAndPropertyName == null) - { - value = null; - return false; - } - - lock (Gate) - { - using (var subKey = this.RegistryKey.OpenSubKey(collectionPathAndPropertyName.Item1)) - { - if (subKey == null) - { - value = null; - return false; - } - - value = valueGetter(subKey, collectionPathAndPropertyName.Item2, optionKey.Option); - return true; - } - } - } - - protected bool TryPersist(OptionKey optionKey, object value, Action valueSetter) - { - // We ignore languageName, since the current use of this class is only for - // language-specific options that apply to a single language. The underlying option - // service has already ensured the languageName is right, so we'll drop it on the floor. - if (this.RegistryKey == null) - { - throw new InvalidOperationException(); - } - - var collectionPathAndPropertyName = GetCollectionPathAndPropertyNameForOption(optionKey.Option, optionKey.Language); - if (collectionPathAndPropertyName == null) - { - return false; - } - - lock (Gate) - { - using (var subKey = this.RegistryKey.CreateSubKey(collectionPathAndPropertyName.Item1)) - { - valueSetter(subKey, collectionPathAndPropertyName.Item2, optionKey.Option, value); - return true; - } - } - } - } -} diff --git a/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs b/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs index 8124387957515..3e3821218c8f5 100644 --- a/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs +++ b/src/VisualStudio/Core/Impl/Options/InternalOptionSerializer.cs @@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options CacheOptions.FeatureName, InternalDiagnosticsOptions.OptionName, InternalSolutionCrawlerOptions.OptionName), Shared] - internal class InternalOptionSerializer : AbstractSettingStoreOptionSerializer + internal class InternalOptionSerializer : AbstractLocalUserRegistryOptionSerializer { [ImportingConstructor] public InternalOptionSerializer(SVsServiceProvider serviceProvider) @@ -29,62 +29,38 @@ public InternalOptionSerializer(SVsServiceProvider serviceProvider) { } - protected override Tuple GetCollectionPathAndPropertyNameForOption(IOption key, string languageName) + protected override string GetCollectionPathForOption(OptionKey key) { - if (key.Feature == EditorComponentOnOffOptions.OptionName) + if (key.Option.Feature == EditorComponentOnOffOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\OnOff\Components", key.Name); + return @"Roslyn\Internal\OnOff\Components"; } - else if (key.Feature == InternalFeatureOnOffOptions.OptionName) + else if (key.Option.Feature == InternalFeatureOnOffOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\OnOff\Features", key.Name); + return @"Roslyn\Internal\OnOff\Features"; } - else if (key.Feature == PerformanceFunctionIdOptionsProvider.Name) + else if (key.Option.Feature == PerformanceFunctionIdOptionsProvider.Name) { - return Tuple.Create(@"Roslyn\Internal\Performance\FunctionId", key.Name); + return @"Roslyn\Internal\Performance\FunctionId"; } - else if (key.Feature == LoggerOptions.FeatureName) + else if (key.Option.Feature == LoggerOptions.FeatureName) { - return Tuple.Create(@"Roslyn\Internal\Performance\Logger", key.Name); + return @"Roslyn\Internal\Performance\Logger"; } - else if (key.Feature == InternalDiagnosticsOptions.OptionName) + else if (key.Option.Feature == InternalDiagnosticsOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\Diagnostics", key.Name); + return @"Roslyn\Internal\Diagnostics"; } - else if (key.Feature == InternalSolutionCrawlerOptions.OptionName) + else if (key.Option.Feature == InternalSolutionCrawlerOptions.OptionName) { - return Tuple.Create(@"Roslyn\Internal\SolutionCrawler", key.Name); + return @"Roslyn\Internal\SolutionCrawler"; } - else if (key.Feature == CacheOptions.FeatureName) + else if (key.Option.Feature == CacheOptions.FeatureName) { - return Tuple.Create(@"Roslyn\Internal\Performance\Cache", key.Name); + return @"Roslyn\Internal\Performance\Cache"; } throw ExceptionUtilities.Unreachable; } - - public override bool TryFetch(OptionKey optionKey, out object value) - { - switch (optionKey.Option.Feature) - { - case CacheOptions.FeatureName: - case InternalSolutionCrawlerOptions.OptionName: - return TryFetch(optionKey, (r, k, o) => r.GetValue(k, defaultValue: o.DefaultValue), out value); - } - - return base.TryFetch(optionKey, out value); - } - - public override bool TryPersist(OptionKey optionKey, object value) - { - switch (optionKey.Option.Feature) - { - case CacheOptions.FeatureName: - case InternalSolutionCrawlerOptions.OptionName: - return TryPersist(optionKey, value, (r, k, o, v) => r.SetValue(k, v, o.Type == typeof(int) ? RegistryValueKind.DWord : RegistryValueKind.QWord)); - } - - return base.TryPersist(optionKey, value); - } } } diff --git a/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj b/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj index 693794ed5db78..953a405263a60 100644 --- a/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj +++ b/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj @@ -244,7 +244,7 @@ - + diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs index 0819647e6177a..31f2cc86114b5 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode.cs @@ -2,41 +2,48 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages { - internal class ForceLowMemoryMode + internal sealed partial class ForceLowMemoryMode { + private readonly IOptionService _optionService; private MemoryHogger _hogger; - public static readonly ForceLowMemoryMode Instance = new ForceLowMemoryMode(); - - private ForceLowMemoryMode() + public ForceLowMemoryMode(IOptionService optionService) { + _optionService = optionService; + + optionService.OptionChanged += Options_OptionChanged; + + RefreshFromSettings(); } - public int Size { get; set; } = 500; // default to 500 MB + private void Options_OptionChanged(object sender, OptionChangedEventArgs e) + { + if (e.Option.Feature == ForceLowMemoryMode.OptionName) + { + RefreshFromSettings(); + } + } - public bool Enabled + private void RefreshFromSettings() { - get + var enabled = _optionService.GetOption(Enabled); + + if (_hogger != null) { - return _hogger != null; + _hogger.Cancel(); + _hogger = null; } - set + if (enabled) { - if (value && _hogger == null) - { - _hogger = new MemoryHogger(); - var ignore = _hogger.PopulateAndMonitorAsync(this.Size); - } - else if (!value && _hogger != null) - { - _hogger.Cancel(); - _hogger = null; - } + _hogger = new MemoryHogger(); + var ignore = _hogger.PopulateAndMonitorAsync(_optionService.GetOption(SizeInMegabytes)); } } @@ -46,7 +53,7 @@ private class MemoryHogger private const int MonitorDelay = 10000; // 10 seconds private readonly List _blocks = new List(); - private bool _cancelled; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); public MemoryHogger() { @@ -59,55 +66,35 @@ public int Count public void Cancel() { - _cancelled = true; + _cancellationTokenSource.Cancel(); } public Task PopulateAndMonitorAsync(int size) { // run on background thread - return Task.Run(() => this.PopulateAndMonitorWorkerAsync(size)); + return Task.Factory.StartNew(() => this.PopulateAndMonitorWorkerAsync(size), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); } private async Task PopulateAndMonitorWorkerAsync(int size) { try - { - for (int n = 0; n < size && !_cancelled; n++) - { - var block = new byte[BlockSize]; - - // initialize block bits (so the memory actually gets allocated.. silly runtime!) - for (int 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 (!_cancelled) { try { - // access all block bytes - for (var b = 0; b < _blocks.Count && !_cancelled; b++) + for (int n = 0; n < size; n++) { - var block = _blocks[b]; + _cancellationTokenSource.Token.ThrowIfCancellationRequested(); - byte tmp; - for (int i = 0; i < block.Length; i++) + var block = new byte[BlockSize]; + + // initialize block bits (so the memory actually gets allocated.. silly runtime!) + for (int i = 0; i < BlockSize; i++) { - tmp = block[i]; + block[i] = 0xFF; } + _blocks.Add(block); + // don't hog the thread await Task.Yield(); } @@ -116,16 +103,47 @@ private async Task PopulateAndMonitorWorkerAsync(int size) { } - await Task.Delay(MonitorDelay).ConfigureAwait(true); - } + // 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]; - _blocks.Clear(); + byte tmp; + for (int i = 0; i < block.Length; i++) + { + tmp = block[i]; + } - // force garbage collection - for (int i = 0; i < 5; i++) + // don't hog the thread + await Task.Yield(); + } + } + catch (OutOfMemoryException) + { + } + + await Task.Delay(MonitorDelay, _cancellationTokenSource.Token).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { - GC.Collect(GC.MaxGeneration); - GC.WaitForPendingFinalizers(); + _blocks.Clear(); + + // force garbage collection + for (int i = 0; i < 5; i++) + { + GC.Collect(GC.MaxGeneration); + GC.WaitForPendingFinalizers(); + } } } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs new file mode 100644 index 0000000000000..2905a375d7108 --- /dev/null +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/ForceLowMemoryMode_Options.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices.Implementation.Options; +using Microsoft.VisualStudio.Shell; + +namespace Roslyn.VisualStudio.DiagnosticsWindow.OptionsPages +{ + internal sealed partial class ForceLowMemoryMode + { + public const string OptionName = nameof(ForceLowMemoryMode); + public static readonly Option Enabled = new Option(OptionName, nameof(Enabled), defaultValue: false); + public static readonly Option SizeInMegabytes = new Option(OptionName, nameof(SizeInMegabytes), defaultValue: 500); + } + + [ExportOptionSerializer(ForceLowMemoryMode.OptionName), Shared] + internal sealed class ForceLowMemoryModeSerializer : AbstractLocalUserRegistryOptionSerializer + { + [ImportingConstructor] + public ForceLowMemoryModeSerializer(SVsServiceProvider serviceProvider) : base(serviceProvider) + { + } + + protected override string GetCollectionPathForOption(OptionKey key) + { + return @"Roslyn\ForceLowMemoryMode"; + } + } +} diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs index 5ed986e9cd107..26c14b8d2e454 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/OptionPages/InternalFeaturesOnOffPage.cs @@ -35,48 +35,23 @@ public InternalFeaturesOptionsControl(string featureOptionName, IServiceProvider protected override void AddOptions(Panel panel) { - base.AddOptions(panel); - // add force low memory mode option var group = new WrapPanel(); - var lowMemoryMode = ForceLowMemoryMode.Instance; - var cb = CreateBoundCheckBox("Forced Low Memory Mode", lowMemoryMode, "Enabled"); - group.Children.Add(cb); - var tb = CreateBoundTextBox("", lowMemoryMode, "Size"); - group.Children.Add(tb); - var text = new TextBlock() { Text = "MB" }; - group.Children.Add(text); - panel.Children.Add(group); - } - - private CheckBox CreateBoundCheckBox(string content, object source, string sourcePropertyName) - { - var cb = new CheckBox { Content = content }; - var binding = new Binding() - { - Source = source, - Path = new PropertyPath(sourcePropertyName) - }; - - base.AddBinding(cb.SetBinding(CheckBox.IsCheckedProperty, binding)); + var cb = new CheckBox { Content = "Forced Low Memory Mode: allocate" }; + BindToOption(cb, ForceLowMemoryMode.Enabled); + group.Children.Add(cb); - return cb; - } + var textBox = new TextBox { MinWidth = 60 }; + BindToOption(textBox, ForceLowMemoryMode.SizeInMegabytes); + group.Children.Add(textBox); - private TextBox CreateBoundTextBox(string content, object source, string sourcePropertyName) - { - var tb = new TextBox { Text = content }; + group.Children.Add(new TextBlock { Text = "megabytes of extra memory in devenv.exe" }); - var binding = new Binding() - { - Source = source, - Path = new PropertyPath(sourcePropertyName) - }; - - base.AddBinding(tb.SetBinding(TextBox.TextProperty, binding)); + panel.Children.Add(group); - return tb; + // and add the rest of the options + base.AddOptions(panel); } } } diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj index 3ac32cdd595b2..4f860a12bc1ae 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindow.csproj @@ -101,6 +101,7 @@ + diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs index 8e538d946576a..cfd8bb89c7dd0 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/VisualStudioDiagnosticsWindowPackage.cs @@ -4,6 +4,9 @@ using System.ComponentModel; using System.ComponentModel.Design; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.Implementation.Options; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -39,8 +42,11 @@ namespace Roslyn.VisualStudio.DiagnosticsWindow [ProvideToolWindow(typeof(DiagnosticsWindow))] [Guid(GuidList.guidVisualStudioDiagnosticsWindowPkgString)] [Description("Roslyn Diagnostics Window")] + [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] public sealed class VisualStudioDiagnosticsWindowPackage : Package { + private ForceLowMemoryMode _forceLowMemoryMode; + /// /// This function is called when the user clicks the menu item that shows the /// tool window. See the Initialize method to see how the menu item is associated to @@ -72,6 +78,10 @@ protected override void Initialize() { base.Initialize(); + var componentModel = (IComponentModel)GetService(typeof(SComponentModel)); + + _forceLowMemoryMode = new ForceLowMemoryMode(componentModel.GetService()); + // Add our command handlers for menu (commands must exist in the .vsct file) OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (null != mcs) diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest index 81112ee54201e..e450b6e8fc219 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/source.extension.vsixmanifest @@ -16,6 +16,7 @@ +