Skip to content

Commit 8065672

Browse files
Fixed case where dynamic registration would fail if Register is called before Initialize (#348)
1 parent 09d9466 commit 8065672

File tree

5 files changed

+104
-47
lines changed

5 files changed

+104
-47
lines changed

src/Server/DefaultLanguageServerFacade.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Reactive.Subjects;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using DryIoc;
@@ -25,7 +26,7 @@ internal class DefaultLanguageServerFacade : LanguageProtocolProxy, ILanguageSer
2526
private readonly Lazy<ISupportedCapabilities> _supportedCapabilities;
2627
private readonly TextDocumentIdentifiers _textDocumentIdentifiers;
2728
private readonly IInsanceHasStarted _instancesHasStarted;
28-
private readonly TaskCompletionSource<Unit> _hasStarted;
29+
private readonly AsyncSubject<System.Reactive.Unit> _hasStarted;
2930

3031
public DefaultLanguageServerFacade(
3132
IResponseRouter requestRouter,
@@ -54,7 +55,7 @@ IInsanceHasStarted instancesHasStarted
5455
_supportedCapabilities = supportedCapabilities;
5556
_textDocumentIdentifiers = textDocumentIdentifiers;
5657
_instancesHasStarted = instancesHasStarted;
57-
_hasStarted = new TaskCompletionSource<Unit>();
58+
_hasStarted = new AsyncSubject<System.Reactive.Unit>();
5859
}
5960

6061
public ITextDocumentLanguageServer TextDocument => _textDocument.Value;
@@ -74,12 +75,13 @@ public IDisposable Register(Action<ILanguageServerRegistry> registryAction)
7475
LanguageServerHelpers.InitHandlers(ResolverContext.Resolve<ILanguageServer>(), result);
7576
}
7677

77-
return LanguageServerHelpers.RegisterHandlers(_hasStarted.Task, Client, _workDoneManager.Value, _supportedCapabilities.Value, result);
78+
return LanguageServerHelpers.RegisterHandlers(_hasStarted, Client, _workDoneManager.Value, _supportedCapabilities.Value, result);
7879
}
7980

8081
Task IOnLanguageServerStarted.OnStarted(ILanguageServer client, CancellationToken cancellationToken)
8182
{
82-
_hasStarted.TrySetResult(Unit.Value);
83+
_hasStarted.OnNext(System.Reactive.Unit.Default);
84+
_hasStarted.OnCompleted();
8385
return Task.CompletedTask;
8486
}
8587
}

src/Server/LanguageServer.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -332,15 +332,15 @@ async Task<InitializeResult> IRequestHandler<InternalInitializeParams, Initializ
332332
var windowCapabilities = ClientSettings.Capabilities.Window ??= new WindowClientCapabilities();
333333
WorkDoneManager.Initialized(windowCapabilities);
334334

335-
{
335+
_disposable.Add(
336336
LanguageServerHelpers.RegisterHandlers(
337-
_initializingTask,
337+
_initializeComplete.Select(z => System.Reactive.Unit.Default),
338338
Client,
339339
WorkDoneManager,
340340
_supportedCapabilities,
341341
_collection
342-
);
343-
}
342+
)
343+
);
344344

345345
await LanguageProtocolEventingHelper.Run(
346346
_initializeDelegates,
@@ -508,7 +508,8 @@ public IDisposable Register(Action<ILanguageServerRegistry> registryAction)
508508
LanguageServerHelpers.InitHandlers(this, result);
509509
}
510510

511-
return LanguageServerHelpers.RegisterHandlers(_initializingTask, Client, WorkDoneManager, _supportedCapabilities, result);
511+
return LanguageServerHelpers.RegisterHandlers(_initializeComplete.Select(z => System.Reactive.Unit.Default), Client, WorkDoneManager, _supportedCapabilities, result);
512+
// return LanguageServerHelpers.RegisterHandlers(_initializingTask ?? _initializeComplete.ToTask(), Client, WorkDoneManager, _supportedCapabilities, result);
512513
}
513514

