-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Ensure that when EnC finishes that it disposes the OOP connection it is holding onto #73465
Changes from all commits
5ad975f
fe28ea8
73083b0
a1d7845
066aa57
f61a54b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Microsoft.CodeAnalysis.Remote; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.EditAndContinue; | ||
|
||
|
@@ -130,25 +131,28 @@ private IEditAndContinueService GetLocalService() | |
if (client == null) | ||
{ | ||
var sessionId = await GetLocalService().StartDebuggingSessionAsync(solution, debuggerService, sourceTextProvider, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false); | ||
return new RemoteDebuggingSessionProxy(solution.Services, LocalConnection.Instance, sessionId); | ||
using var disposable = new ReferenceCountedDisposable<IDisposable>(LocalConnection.Instance); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. initial ref count will be 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to review this one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is potentially problematic, and doesn't follow the general rules for RCD. Resources can't be "partially" owned by RCD, so this should be rewritten to make it trivially clear to the reader that the value passed to the RCD instance is a new instance that was created as part of creating the RCD. |
||
// RemoteDebuggingSessionProxy will add its own refcount to 'disposable'. | ||
return new RemoteDebuggingSessionProxy(solution.Services, disposable, sessionId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will increase ref count to '2', before the using lowers it back down to 1. |
||
} | ||
|
||
// need to keep the providers alive until the session ends: | ||
var connection = client.CreateConnection<IRemoteEditAndContinueService>( | ||
callbackTarget: new DebuggingSessionCallback(debuggerService, sourceTextProvider)); | ||
// | ||
// Wrap with a ref-counted-disposable here to ensure we clean this up properly if we don't transfer ownership to the RemoteDebuggingSessionProxy. | ||
using var connection = new ReferenceCountedDisposable<RemoteServiceConnection<IRemoteEditAndContinueService>>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ref count of connection will start at 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this code ensures that if we don't properly do all the following steps that we dispose of hte connection. ew don't want connections leaked if we move to a world where we hold off on disposing the RemoteHostClient while there are still connections open to the OOP server (see #73467) |
||
client.CreateConnection<IRemoteEditAndContinueService>( | ||
callbackTarget: new DebuggingSessionCallback(debuggerService, sourceTextProvider))); | ||
|
||
var sessionIdOpt = await connection.TryInvokeAsync( | ||
var sessionIdOpt = await connection.Target.TryInvokeAsync( | ||
solution, | ||
async (service, solutionInfo, callbackId, cancellationToken) => await service.StartDebuggingSessionAsync(solutionInfo, callbackId, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false), | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
if (sessionIdOpt.HasValue) | ||
{ | ||
return new RemoteDebuggingSessionProxy(solution.Services, connection, sessionIdOpt.Value); | ||
} | ||
if (!sessionIdOpt.HasValue) | ||
return null; | ||
|
||
connection.Dispose(); | ||
return null; | ||
// Pass the connection to the RemoteDebuggingSessionProxy which will add its own refcount to it. | ||
return new RemoteDebuggingSessionProxy(solution.Services, connection, sessionIdOpt.Value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it will increase to '2' inside RemoteDebuggingSessionProxy, then drop back to '1' when the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is the standard ownership transfer strategy for RCD |
||
} | ||
|
||
public async ValueTask<ImmutableArray<DiagnosticData>> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why passing in IReferenceCountedDisposable instead of the actual connection value like before? Once the constructor is reached it owns the object and is responsible for disposal. Indeed, you are throwing an unreachable exception if the underlying connection is not available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReferenceCountedDisposal can't be variant. And it wraps a specific type (not an IDisposable). The IRefCountedDisposable exists for this pattern where we want to pass a base interface type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is sometimes the case, but sometimes not. It also doesn't work at all for asynchronous code that uses resources. RCD provides stronger/clearer semantics in the face of ownership transfers and asynchronous/concurrent operations.
Callers are not expected to pass in an RCD which is not owned/alive by the caller. Unreachable seems like an appropriate exception here.