Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken)
solution,
new ManagedHotReloadServiceBridge(debuggerService.Value),
sourceTextProvider,
captureMatchingDocuments: [],
captureAllMatchingDocuments: false,
reportDiagnostics: true,
cancellationToken).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,20 @@ public GlassTestsHotReloadService(HostWorkspaceServices services, IManagedHotRel
_debuggerService = debuggerService;
}

public async Task StartSessionAsync(Solution solution, CancellationToken cancellationToken)
#pragma warning disable IDE0060 // Remove unused parameter
public Task StartSessionAsync(Solution solution, CancellationToken cancellationToken)
#pragma warning restore IDE0060
{
var newSessionId = await _encService.StartDebuggingSessionAsync(
var newSessionId = _encService.StartDebuggingSession(
solution,
new ManagedHotReloadServiceBridge(_debuggerService),
NullPdbMatchingSourceTextProvider.Instance,
captureMatchingDocuments: [],
captureAllMatchingDocuments: true,
reportDiagnostics: false,
cancellationToken).ConfigureAwait(false);
reportDiagnostics: false);

Contract.ThrowIfFalse(_sessionId == default, "Session already started");
_sessionId = newSessionId;

return Task.CompletedTask;
}

private DebuggingSessionId GetSessionId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution

// StartDebuggingSession

var debuggingSession = mockEncService.StartDebuggingSessionImpl = (_, _, _, _, _, _) => new DebuggingSessionId(1);
var debuggingSession = mockEncService.StartDebuggingSessionImpl = (_, _, _, _) => new DebuggingSessionId(1);

Assert.False(sessionState.IsSessionActive);
Assert.Empty(sessionState.ApplyChangesDiagnostics);
Expand Down
85 changes: 6 additions & 79 deletions src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue;
/// Encapsulates access to the last committed solution.
/// We don't want to expose the solution directly since access to documents must be gated by out-of-sync checks.
/// </summary>
internal sealed class CommittedSolution
internal sealed class CommittedSolution(DebuggingSession debuggingSession, Solution solution)
{
internal enum DocumentState
{
Expand Down Expand Up @@ -57,12 +57,10 @@ internal enum DocumentState
MatchesBuildOutput = 4
}

private readonly DebuggingSession _debuggingSession;

/// <summary>
/// Current solution snapshot used as a baseline for calculating EnC delta.
/// </summary>
private Solution _solution;
private Solution _solution = solution;

/// <summary>
/// Tracks stale projects. Changes in these projects are ignored and their representation in the <see cref="_solution"/> does not match the binaries on disk.
Expand Down Expand Up @@ -110,13 +108,6 @@ internal enum DocumentState

private readonly object _guard = new();

public CommittedSolution(DebuggingSession debuggingSession, Solution solution, IEnumerable<KeyValuePair<DocumentId, DocumentState>> initialDocumentStates)
{
_solution = solution;
_debuggingSession = debuggingSession;
_documentState.AddRange(initialDocumentStates);
}

// test only
internal void Test_SetDocumentState(DocumentId documentId, DocumentState state)
{
Expand All @@ -135,9 +126,6 @@ internal void Test_SetDocumentState(DocumentId documentId, DocumentState state)
}
}

public bool HasNoChanges(Solution solution)
=> _solution == solution;

public Project? GetProject(ProjectId id)
=> _solution.GetProject(id);

Expand Down Expand Up @@ -341,7 +329,7 @@ public bool ContainsDocument(DocumentId documentId)
var maybePdbHasDocument = TryReadSourceFileChecksumFromPdb(document, out var requiredChecksum, out var checksumAlgorithm);

var maybeMatchingSourceText = (maybePdbHasDocument == true)
? await TryGetMatchingSourceTextAsync(_debuggingSession.SessionLog, sourceText, document.FilePath, currentDocument, _debuggingSession.SourceTextProvider, requiredChecksum, checksumAlgorithm, cancellationToken).ConfigureAwait(false)
? await TryGetMatchingSourceTextAsync(debuggingSession.SessionLog, sourceText, document.FilePath, currentDocument, debuggingSession.SourceTextProvider, requiredChecksum, checksumAlgorithm, cancellationToken).ConfigureAwait(false)
: default;

