Skip to content

Commit c58e948

Browse files
Don't spin in GetRootPathAsync waiting for initialize
Instead of spinning, use a TaskCompletionSource to track initialization and an AsyncLazy for the root path.
1 parent 8e085c4 commit c58e948

File tree

2 files changed

+39
-32
lines changed

2 files changed

+39
-32
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CapabilitiesManager.cs

+38-31
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,44 @@
44
using System;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Razor.Threading;
78
using Microsoft.CodeAnalysis.Razor;
89
using Microsoft.CodeAnalysis.Razor.Protocol;
910
using Microsoft.CodeAnalysis.Razor.Workspaces;
1011
using Microsoft.CommonLanguageServerProtocol.Framework;
1112
using Microsoft.VisualStudio.LanguageServer.Protocol;
1213
using Microsoft.VisualStudio.RpcContracts.Settings;
14+
using Microsoft.VisualStudio.Threading;
1315

1416
namespace Microsoft.AspNetCore.Razor.LanguageServer;
1517

16-
internal sealed class CapabilitiesManager(ILspServices lspServices)
17-
: IInitializeManager<InitializeParams, InitializeResult>, IClientCapabilitiesService, IWorkspaceRootPathProvider
18+
internal sealed class CapabilitiesManager : IInitializeManager<InitializeParams, InitializeResult>, IClientCapabilitiesService, IWorkspaceRootPathProvider
1819
{
19-
private readonly ILspServices _lspServices = lspServices;
20-
private InitializeParams? _initializeParams;
20+
private readonly ILspServices _lspServices;
21+
private readonly TaskCompletionSource<InitializeParams> _initializeParamsTaskSource;
22+
private readonly AsyncLazy<string> _lazyRootPath;
2123

22-
public bool HasInitialized => _initializeParams is not null;
24+
public bool HasInitialized => _initializeParamsTaskSource.Task.IsCompleted;
2325

2426
public bool CanGetClientCapabilities => HasInitialized;
2527

2628
public VSInternalClientCapabilities ClientCapabilities => GetInitializeParams().Capabilities.ToVSInternalClientCapabilities();
2729

30+
public CapabilitiesManager(ILspServices lspServices)
31+
{
32+
_lspServices = lspServices;
33+
34+
_initializeParamsTaskSource = new();
35+
36+
#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed
37+
_lazyRootPath = new(ComputeRootPathAsync);
38+
#pragma warning restore VSTHRD012
39+
}
40+
2841
public InitializeParams GetInitializeParams()
29-
=> _initializeParams ??
30-
throw new InvalidOperationException($"{nameof(GetInitializeParams)} was called before '{Methods.InitializeName}'");
42+
{
43+
return _initializeParamsTaskSource.Task.VerifyCompleted();
44+
}
3145

3246
public InitializeResult GetInitializeResult()
3347
{
@@ -51,39 +65,32 @@ public InitializeResult GetInitializeResult()
5165

5266
public void SetInitializeParams(InitializeParams request)
5367
{
54-
_initializeParams = request ?? throw new ArgumentNullException(nameof(request));
68+
if (_initializeParamsTaskSource.Task.IsCompleted)
69+
{
70+
throw new InvalidOperationException($"{nameof(SetInitializeParams)} already called.");
71+
}
72+
73+
_initializeParamsTaskSource.TrySetResult(request);
5574
}
5675

57-
public ValueTask<string> GetRootPathAsync(CancellationToken cancellationToken)
76+
private async Task<string> ComputeRootPathAsync()
5877
{
59-
return HasInitialized
60-
? new(GetRootPath())
61-
: new(GetRootPathCoreAsync(this, cancellationToken));
78+
#pragma warning disable VSTHRD003 // Avoid awaiting foreign Tasks
79+
var initializeParams = await _initializeParamsTaskSource.Task.ConfigureAwaitRunInline();
80+
#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks
6281

63-
static async Task<string> GetRootPathCoreAsync(CapabilitiesManager manager, CancellationToken cancellationToken)
82+
if (initializeParams.RootUri is Uri rootUri)
6483
{
65-
while (!manager.HasInitialized)
66-
{
67-
cancellationToken.ThrowIfCancellationRequested();
68-
await Task.Delay(millisecondsDelay: 1, cancellationToken).ConfigureAwait(false);
69-
}
70-
71-
return manager.GetRootPath();
84+
return rootUri.GetAbsoluteOrUNCPath();
7285
}
73-
}
7486

75-
private string GetRootPath()
76-
{
77-
var initializeParams = GetInitializeParams();
87+
// RootUri was added in LSP3, fall back to RootPath
7888

79-
if (initializeParams.RootUri is null)
80-
{
8189
#pragma warning disable CS0618 // Type or member is obsolete
82-
// RootUri was added in LSP3, fallback to RootPath
83-
return initializeParams.RootPath.AssumeNotNull();
90+
return initializeParams.RootPath.AssumeNotNull();
8491
#pragma warning restore CS0618 // Type or member is obsolete
85-
}
86-
87-
return initializeParams.RootUri.GetAbsoluteOrUNCPath();
8892
}
93+
94+
public Task<string> GetRootPathAsync(CancellationToken cancellationToken)
95+
=> _lazyRootPath.GetValueAsync(cancellationToken);
8996
}

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/IWorkspaceRootPathProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer;
88

99
internal interface IWorkspaceRootPathProvider
1010
{
11-
ValueTask<string> GetRootPathAsync(CancellationToken cancellationToken);
11+
Task<string> GetRootPathAsync(CancellationToken cancellationToken);
1212
}

0 commit comments

Comments
 (0)