{"id":234670,"date":"2021-09-24T15:12:02","date_gmt":"2021-09-24T22:12:02","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/visualstudio\/?p=234670"},"modified":"2021-09-24T15:12:02","modified_gmt":"2021-09-24T22:12:02","slug":"avoiding-memory-leaks-in-visual-studio-editor-extensions","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/visualstudio\/avoiding-memory-leaks-in-visual-studio-editor-extensions\/","title":{"rendered":"Avoiding Memory Leaks in Visual Studio Editor Extensions"},"content":{"rendered":"<p>Visual Studio extenders make VS even better by augmenting it with specialized tools, new languages, and workflows. As a Visual Studio extender, you can ensure your extension\u2019s customers have the most performant, reliable experience possible by avoiding common sources of memory leaks, described within this blog post.<\/p>\n<h2>Background<\/h2>\n<p>In the early 2010s, a small team of engineers was spun up to rethink Visual Studio\u2019s C++\/COM\/Win32 Editor and Extensibility in terms of new, cutting edge technologies like C#, .NET, and <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/desktop\/wpf\/overview\/?view=netdesktop-5.0\">WPF<\/a>.<\/p>\n<p>This team created several foundational pieces, such as <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/extensibility\/managed-extensibility-framework-in-the-editor?view=vs-2019\">Visual Studio MEF<\/a> and the <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/extensibility\/inside-the-editor?view=vs-2019\">Visual Studio Text data model platform<\/a>. Together, these technologies and the innovations that followed them re-invigorated the Visual Studio ecosystem and enabled some of its most distinctive features, like <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/ide\/find-code-changes-and-other-history-with-codelens?view=vs-2019\">CodeLens<\/a>.<\/p>\n<p>After a decade of iteration, the VS IDE team has accumulated a list of best practices for using these components from extensions without adversely impacting Visual Studio performance. In-process extensions can do just about anything, so in the words of \u2018Uncle Ben\u2019, <em>\u201cWith great power comes great responsibility\u201d<\/em>.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/the-future-of-visual-studio-extensions\/\">The Future of Visual Studio Extensions<\/a> details how we\u2019ll improve Visual Studio\u2019s resiliency in future releases by running extensions in a separate process so they can\u2019t impact the IDE\u2019s performance. For now, though, here\u2019s some DOs and DON\u2019Ts for crafting leak free VS Editor extensions with the existing in-process extensibility model.<\/p>\n<h2>MEF Part Memory Leaks<\/h2>\n<h3>What are MEF Parts<\/h3>\n<p>At its base level, the Visual Studio text editor is a collection of <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/framework\/mef\/attributed-programming-model-overview-mef\">MEF parts<\/a>. For a class to be a MEF part, all that is required is that it be decorated with an [Export] attribute in C#, like the following, be declared in a DLL <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/extensibility\/vsix-manifest-designer?view=vs-2019\">registered in the VSIX manifest as a MefComponent<\/a>, and be imported somewhere by the IDE or another MEF part.<\/p>\n<pre class=\"prettyprint\">[Export(typeof(ITextViewCreationListener))]\r\ninternal class MyPart : ITextViewCreationListener\r\n{\r\n    private readonly Lazy&lt;IContentTypeRegistryService&gt; contentTypeRegistryService;\r\n\r\n    [ImportingConstructor]\r\n    public MyPart(Lazy&lt;IContentTypeRegistryService&gt; contentTypeRegistryService)\r\n    {\r\n        this.contentTypeRegistryService = contentTypeRegistryService;\r\n    }\r\n\r\n    public void TextViewCreated(ITextView textView)\r\n    {\r\n    }\r\n}<\/pre>\n<p>In the example above, we are exporting a MEF part, implementing ITextViewCreationListener, and importing a service that implements IContentTypeRegistryService from the IDE.<\/p>\n<h3>Properties of MEF<\/h3>\n<p>MEF has some important properties that you should make note of to avoid MEF related leaks.<\/p>\n<ul>\n<li>The default policy for MEF parts in Visual Studio is shared, meaning that up to <span style=\"font-weight: normal !msorm;\"><strong>one instance of each part<\/strong><\/span> is created, no matter how often it\u2019s imported.<\/li>\n<li>Shared MEF parts can be configured to be instantiated only as needed, as shown via the Lazy&lt;T&gt; class, but <span style=\"font-weight: normal !msorm;\"><strong>once they are created, these parts and any object they reference will live until Visual Studio is restarted<\/strong><\/span>.<\/li>\n<\/ul>\n<h3>Common MEF Part Leaks<\/h3>\n<p>Consider a second MEF part example: an extender is exporting\u00a0an ITextViewCreationListener, that will be signaled whenever a new text editor is opened, storing a reference to the text view for later use.<\/p>\n<pre class=\"prettyprint\">[Export(typeof(ITextViewCreationListener))]\r\ninternal sealed class MyPart : ITextViewCreationListener\r\n{\r\n    private readonly Lazy&lt;IContentTypeRegistryService&gt; contentTypeRegistryService;\r\n\r\n    private ITextView mostRecentTextView;\r\n\r\n    [ImportingConstructor]\r\n    public MyPart(Lazy&lt;IContentTypeRegistryService&gt; contentTypeRegistryService)\r\n    {\r\n        this.contentTypeRegistryService = contentTypeRegistryService;\r\n    }\r\n\r\n    public void TextViewCreated(ITextView textView)\r\n    {\r\n        this.mostRecentTextView = textView;\r\n    }\r\n}<\/pre>\n<p>The state in the class has a fixed size (one text view, not a continuously growing collection), and it\u2019s stored in a local variable, so at first glance, this code seems harmless.<\/p>\n<h3>The Problem<\/h3>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture1-1.png\"><img decoding=\"async\" class=\"alignnone wp-image-234676 \" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture1-1.png\" alt=\"Image Picture1\" width=\"767\" height=\"263\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture1-1.png 624w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture1-1-300x103.png 300w\" sizes=\"(max-width: 767px) 100vw, 767px\" \/><\/a><\/p>\n<ul>\n<li>ITextViewCreationListener is a MEF part, meaning it will not be <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/garbage-collection\/\">garbage collected<\/a> until Visual Studio is restarted, and any objects it directly references, including this text view, will leak.<\/li>\n<li>The ITextView can be a very heavy object:\n<ul>\n<li>It has references to the ITextBuffer containing the underlying document, which can be very large, and is entirely stored in memory.<\/li>\n<li>It has references to the undo stack for the text view, which can potentially store a long history of changes to the document while it was open.<\/li>\n<li>It has references to the various UI elements, extensions, margins, taggers, etc. that were used to customize the presentation of the document.<\/li>\n<li>Both ITextView and ITextBuffer have a \u2018.Properties\u2019 member, which is a dictionary of lots of arbitrary data that extensions have associated with that editor. The Roslyn C# language service, for example, stores a reference to the workspace in this property bag.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>The implication is that <span style=\"font-weight: normal !msorm;\"><strong>a single leaked object can transitively leak much more than expected<\/strong><\/span>. With this example, closing your solution and opening another solution would consume the memory of having both solutions open simultaneously!<\/p>\n<h3>Best Practices for MEF parts<\/h3>\n<p>Since MEF parts live until Visual Studio is shutdown, it is important to be careful about how you use them to avoid memory leaks.<\/p>\n<ul>\n<li>Avoid storing references to objects in MEF parts except other shared MEF parts (singleton services).<\/li>\n<li>Avoid storing any state in MEF parts. Most Visual Studio IDE MEF parts follow a provider pattern: the MEF part is a stateless factory (e.g.: <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.visualstudio.text.tagging.itaggerprovider?view=visualstudiosdk-2019\">ITaggerProvider<\/a>) for acquiring services and it in turn creates one or more stateful extensions (e.g.: <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.visualstudio.text.tagging.itagger-1?view=visualstudiosdk-2019\">ITagger<\/a>).<\/li>\n<li>Associate any shared state with objects that have a well-defined lifetime instead of with a MEF part. For example, .NET compiler (Roslyn) has a core \u2018workspace\u2019 concept, that is associated with an ITextView via the view\u2019s \u2018.Properties\u2019 member. <a href=\"https:\/\/github.com\/microsoft\/VSSDK-Extensibility-Samples\/search?q=GetOrCreateSingletonProperty\">Many VS extensions follow this pattern<\/a>, enabling their state to go out of scope and get collected when the owning text view is closed or the owning document is garbage collected.<\/li>\n<li>In the rare case where you need to reference state in a MEF part, consider using a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.weakreference?view=net-5.0\">WeakReference<\/a>, using <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.visualstudio.text.editor.itextview.closed?view=visualstudiosdk-2019\">relevant cleanup events<\/a> to delete it when done with it, or enforcing a max size, if the stored data is a cache or collection of items.<\/li>\n<\/ul>\n<h2>C# Event Listener leaks<\/h2>\n<p>In addition to MEF, Visual Studio relies heavily on <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/programming-guide\/events\/\">C# events<\/a> to compose extensions into the editor. Under all the syntactic sugar, an event in C# is effectively just a collection of subscriber delegates that get invoked by the class implementing the event one by one when an event is \u2018raised\u2019. One implication of this is that every class implementing an event has a strong reference to any current subscribers, preventing those objects from being garbage collected.<\/p>\n<pre class=\"prettyprint\">[Export(typeof(ITextViewCreationListener))]\r\n[Name(\"Colorization tagger\")]\r\n[ContentType(\"CSharp\")]\r\n[TagType(typeof(IClassificationTag))]\r\ninternal sealed class MyPart : IViewTaggerProvider\r\n{\r\n    private readonly Lazy&lt;IEditorOptionsFactoryService&gt; optionsFactoryService;\r\n\r\n    [ImportingConstructor]\r\n    public MyPart(Lazy&lt;IEditorOptionsFactoryService&gt; optionsFactoryService)\r\n    {\r\n        this.optionsFactoryService = optionsFactoryService;\r\n    }\r\n\r\n    public ITagger&lt;T&gt; CreateTagger&lt;T&gt;(ITextView textView, ITextBuffer buffer) where T : ITag\r\n    {\r\n        return new MyTagger(textView, this.optionsFactoryService.Value) as ITagger&lt;T&gt;;\r\n    }\r\n}\r\n\r\ninternal sealed class MyTagger : ITagger&lt;IClassificationTag&gt;\r\n{\r\n    private readonly ITextView textView;\r\n\r\n    public MyTagger(\r\n        ITextView textView,\r\n        IEditorOptionsFactoryService optionsFactoryService)\r\n    {\r\n        this.textView = textView;\r\n        optionsFactoryService.GlobalOptions.OptionsChanged += this.OnOptionChanged;\r\n    }\r\n\r\n    public event EventHandler&lt;SnapshotSpanEventArgs&gt; TagsChanged;\r\n\r\n    public IEnumerable&lt;ITagSpan&lt;IClassificationTag&gt;&gt; GetTags(NormalizedSnapshotSpanCollection spans)\r\n    {\r\n        return Array.Empty&lt;ITagSpan&lt;IClassificationTag&gt;&gt;();\r\n    }\r\n\r\n    private void OnOptionChanged(object sender, EditorOptionChangedEventArgs e)\r\n    {\r\n    }\r\n}<\/pre>\n<p>In the above example, we are once again doing something fairly innocuous: we\u2019re creating a \u2018classification <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/extensibility\/inside-the-editor?view=vs-2019#tags-and-classifiers\">tagger<\/a>\u2019, commonly used to provide code colorization, and subscribing to EditorOptions.OptionChanged, so we can update the colorization if some global option changes.<\/p>\n<p>Subscribing each produced ITagger to OptionChanged, constructs a strong reference to this tagger. Since IEditorOptionsFactoryService.GlobalOptions is referenced by a MEF part (IEditorOptionsFactoryService), it is kept alive for the entirety of the VS session, and anything it references is too.<\/p>\n<p>In this example, every time a document is opened and closed, we would leak another instance of MyTagger and anything it references. The tagger references the view, and is targeting C#, so we\u2019d also leak the entire Roslyn workspace, and <strong>a unique copy of every C# file that user opened during their VS session<\/strong>!<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture3.png\"><img decoding=\"async\" class=\"alignnone wp-image-234683\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture3.png\" alt=\"Image Picture3\" width=\"743\" height=\"212\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture3.png 624w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture3-300x86.png 300w\" sizes=\"(max-width: 743px) 100vw, 743px\" \/><\/a><\/p>\n<h3>Best Practices for C# Event Listeners<\/h3>\n<p>Now that we\u2019ve established the risks of memory leaks from C# event listeners, here are some best practices you can adopt in your extension.<\/p>\n<ul>\n<li>Every event subscription should be unsubscribed at the appropriate time. If you see event handlers in your code that do not unsubscribe, look for opportunities, such as ITextView.Close, to unsubscribe them.<\/li>\n<li>Many Editor extensibility points, including, <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.visualstudio.text.tagging.itagger-1?view=visualstudiosdk-2019\">ITaggers<\/a>, automatically call Dispose() on any part that implements the optional <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.idisposable?view=net-5.0\">IDisposable<\/a> interface when that part is no longer needed. When implementing editor parts, consider implementing IDisposable.Dispose() to see if it\u2019s called by the editor when the owning text view, text document, or IntelliSense is cleaned up. You can unsubscribe from event handlers in your implementation of IDisposable.Dispose().<\/li>\n<\/ul>\n<h2>Other Common Leaks<\/h2>\n<p>Other common sources of memory leaks are:<\/p>\n<ul>\n<li><strong><u>Static State<\/u>:<\/strong> much like MEF part leaks, information stored in static fields and properties is <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/garbage-collection\/fundamentals#memory-release\">gc-rooted<\/a> by the garbage collector when the owning type is referenced for the first time and is not cleaned up until Visual Studio shutdown. Best practice is to:\n<ul>\n<li>Avoid static state unless there is no other alternative.<\/li>\n<li>For static collections, especially caches, set a max size (e.g.: \u201910 items\u2019).<\/li>\n<\/ul>\n<\/li>\n<li><strong><u>WPF Data Binding<\/u><\/strong>: behind XAML markup, WPF UI, used by Visual Studio extensions, employs C# event handlers, delegates, and even static dictionaries to implement Data Binding. Certain types of data binding can cause UI objects and any state it references to leak. For example, AddValueChanged(), as used below, adds a reference to the WPF control to a static dictionary, leaking it unless the listener is later unsubscribed.<\/li>\n<\/ul>\n<pre class=\"prettyprint\">private void OnLoaded(object sender, RoutedEventArgs e)\r\n{\r\n    var textDp = DepedencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));\r\n    textDp.AddValueChanged(this, this.OnTextChanged);\r\n\r\n    this.RenderHighlighedSpans();\r\n}<\/pre>\n<h2>Diagnosing Leaks<\/h2>\n<p>To diagnose leaks in your Visual Studio extension you can use either of the following memory debugging tools.<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/profiling\/memory-usage-without-debugging2?view=vs-2019#memory-usage-snapshot-reports\">Visual Studio .NET Memory Usage Snapshots tool<\/a>: this built in Visual Studio functionality captures \u2018memory snapshots\u2019, before and after your extension scenario, that can then be diffed to identify memory leaks.<\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/improving-your-apps-performance-with-perfview\/#gc-heap-dump-viewer\">PerfView<\/a>: this open-source Microsoft tool offers similar heap snapshotting and diffing capabilities under Memory &gt; Take Heap Snapshot.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture4.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-234684\" src=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture4.png\" alt=\"Image Picture4\" width=\"326\" height=\"125\" srcset=\"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture4.png 326w, https:\/\/devblogs.microsoft.com\/visualstudio\/wp-content\/uploads\/sites\/4\/2021\/09\/Picture4-300x115.png 300w\" sizes=\"(max-width: 326px) 100vw, 326px\" \/><\/a><\/p>\n<p>The typical workflow is to open Visual Studio, run your scenario once and then close all associated tool windows\/projects\/documents and take a memory snapshot (the warmup iteration), then repeat the scenario again, take another snapshot, and diff the two. Commonly leaked types to look for are:<\/p>\n<ul>\n<li>WpfTextView<\/li>\n<li>TextBuffer<\/li>\n<li>ProjectionBuffer<\/li>\n<\/ul>\n<p>In this blog post we covered Visual Studio editor architecture, its history, and the various factors that contribute to unintentional memory leaks. Having read this post, you now have the tools to prevent reliability issues from memory leaks, and ensure your extension customers have the most reliable, performant experience possible.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Visual Studio extenders make VS even better by augmenting it with specialized tools, new languages, and workflows. As a Visual Studio extender, you can ensure your extension\u2019s customers have the most performant, reliable experience possible by avoiding common sources of memory leaks, described within this blog post. Background In the early 2010s, a small team [&hellip;]<\/p>\n","protected":false},"author":7127,"featured_media":255385,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1388,155],"tags":[],"class_list":["post-234670","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-extensibility","category-visual-studio"],"acf":[],"blog_post_summary":"<p>Visual Studio extenders make VS even better by augmenting it with specialized tools, new languages, and workflows. As a Visual Studio extender, you can ensure your extension\u2019s customers have the most performant, reliable experience possible by avoiding common sources of memory leaks, described within this blog post. Background In the early 2010s, a small team [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/234670","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/users\/7127"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/comments?post=234670"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/posts\/234670\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media\/255385"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/media?parent=234670"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/categories?post=234670"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/visualstudio\/wp-json\/wp\/v2\/tags?post=234670"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}