Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to a callback for cleaning up resources instead of passing in an explicit IDisposable. #62373

Merged
merged 8 commits into from
Jul 7, 2022

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Jul 5, 2022

Fixes #61778

PRs #61630 and #61693 added support to the compiler metadata components to allow them to hold onto an object (specifically an IDisposable) during their lifetime that they would then dispose when teh actual metadata component was disposed.

This was very beneficial for hte IDE as the IDE often uses APIs that allocate and pin unmanaged memory which should be cleaned up when no longer needed. Prior to these APIs, tehre was lots of codein the IDE (including several CWTs) that were used to ensure this, though with high complexity. The ability to have hte metadata itself just take over this responsibility greatly simplified things.

However, during API review, it was felt that hte API was a bit too complex and transparent. It requires two parameters to be passed in, and it makes it requires that the main parameter be an IDisposable object. This is really not necessary as all we actually need to support this concept is just a way to know when teh metadata itself is being disposed. What is done with that knowledge doesn't need to be encoded into the contract here.

To that end, this PR changes the API in question to be a much simpler one where instead of passing in two low-level values into the API, an opaque Action is passed in instead. When the metadata is disposed, that Action will be invoked, allowing the consumer to determine what should happen in that case.

The end result is functionally the same, but it's a simpler and cleaner API choice.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review July 5, 2022 16:44
@CyrusNajmabadi CyrusNajmabadi requested review from a team as code owners July 5, 2022 16:44
@@ -76,7 +76,7 @@ override sealed Microsoft.CodeAnalysis.CompilationOptions.GetHashCode() -> int
override sealed Microsoft.CodeAnalysis.Diagnostic.Equals(object? obj) -> bool
*REMOVED*static Microsoft.CodeAnalysis.SyntaxNodeExtensions.ReplaceSyntax<TRoot>(this TRoot! root, System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.SyntaxNode!>! nodes, System.Func<Microsoft.CodeAnalysis.SyntaxNode!, Microsoft.CodeAnalysis.SyntaxNode!, Microsoft.CodeAnalysis.SyntaxNode!>! computeReplacementNode, System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.SyntaxToken>! tokens, System.Func<Microsoft.CodeAnalysis.SyntaxToken, Microsoft.CodeAnalysis.SyntaxToken, Microsoft.CodeAnalysis.SyntaxToken>! computeReplacementToken, System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.SyntaxTrivia>! trivia, System.Func<Microsoft.CodeAnalysis.SyntaxTrivia, Microsoft.CodeAnalysis.SyntaxTrivia, Microsoft.CodeAnalysis.SyntaxTrivia>! computeReplacementTrivia) -> TRoot!
static Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation.Create(System.Collections.Immutable.ImmutableArray<byte> compressedSlotMap, System.Collections.Immutable.ImmutableArray<byte> compressedLambdaMap, System.Collections.Immutable.ImmutableArray<byte> compressedStateMachineStateMap) -> Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation
static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(System.IntPtr metadata, int size, System.IDisposable! owner, bool disposeOwner) -> Microsoft.CodeAnalysis.ModuleMetadata!
static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(System.IntPtr metadata, int size, System.Action! onDispose) -> Microsoft.CodeAnalysis.ModuleMetadata!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this new public API. Makes me happy :).

/// Whether or not to call _onDispose when this is disposed. The reason that this is controlled by a flag is
/// that if we make a copy of this modulemetadata, we still want to keep the Action alive in it (as it may be
/// keeping alive underlying object this metadata needs). However, the copy of the metadata should not call the
/// action as only the <see cref="Metadata.IsImageOwner"/> metadata is responsible for that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems dangerous to me. Who says that the copy has a shorter lifetime than the original? Feels like we need some sort of reference count wrapper that is decremented on dispose of the containing reference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree ti's dangerous. BUt it's already the established semantics of ModuleMetadta. e.g. prior to this change if you did this:

ModuleMetadata mm = ... 
mmCopy = mm.Copy();
mm.Dispose();

