Skip to content

Commit 01058bc

Browse files
DustinCampbelldavidwengier
authored andcommitted
Fix issue where RemoteServiceInvoker never initializes
The first time that a remote service call is made by RemoteServiceInvoker, it calls into Roslyn OOP to initialize itself. However, the CancellationToken that's passed for initialization is the same one that is passed by the original service caller. If that token is cancelled by the caller during initialization, RemoteServiceInvoker will get stuck and never actually make a remote service call because the initialization task will forever be in a cancelled state. The fix for this issue is to make RemoteServiceInvoker disposable, and add a CancellationTokenSource that is cancelled when the invoker itself is dispose. Then, pass that CancellationTokenSource's token to initialization.
1 parent b950e5b commit 01058bc

File tree

1 file changed

+18
-6
lines changed

1 file changed

+18
-6
lines changed

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Remote/RemoteServiceInvoker.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ internal sealed class RemoteServiceInvoker(
2626
IClientCapabilitiesService clientCapabilitiesService,
2727
ISemanticTokensLegendService semanticTokensLegendService,
2828
ITelemetryReporter telemetryReporter,
29-
ILoggerFactory loggerFactory)
30-
: IRemoteServiceInvoker
29+
ILoggerFactory loggerFactory) : IRemoteServiceInvoker, IDisposable
3130
{
3231
private readonly IWorkspaceProvider _workspaceProvider = workspaceProvider;
3332
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;
@@ -36,11 +35,24 @@ internal sealed class RemoteServiceInvoker(
3635
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
3736
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<RemoteServiceInvoker>();
3837

38+
private readonly CancellationTokenSource _disposeTokenSource = new();
39+
3940
private readonly object _gate = new();
4041
private ValueTask<bool>? _isInitializedTask;
4142
private ValueTask<bool>? _isLSPInitializedTask;
4243
private bool _fullyInitialized;
4344

45+
public void Dispose()
46+
{
47+
if (_disposeTokenSource.IsCancellationRequested)
48+
{
49+
return;
50+
}
51+
52+
_disposeTokenSource.Cancel();
53+
_disposeTokenSource.Dispose();
54+
}
55+
4456
public async ValueTask<TResult?> TryInvokeAsync<TService, TResult>(
4557
Solution solution,
4658
Func<TService, RazorPinnedSolutionInfoWrapper, CancellationToken, ValueTask<TResult>> invocation,
@@ -94,7 +106,7 @@ internal sealed class RemoteServiceInvoker(
94106
return null;
95107
}
96108

97-
await InitializeRemoteClientAsync(remoteClient, cancellationToken).ConfigureAwait(false);
109+
await InitializeRemoteClientAsync(remoteClient).ConfigureAwait(false);
98110

99111
return remoteClient;
100112
}
@@ -117,7 +129,7 @@ internal sealed class RemoteServiceInvoker(
117129
cancellationToken).ConfigureAwait(false);
118130
}
119131

120-
private async Task InitializeRemoteClientAsync(RazorRemoteHostClient remoteClient, CancellationToken cancellationToken)
132+
private async Task InitializeRemoteClientAsync(RazorRemoteHostClient remoteClient)
121133
{
122134
if (_fullyInitialized)
123135
{
@@ -142,7 +154,7 @@ private async Task InitializeRemoteClientAsync(RazorRemoteHostClient remoteClien
142154

143155
_isInitializedTask = remoteClient.TryInvokeAsync<IRemoteClientInitializationService>(
144156
(s, ct) => s.InitializeAsync(initParams, ct),
145-
cancellationToken);
157+
_disposeTokenSource.Token);
146158
}
147159
}
148160

@@ -164,7 +176,7 @@ private async Task InitializeRemoteClientAsync(RazorRemoteHostClient remoteClien
164176

165177
_isLSPInitializedTask = remoteClient.TryInvokeAsync<IRemoteClientInitializationService>(
166178
(s, ct) => s.InitializeLSPAsync(initParams, ct),
167-
cancellationToken);
179+
_disposeTokenSource.Token);
168180
}
169181
}
170182

0 commit comments

Comments
 (0)