return (maybeMatchingSourceText, maybePdbHasDocument);
Expand Down Expand Up @@ -373,67 +361,6 @@ public bool ContainsDocument(DocumentId documentId)
return await Task.Run(() => TryGetPdbMatchingSourceTextFromDisk(log, filePath, sourceText.Encoding, requiredChecksum, checksumAlgorithm), cancellationToken).ConfigureAwait(false);
}

internal static async Task<IEnumerable<KeyValuePair<DocumentId, DocumentState>>> GetMatchingDocumentsAsync(
TraceLog log,
IEnumerable<(Project, IEnumerable<CodeAnalysis.DocumentState>)> documentsByProject,
Func<Project, CompilationOutputs> compilationOutputsProvider,
IPdbMatchingSourceTextProvider sourceTextProvider,
CancellationToken cancellationToken)
{
var projectTasks = documentsByProject.Select(async projectDocumentStates =>
{
cancellationToken.ThrowIfCancellationRequested();

var (project, documentStates) = projectDocumentStates;

// Skip projects that do not support Roslyn EnC (e.g. F#, etc).
// Source files of these may not even be captured in the solution snapshot.
if (!project.SupportsEditAndContinue())
{
return [];
}

using var debugInfoReaderProvider = GetMethodDebugInfoReader(log, compilationOutputsProvider(project), project.Name);
if (debugInfoReaderProvider == null)
{
return [];
}

var debugInfoReader = debugInfoReaderProvider.CreateEditAndContinueMethodDebugInfoReader();

var documentTasks = documentStates.Select(async documentState =>
{
cancellationToken.ThrowIfCancellationRequested();

if (documentState.SupportsEditAndContinue())
{
var sourceFilePath = documentState.FilePath;
Contract.ThrowIfNull(sourceFilePath);

// Hydrate the solution snapshot with the content of the file.
// It's important to do this before we start watching for changes so that we have a baseline we can compare future snapshots to.
var sourceText = await documentState.GetTextAsync(cancellationToken).ConfigureAwait(false);

// TODO: https://github.com/dotnet/roslyn/issues/51993
// avoid rereading the file in common case - the workspace should create source texts with the right checksum algorithm and encoding
if (TryReadSourceFileChecksumFromPdb(log, debugInfoReader, sourceFilePath, out var requiredChecksum, out var checksumAlgorithm) == true &&
await TryGetMatchingSourceTextAsync(log, sourceText, sourceFilePath, currentDocument: null, sourceTextProvider, requiredChecksum, checksumAlgorithm, cancellationToken).ConfigureAwait(false) is { HasValue: true, Value: not null })
{
return documentState.Id;
}
}

return null;
});

return await Task.WhenAll(documentTasks).ConfigureAwait(false);
});

var documentIdArrays = await Task.WhenAll(projectTasks).ConfigureAwait(false);

return documentIdArrays.SelectMany(ids => ids.WhereNotNull()).Select(id => KeyValuePair.Create(id, DocumentState.MatchesBuildOutput));
}

