Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
69c1c61
Enhance Hot Reload session management and improve cancellation handling
LittleLittleCloud Nov 13, 2025
73c97ce
Implement cancellation token workaround in InitializeApplicationAsync…
LittleLittleCloud Nov 13, 2025
455ebc0
Add comment to clarify workaround for DefaultHotreloadClient cancella…
LittleLittleCloud Nov 13, 2025
3bfcf91
Refactor cancellation handling in InitializeApplicationAsync to impro…
LittleLittleCloud Nov 13, 2025
62b9dec
Refactor Hot Reload session management to improve cancellation handli…
LittleLittleCloud Nov 14, 2025
d25dce4
Refactor session disposal logic to ensure proper cleanup and stop ses…
LittleLittleCloud Nov 14, 2025
900c034
Add projectThreadingService parameter to CreateInstance method for im…
LittleLittleCloud Nov 14, 2025
c23b2ea
Add comment to clarify semaphore usage for race condition prevention …
LittleLittleCloud Nov 14, 2025
3feae6a
Refactor ProjectHotReloadSessionManager to enhance thread safety and …
LittleLittleCloud Nov 14, 2025
114a304
Add graceful exit handling in Dispose method for HotReload session ma…
LittleLittleCloud Nov 14, 2025
72a80f5
Remove unused Application Insights namespace from ProjectHotReloadSes…
LittleLittleCloud Nov 14, 2025
6fab82a
Enhance StopProjectAsync method to ensure proper process termination …
LittleLittleCloud Nov 14, 2025
9b43d9e
Refactor StopProjectAsync method to improve process termination handl…
LittleLittleCloud Nov 14, 2025
6a3a74c
Update src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSys…
LittleLittleCloud Nov 14, 2025
7744b01
Add tracking issue comment for cancellation token handling in Initial…
LittleLittleCloud Nov 14, 2025
34a4497
Change DebugTrace method to instance method for improved access to cl…
LittleLittleCloud Nov 14, 2025
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
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using System.Diagnostics;
using System.Xml.Linq;
using Microsoft.VisualStudio.Debugger.Contracts.HotReload;
using Microsoft.VisualStudio.Debugger.Interop;
using Microsoft.VisualStudio.HotReload.Components.DeltaApplier;
Expand Down Expand Up @@ -201,24 +202,54 @@ async Task ActivateSessionInternalAsync()
{
if (_pendingSessionState is { Session: IProjectHotReloadSession session })
{
Process process = Process.GetProcessById((int)vsDebugTargetProcessInfo.dwProcessId);
_pendingSessionState.LaunchedProcess = launchedProcess;
_pendingSessionState.Process = process;
Process? process = null;
try
{
_pendingSessionState.LaunchedProcess = launchedProcess;
process = Process.GetProcessById((int)vsDebugTargetProcessInfo.dwProcessId);
_pendingSessionState.Process = process;
}
catch (ArgumentException)
{
// process might have been exited in some cases.
// in that case, we early return without starting hotreload session
// one way to mimic this is to hit control + C as fast as you can once hit F5/Control + F5
_pendingSessionState = null;
return;
}

CancellationTokenSource cts = new CancellationTokenSource();
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated
process.Exited += (sender, e) =>
{
DebugTrace("Process exited");
cts.Cancel();
};

if (!process.HasExited)
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated
{
process.Exited += (sender, e) =>
{
_threadingService.ExecuteSynchronously(() => session.StopSessionAsync(CancellationToken.None));
};
process.EnableRaisingEvents = true;
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated

await _pendingSessionState.Session.StartSessionAsync(CancellationToken.None);
lock (_activeSessionStates)
await _pendingSessionState.Session.StartSessionAsync(cts.Token);
if (cts.IsCancellationRequested)
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated
{
_activeSessionStates.Add(_pendingSessionState);
// Process exited before we could start the session
DebugTrace("Hot Reload session not started because process exited.");
_pendingSessionState = null;
return;
}
else
{
DebugTrace("Hot Reload session started.");
cts.Token.Register(() =>
{
DebugTrace("Cancellation requested, stopping session.");
_threadingService.RunAndForget(() => session.StopSessionAsync(default), _unconfiguredProject);
});
lock (_activeSessionStates)
{
_activeSessionStates.Add(_pendingSessionState);
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated
}
await _projectHotReloadNotificationService.Value.SetHotReloadStateAsync(isInHotReload: true);
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated
}
await _projectHotReloadNotificationService.Value.SetHotReloadStateAsync(isInHotReload: true);
}

_pendingSessionState = null;
Expand Down Expand Up @@ -301,4 +332,18 @@ public async Task<bool> StopProjectAsync(CancellationToken cancellationToken)
return true;
}
}

private void DebugTrace(string message)
{
#if DEBUG
var projectName = _unconfiguredProject.GetProjectName();
_hotReloadDiagnosticOutputService.Value.WriteLine(
new HotReloadLogMessage(
HotReloadVerbosity.Detailed,
message,
projectName,
errorLevel: HotReloadDiagnosticErrorLevel.Info),
CancellationToken.None);
#endif
}
Comment thread
LittleLittleCloud marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ public async ValueTask<ImmutableArray<string>> GetCapabilitiesAsync(Cancellation

public async ValueTask InitializeApplicationAsync(CancellationToken cancellationToken)
{
_ = await client.GetUpdateCapabilitiesAsync(cancellationToken);
// Not all clients respond correctly to cancellation tokens.
// For example, `DefaultHotreloadClient.GetUpdateCapabilitiesAsync(ct)`doesn't listen to the passed ct.
// Since DefaultHotreloadClient is defined in a source package, it can't be modified directly from project-system.
// Work around this by creating a TaskCompletionSource that completes when the token is cancelled.
TaskCompletionSource tcs = new TaskCompletionSource();
cancellationToken.Register(() => tcs.TrySetResult());
_ = Task.WaitAny(client.GetUpdateCapabilitiesAsync(cancellationToken), tcs.Task);
Comment thread
LittleLittleCloud marked this conversation as resolved.
Outdated

// TODO: apply initial updates?
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2571676
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public async Task StartSessionAsync(CancellationToken cancellationToken)

await _hotReloadAgentManagerClient.Value.AgentStartedAsync(this, flags, processInfo, runningProjectInfo, cancellationToken);

WriteToOutputWindow(Resources.HotReloadStartSession, default);
WriteToOutputWindow(Resources.HotReloadStartSession, cancellationToken);

if (await GetOrCreateDeltaApplierAsync(cancellationToken) is IDeltaApplierInternal applierInternal)
{
Expand Down
Loading