514515
object IServiceProvider.GetService(Type serviceType) => Services.GetService(serviceType);

src/Server/LanguageServerHelpers.cs

+64-37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Reactive;
45
using System.Reactive.Disposables;
56
using System.Reactive.Linq;
67
using System.Reactive.Threading.Tasks;
@@ -51,14 +52,14 @@ internal static void InitHandlers(ILanguageServer client, CompositeDisposable re
5152
}
5253

5354
internal static IDisposable RegisterHandlers(
54-
Task initializeComplete,
55+
IObservable<Unit> initializeComplete,
5556
IClientLanguageServer client,
5657
IServerWorkDoneManager serverWorkDoneManager,
5758
ISupportedCapabilities supportedCapabilities,
5859
IEnumerable<ILspHandlerDescriptor> collection
5960
)
6061
{
61-
var registrations = new List<Registration>();
62+
var descriptors = new List<ILspHandlerDescriptor>();
6263
foreach (var descriptor in collection)
6364
{
6465
if (descriptor is LspHandlerDescriptor lspHandlerDescriptor &&
@@ -68,51 +69,77 @@ IEnumerable<ILspHandlerDescriptor> collection
6869
continue;
6970
}
7071

71-
if (descriptor.HasCapability && supportedCapabilities.AllowsDynamicRegistration(descriptor.CapabilityType))
72-
{
73-
if (descriptor.RegistrationOptions is IWorkDoneProgressOptions wdpo)
74-
{
75-
wdpo.WorkDoneProgress = serverWorkDoneManager.IsSupported;
76-
}
77-
78-
registrations.Add(
79-
new Registration {
80-
Id = descriptor.Id.ToString(),
81-
Method = descriptor.Method,
82-
RegisterOptions = descriptor.RegistrationOptions
83-
}
84-
);
85-
}
72+
descriptors.Add(descriptor);
8673
}
8774

88-
// Fire and forget
89-
DynamicallyRegisterHandlers(client, initializeComplete, registrations.ToArray()).ToObservable().Subscribe();
90-
91-
return Disposable.Create(
92-
() => {
93-
client.UnregisterCapability(
94-
new UnregistrationParams {
95-
Unregisterations = registrations.ToArray()
96-
}
97-
).ToObservable().Subscribe();
98-
}
99-
);
75+
return DynamicallyRegisterHandlers(client, initializeComplete, serverWorkDoneManager, supportedCapabilities, descriptors);
10076
}
10177

102-
internal static async Task DynamicallyRegisterHandlers(IClientLanguageServer client, Task initializeComplete, Registration[] registrations)
78+
internal static IDisposable DynamicallyRegisterHandlers(
79+
IClientLanguageServer client,
80+
IObservable<Unit> initializeComplete,
81+
IServerWorkDoneManager serverWorkDoneManager,
82+
ISupportedCapabilities supportedCapabilities,
83+
IReadOnlyList<ILspHandlerDescriptor> descriptors
84+
)
10385
{
104-
if (registrations.Length == 0)
105-
return; // No dynamic registrations supported by client.
86+
if (descriptors.Count == 0)
87+
return Disposable.Empty; // No dynamic registrations supported by client.
88+
89+
var disposable = new CompositeDisposable();
10690

107-
var @params = new RegistrationParams { Registrations = registrations };
91+
var result = initializeComplete
92+
.LastOrDefaultAsync()
93+
.Select(
94+
_ => {
95+
var registrations = new List<Registration>();
96+
foreach (var descriptor in descriptors)
97+
{
98+
if (descriptor.HasCapability && supportedCapabilities.AllowsDynamicRegistration(descriptor.CapabilityType))
99+
{
100+
if (descriptor.RegistrationOptions is IWorkDoneProgressOptions wdpo)
101+
{
102+
wdpo.WorkDoneProgress = serverWorkDoneManager.IsSupported;
103+
}
108104

109-
await initializeComplete;
105+
registrations.Add(
106+
new Registration {
107+
Id = descriptor.Id.ToString(),
108+
Method = descriptor.Method,
109+
RegisterOptions = descriptor.RegistrationOptions
110+
}
111+
);
112+
}
113+
}
110114

111-
await client.RegisterCapability(@params);
115+
return registrations;
116+
}
117+
)
118+
.SelectMany(
119+
registrations => Observable.FromAsync(ct => client.RegisterCapability(new RegistrationParams { Registrations = registrations }, ct)), (a, b) => a
120+
)
121+
.Aggregate((z, b) => z)
122+
.Subscribe(
123+
registrations => {
124+
disposable.Add(
125+
Disposable.Create(
126+
() => {
127+
client.UnregisterCapability(
128+
new UnregistrationParams {
129+
Unregisterations = registrations
130+
}
131+
).ToObservable().Subscribe();
132+
}
133+
)
134+
);
135+
}
136+
);
137+
disposable.Add(result);
138+
return disposable;
112139
}
113140