private static DebugInformationReaderProvider? GetMethodDebugInfoReader(TraceLog log, CompilationOutputs compilationOutputs, string projectName)
{
DebugInformationReaderProvider? debugInfoReaderProvider;
Expand Down Expand Up @@ -513,8 +440,8 @@ private static bool IsMatchingSourceText(SourceText sourceText, ImmutableArray<b
{
Contract.ThrowIfNull(document.FilePath);

var compilationOutputs = _debuggingSession.GetCompilationOutputs(document.Project);
using var debugInfoReaderProvider = GetMethodDebugInfoReader(_debuggingSession.SessionLog, compilationOutputs, document.Project.Name);
var compilationOutputs = debuggingSession.GetCompilationOutputs(document.Project);
using var debugInfoReaderProvider = GetMethodDebugInfoReader(debuggingSession.SessionLog, compilationOutputs, document.Project.Name);
if (debugInfoReaderProvider == null)
{
// unable to determine whether document is in the PDB
Expand All @@ -524,7 +451,7 @@ private static bool IsMatchingSourceText(SourceText sourceText, ImmutableArray<b
}

var debugInfoReader = debugInfoReaderProvider.CreateEditAndContinueMethodDebugInfoReader();
return TryReadSourceFileChecksumFromPdb(_debuggingSession.SessionLog, debugInfoReader, document.FilePath, out requiredChecksum, out checksumAlgorithm);
return TryReadSourceFileChecksumFromPdb(debuggingSession.SessionLog, debugInfoReader, document.FilePath, out requiredChecksum, out checksumAlgorithm);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ internal DebuggingSession(
IManagedHotReloadService debuggerService,
Func<Project, CompilationOutputs> compilationOutputsProvider,
IPdbMatchingSourceTextProvider sourceTextProvider,
IEnumerable<KeyValuePair<DocumentId, CommittedSolution.DocumentState>> initialDocumentStates,
TraceLog sessionLog,
TraceLog analysisLog,
bool reportDiagnostics)
Expand All @@ -148,7 +147,7 @@ internal DebuggingSession(

Id = id;
DebuggerService = debuggerService;
LastCommittedSolution = new CommittedSolution(this, solution, initialDocumentStates);
LastCommittedSolution = new CommittedSolution(this, solution);

EditSession = new EditSession(
this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,39 +131,36 @@ private ImmutableArray<DebuggingSession> GetDiagnosticReportingDebuggingSessions
}
}

public async ValueTask<DebuggingSessionId> StartDebuggingSessionAsync(
internal static async ValueTask HydrateDocumentsAsync(Solution solution, CancellationToken cancellationToken)
{
var documentTasks =
from project in solution.Projects
where project.SupportsEditAndContinue()
from documentState in GetDocumentStates(project.State)
where documentState.SupportsEditAndContinue()
select documentState.GetTextAsync(cancellationToken).AsTask();

_ = await Task.WhenAll(documentTasks).ConfigureAwait(false);

static IEnumerable<TextDocumentState> GetDocumentStates(ProjectState projectState)
=> ((IEnumerable<TextDocumentState>)projectState.DocumentStates.States.Values).Concat(
projectState.AdditionalDocumentStates.States.Values).Concat(
projectState.AnalyzerConfigDocumentStates.States.Values);
}

public DebuggingSessionId StartDebuggingSession(
Solution solution,
IManagedHotReloadService debuggerService,
IPdbMatchingSourceTextProvider sourceTextProvider,
ImmutableArray<DocumentId> captureMatchingDocuments,
bool captureAllMatchingDocuments,
bool reportDiagnostics,
CancellationToken cancellationToken)
bool reportDiagnostics)
{
try
{
Contract.ThrowIfTrue(captureAllMatchingDocuments && !captureMatchingDocuments.IsEmpty);

IEnumerable<KeyValuePair<DocumentId, CommittedSolution.DocumentState>> initialDocumentStates;

if (captureAllMatchingDocuments || !captureMatchingDocuments.IsEmpty)
{
var documentsByProject = captureAllMatchingDocuments
? solution.Projects.Select(project => (project, project.State.DocumentStates.States.Values))
: GetDocumentStatesGroupedByProject(solution, captureMatchingDocuments);

initialDocumentStates = await CommittedSolution.GetMatchingDocumentsAsync(Log, documentsByProject, _compilationOutputsProvider, sourceTextProvider, cancellationToken).ConfigureAwait(false);
}
else
{
initialDocumentStates = [];
}

// Make sure the solution snapshot has all source-generated documents up-to-date:
solution = solution.WithUpToDateSourceGeneratorDocuments(solution.ProjectIds);

var sessionId = new DebuggingSessionId(Interlocked.Increment(ref s_debuggingSessionId));
var session = new DebuggingSession(sessionId, solution, debuggerService, _compilationOutputsProvider, sourceTextProvider, initialDocumentStates, Log, AnalysisLog, reportDiagnostics);
var session = new DebuggingSession(sessionId, solution, debuggerService, _compilationOutputsProvider, sourceTextProvider, Log, AnalysisLog, reportDiagnostics);

lock (_debuggingSessions)
{
Expand All @@ -174,19 +171,12 @@ public async ValueTask<DebuggingSessionId> StartDebuggingSessionAsync(
return sessionId;

}
catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken))
catch (Exception ex) when (FatalError.ReportAndPropagate(ex))
{
throw ExceptionUtilities.Unreachable();
}
}

private static IEnumerable<(Project, IEnumerable<DocumentState>)> GetDocumentStatesGroupedByProject(Solution solution, ImmutableArray<DocumentId> documentIds)
=> from documentId in documentIds
where solution.ContainsDocument(documentId)
group documentId by documentId.ProjectId into projectDocumentIds
let project = solution.GetRequiredProject(projectDocumentIds.Key)
select (project, from documentId in projectDocumentIds select project.State.DocumentStates.GetState(documentId));

public void EndDebuggingSession(DebuggingSessionId sessionId)
{
DebuggingSession? debuggingSession;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal interface IEditAndContinueService
void CommitSolutionUpdate(DebuggingSessionId sessionId);
void DiscardSolutionUpdate(DebuggingSessionId sessionId);

ValueTask<DebuggingSessionId> StartDebuggingSessionAsync(Solution solution, IManagedHotReloadService debuggerService, IPdbMatchingSourceTextProvider sourceTextProvider, ImmutableArray<DocumentId> captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken);
DebuggingSessionId StartDebuggingSession(Solution solution, IManagedHotReloadService debuggerService, IPdbMatchingSourceTextProvider sourceTextProvider, bool reportDiagnostics);
void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState);
void EndDebuggingSession(DebuggingSessionId sessionId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal interface ICallback
ValueTask CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken);
ValueTask DiscardSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken);

ValueTask<DebuggingSessionId> StartDebuggingSessionAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, ImmutableArray<DocumentId> captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken);
ValueTask<DebuggingSessionId> StartDebuggingSessionAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, bool reportDiagnostics, CancellationToken cancellationToken);

/// <summary>
/// Returns ids of documents for which diagnostics need to be refreshed in-proc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,13 @@ private IEditAndContinueService GetLocalService()
Solution solution,
IManagedHotReloadService debuggerService,
IPdbMatchingSourceTextProvider sourceTextProvider,
ImmutableArray<DocumentId> captureMatchingDocuments,
bool captureAllMatchingDocuments,
bool reportDiagnostics,
CancellationToken cancellationToken)
{
var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false);
if (client == null)
{
var sessionId = await GetLocalService().StartDebuggingSessionAsync(solution, debuggerService, sourceTextProvider, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false);
var sessionId = GetLocalService().StartDebuggingSession(solution, debuggerService, sourceTextProvider, reportDiagnostics);
return new RemoteDebuggingSessionProxy(solution.Services, LocalConnection.Instance, sessionId);
}

