Skip to content

Conversation

@dibarbet
Copy link
Member

@dibarbet dibarbet commented Nov 14, 2025

Followup to the canonical misc changes. This PR has two parts

Move server initiated restore entirely to server

Previously restores initiated by the server had to call out to the client to setup the correct UI, which then called back to the server to run the restore, all using custom LSP requests. This wasn't ideal as it meant extra custom client side code and an extra server -> client -> server loop.

Instead, we can utilize the standard LSP server initiated work done progress API to display progress from the server on the client side. This means server initiated restores no longer need any custom client code, and the server can choose when to display a UI without a client code change.

This did require a bit of complex code to handle either the server or client cancelling the request.

Additionally, we still have custom requests for manually initiated restores from the client.

Hide restore progress for canonical files

Utilizing the above change, I modified the server behavior to not show any progress when restoring the canonical file. This is an internal server operation and does not need to be displayed client side (based on feedback it only causes confusion).

CLient side change - dotnet/vscode-csharp#8780

/// Manages server initiated work done progress reporting to the client.
/// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverInitiatedProgress
/// </summary>
class WorkDoneProgressManager(IClientLanguageServerManager clientLanguageServerManager) : ILspService
Copy link
Member Author

@dibarbet dibarbet Nov 14, 2025

Choose a reason for hiding this comment

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

the most interesting part of the change. happy to take feedback here on more ergonomic ways to implement.

here we have to handle sending the initial request to create the progress, sending reports, completing the progress. Additionally, this needs to support either being cancelled by the server or client.


// Tell the client to end the work done progress if the server cancels the request.
// This needs to not observe the linked cancellation token as it will already be cancelled.
serverCancellationToken.Register(() =>
Copy link
Member Author

Choose a reason for hiding this comment

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

we observe only the server cancellation token here. if the client cancels, there is no need to send this notification as the client already knows it is cancelled.

@dibarbet dibarbet marked this pull request as ready for review November 14, 2025 20:49
@dibarbet dibarbet requested a review from a team as a code owner November 14, 2025 20:49
@RikkiGibson RikkiGibson self-requested a review November 14, 2025 21:07
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

A little too complex to sign off just yet. Probably want a quick chat to understand more.

Contract.ThrowIfNull(context.Solution);
using var progress = BufferedProgress.Create(request.PartialResultToken);

progress.Report(new RestorePartialResult(LanguageServerResources.Restore, LanguageServerResources.Restore_started));
Copy link
Member

Choose a reason for hiding this comment

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

Why no progress reporting?

Copy link
Member Author

Choose a reason for hiding this comment

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

Progress is reported via the work done progress mechanism below instead.

Background

This part of the handler handles manual restore requests from the client. Previously we were using custom client side code to make a streaming request to the server and show progress on the client.

Now the client makes a normal request, and the server enables progress reporting via work done progress.

using var progress = await workDoneProgressManager.CreateWorkDoneProgressAsync(reportProgressToClient: enableProgressReporting, cancellationToken);
// Ensure we're observing cancellation token from the work done progress (to allow client cancellation).
cancellationToken = progress.CancellationToken;
return await RestoreCoreAsync(pathsToRestore, progress, dotnetCliHelper, logger, cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

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

Is progress.CancellationToken already linked to cancellationToken? Or we don't want to cancel RestoreCoreAsync, when the original cancellationToken parameter value is canceled?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is progress.CancellationToken already linked to cancellationToken

Yeah, progress.CancellationToken is linked to the cancellation token and client side token.


// Perform restore and mock up project restore client handler
// Perform restore
ProcessUtilities.Run("dotnet", $"restore --project {projectPath}");
Copy link
Member

Choose a reason for hiding this comment

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

Do you feel the new WorkDoneProgress code is exercised well by the existing tests?

Copy link
Member Author

Choose a reason for hiding this comment

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

Restore wasn't really exercised by the tests at all really. We likely need integration tests for this that actually do the full load / restore loop.

@dibarbet dibarbet enabled auto-merge (squash) November 19, 2025 00:07
@dibarbet dibarbet merged commit 7a165d5 into dotnet:main Nov 19, 2025
25 of 26 checks passed
@dibarbet dibarbet deleted the restore_server_side branch November 19, 2025 01:13
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Nov 19, 2025
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.

3 participants