114141
internal static IDisposable RegisterHandlers(
115-
Task initializeComplete,
142+
IObservable<Unit> initializeComplete,
116143
IClientLanguageServer client,
117144
IServerWorkDoneManager serverWorkDoneManager,
118145
ISupportedCapabilities supportedCapabilities,
@@ -145,4 +172,4 @@ IDisposable handlerDisposable
145172
return cd;
146173
}
147174
}
148-
}
175+
}

test/Lsp.Tests/Integration/InitializationTests.cs

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Collections.Generic;
2+
using System.Reactive.Linq;
3+
using System.Reactive.Threading.Tasks;
24
using System.Threading;
35
using System.Threading.Tasks;
46
using FluentAssertions;
@@ -42,11 +44,34 @@ public async Task Facades_should_be_resolvable()
4244
server.Services.GetService<ILanguageServerFacade>().Should().NotBeNull();
4345
// This ensures that the facade made it.
4446
var response = await client.RequestCodeLens(new CodeLensParams(), CancellationToken);
47+
response.Should().NotBeNull();
48+
}
49+
50+
[Fact]
51+
public async Task Should_Be_Able_To_Register_Before_Initialize()
52+
{
53+
var (client, server) = Create(options => options.EnableDynamicRegistration().EnableAllCapabilities(), options => {});
54+
55+
server.Register(
56+
r => {
57+
r.AddHandler<CodeLensHandlerA>();
58+
}
59+
);
60+
61+
await Observable.FromAsync(client.Initialize)
62+
.ForkJoin(Observable.FromAsync(server.Initialize), (a, b) => ( client, server ))
63+
.ToTask(CancellationToken);
64+
65+
client.Services.GetService<ILanguageClientFacade>().Should().NotBeNull();
66+
server.Services.GetService<ILanguageServerFacade>().Should().NotBeNull();
67+
// This ensures that the facade made it.
68+
var response = await client.RequestCodeLens(new CodeLensParams(), CancellationToken);
69+
response.Should().NotBeNull();
4570
}
4671

4772
class CodeLensHandlerA : CodeLensHandler
4873
{
49-
public CodeLensHandlerA(ILanguageServerFacade languageServerFacade) : base(new CodeLensRegistrationOptions())
74+
public CodeLensHandlerA(ILanguageServerFacade languageServerFacade) : base(new CodeLensRegistrationOptions() { })
5075
{
5176
languageServerFacade.Should().NotBeNull();
5277
}

test/Lsp.Tests/Integration/WorkspaceFolderTests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class WorkspaceFolderTests : LanguageProtocolTestBase
2626
{
2727
public WorkspaceFolderTests(ITestOutputHelper outputHelper) : base(
2828
new JsonRpcTestOptions().ConfigureForXUnit(outputHelper, LogEventLevel.Verbose)
29+
.WithTimeout(TimeSpan.FromMilliseconds(1200))
30+
.WithWaitTime(TimeSpan.FromMilliseconds(600))
2931
)
3032
{
3133
}

0 commit comments

Comments
 (0)