Expand All @@ -139,7 +137,7 @@ private IEditAndContinueService GetLocalService()

var sessionIdOpt = await connection.TryInvokeAsync(
solution,
async (service, solutionInfo, callbackId, cancellationToken) => await service.StartDebuggingSessionAsync(solutionInfo, callbackId, captureMatchingDocuments, captureAllMatchingDocuments, reportDiagnostics, cancellationToken).ConfigureAwait(false),
async (service, solutionInfo, callbackId, cancellationToken) => await service.StartDebuggingSessionAsync(solutionInfo, callbackId, reportDiagnostics, cancellationToken).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);

if (sessionIdOpt.HasValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ public readonly struct Update(
/// <param name="capabilities">Array of capabilities retrieved from the runtime to dictate supported rude edits.</param>
public async Task StartSessionAsync(Solution solution, ImmutableArray<string> capabilities, CancellationToken cancellationToken)
{
var newSessionId = await _encService.StartDebuggingSessionAsync(
// Hydrate the solution snapshot with file content.
// It's important to do this before we start watching for changes so that we have a baseline we can compare future snapshots to.
await EditAndContinueService.HydrateDocumentsAsync(solution, cancellationToken).ConfigureAwait(false);

var newSessionId = _encService.StartDebuggingSession(
solution,
new DebuggerService(capabilities),
NullPdbMatchingSourceTextProvider.Instance,
captureMatchingDocuments: [],
captureAllMatchingDocuments: true,
reportDiagnostics: false,
cancellationToken).ConfigureAwait(false);
reportDiagnostics: false);

Contract.ThrowIfFalse(_sessionId == default, "Session already started");
_sessionId = newSessionId;
Expand Down
Loading
Loading