then mmCopy was now in an 'no longer usable' state (same with any other copies created). I'm genuinely surprised we went with those semantics, but it's been the way of this type since the beginning, so it's just part of the nature of these and hte entire stack is written to be ok with that.

I would totally be ok with moving to a refcounted system. And, importantly, it would not change this new API at all. All it would do would be to allow us to remove those comments and make things safer. As such, i would prefer it be a separate orthogonal API :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm genuinely surprised we went with those semantics,

For performance reasons. Entirely safe semantics would be slow since every read from the metadata would need to check whether the memory is disposed or not before it reads and do these two operations atomically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who says that the copy has a shorter lifetime than the original?

It's responsibility of the owner of the metadata to guarantee that.

Copy link
Member

@tmat tmat Jul 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be possible to make metadata reads safe using ReadOnlySpan<T> and ReadOnlyMemory<T>. But that would require a lot of changes throughout SRM and also might not be sufficiently efficient on .NET Framework.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess making the copies ref-counted would add some value. It wouldn't make it entirely safe but perhaps safer.

Copy link
Member

@tmat tmat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@CyrusNajmabadi CyrusNajmabadi requested a review from 333fred July 5, 2022 19:02
@CyrusNajmabadi
Copy link
Member Author

@dotnet/roslyn-compiler for eyes. Thanks!

@CyrusNajmabadi
Copy link
Member Author

@333fred @dotnet/roslyn-compiler ptal. thanks!

/// Disposed. Is controlled by the <c>leaveOpen</c> flag in <see cref="CreateFromStream(Stream, bool)"/>, or
/// the <see cref="PEStreamOptions.LeaveOpen"/> flag in <see cref="CreateFromStream(Stream, PEStreamOptions)"/>.
/// Whether or not to call _onDispose when this is disposed. The reason that this is controlled by a flag is
/// that if we make a copy of this modulemetadata, we still want to keep the Action alive in it (as it may be
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modulemetadata

Minor: "ModuleMetadata"

@CyrusNajmabadi CyrusNajmabadi requested review from cston and tmat July 7, 2022 19:00
@CyrusNajmabadi CyrusNajmabadi enabled auto-merge (squash) July 7, 2022 19:09
@CyrusNajmabadi CyrusNajmabadi merged commit 44bf662 into dotnet:main Jul 7, 2022
@ghost ghost added this to the Next milestone Jul 7, 2022
@CyrusNajmabadi CyrusNajmabadi deleted the disposeAction branch July 7, 2022 20:59
333fred added a commit to 333fred/roslyn that referenced this pull request Jul 7, 2022
* upstream/main: (62 commits)
  Prevent assert from being hit (dotnet#62366)
  Don't offer '??=' for pointer types (dotnet#62476)
  Integrate generator times into /reportAnalyzer (dotnet#61661)
  Switch to a callback for cleaning up resources instead of passing in an explicit IDisposable. (dotnet#62373)
  Filter trees to only those containing global-usings or attributes prior to analyzing them. (dotnet#62444)
  Update PublishData.json
  Complete 'file' support for `SyntaxGenerator` (dotnet#62413)
  Apply changes directly to text buffer (dotnet#62337)
  Remove LangVer check from extended nameof binding (dotnet#62339)
  Fixed shared project file error (dotnet#62466)
  Handle new error codes
  Use MSBuid generated property for package path
  Exclude BCL libraries from Roslyn vsix
  Bump the integration test timeouts a bit
  Skip the balanced switch dispatch optimization for patterns on floating-point inputs (dotnet#62322)
  Test helpers shouldn't escape quotes by default (dotnet#62321)
  Reuse prior TableEntry values in the increment NodeTable builder if possible. (dotnet#62320)
  Install 3.1 runtime for SBOM tool
  Generate VS SBOM during official build.
  Minor refactoring in 'required' handling for override completion (dotnet#62422)
  ...
@allisonchou allisonchou modified the milestones: Next, 17.4 P1 Jul 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update ModuleMetadata to hold onto an object it can keep-alive.
5 participants