Skip to content

Commit 1d3e9da

Browse files
committed
Implement a notification queue so sends don't block
1 parent 1059c22 commit 1d3e9da

File tree

1 file changed

+49
-11
lines changed

1 file changed

+49
-11
lines changed

src/PowerShellEditorServices/Server/SafeLanguageServer.cs

+49-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System;
5+
using System.Collections.Concurrent;
46
using System.Threading;
57
using System.Threading.Tasks;
68
using MediatR;
@@ -10,6 +12,10 @@
1012

1113
namespace Microsoft.PowerShell.EditorServices.Server
1214
{
15+
/// <summary>
16+
/// An LSP server that ensures that client/server initialization has occurred
17+
/// before sending or receiving any other notifications or requests.
18+
/// </summary>
1319
internal interface ISafeLanguageServer : IResponseRouter
1420
{
1521
ITextDocumentLanguageServer TextDocument { get; }
@@ -23,12 +29,25 @@ internal interface ISafeLanguageServer : IResponseRouter
2329
IWorkspaceLanguageServer Workspace { get; }
2430
}
2531

32+
/// <summary>
33+
/// An implementation around Omnisharp's LSP server to ensure
34+
/// messages are not sent before initialization has completed.
35+
/// </summary>
2636
internal class SafeLanguageServer : ISafeLanguageServer
2737
{
2838
private readonly ILanguageServerFacade _languageServer;
2939

3040
private readonly AsyncLatch _serverReady;
3141

42+
private readonly ConcurrentQueue<Action> _notificationQueue;
43+
44+
public SafeLanguageServer(ILanguageServerFacade languageServer)
45+
{
46+
_languageServer = languageServer;
47+
_serverReady = new AsyncLatch();
48+
_notificationQueue = new ConcurrentQueue<Action>();
49+
}
50+
3251
public ITextDocumentLanguageServer TextDocument
3352
{
3453
get
@@ -77,29 +96,44 @@ public IWorkspaceLanguageServer Workspace
7796
public void SetReady()
7897
{
7998
_serverReady.Open();
80-
}
8199

82-
public SafeLanguageServer(ILanguageServerFacade languageServer)
83-
{
84-
_languageServer = languageServer;
85-
_serverReady = new AsyncLatch();
100+
// Send any pending notifications now
101+
while (_notificationQueue.TryDequeue(out Action notifcationAction))
102+
{
103+
notifcationAction();
104+
}
86105
}
87106

88107
public void SendNotification(string method)
89108
{
90-
_serverReady.Wait();
109+
if (!_serverReady.IsReady)
110+
{
111+
_notificationQueue.Enqueue(() => _languageServer.SendNotification(method));
112+
return;
113+
}
114+
91115
_languageServer.SendNotification(method);
92116
}
93117

94118
public void SendNotification<T>(string method, T @params)
95119
{
96-
_serverReady.Wait();
120+
if (!_serverReady.IsReady)
121+
{
122+
_notificationQueue.Enqueue(() => _languageServer.SendNotification(method, @params));
123+
return;
124+
}
125+
97126
_languageServer.SendNotification(method, @params);
98127
}
99128

100129
public void SendNotification(IRequest request)
101130
{
102-
_serverReady.Wait();
131+
if (!_serverReady.IsReady)
132+
{
133+
_notificationQueue.Enqueue(() => _languageServer.SendNotification(request));
134+
return;
135+
}
136+
103137
_languageServer.SendNotification(request);
104138
}
105139

@@ -123,7 +157,7 @@ public async Task<TResponse> SendRequest<TResponse>(IRequest<TResponse> request,
123157

124158
public bool TryGetRequest(long id, out string method, out TaskCompletionSource<JToken> pendingTask)
125159
{
126-
if (!_serverReady.TryWait())
160+
if (!_serverReady.IsReady)
127161
{
128162
method = default;
129163
pendingTask = default;
@@ -133,6 +167,10 @@ public bool TryGetRequest(long id, out string method, out TaskCompletionSource<J
133167
return _languageServer.TryGetRequest(id, out method, out pendingTask);
134168
}
135169

170+
/// <summary>
171+
/// Implements a latch (a monotonic manual reset event that starts in the blocking state)
172+
/// that can be waited on synchronously or asynchronously without wasting thread resources.
173+
/// </summary>
136174
private class AsyncLatch
137175
{
138176
private readonly ManualResetEvent _resetEvent;
@@ -148,12 +186,12 @@ public AsyncLatch()
148186
_isOpen = false;
149187
}
150188

189+
public bool IsReady => _isOpen;
190+
151191
public void Wait() => _resetEvent.WaitOne();
152192

153193
public Task WaitAsync() => _awaitLatchOpened;
154194

155-
public bool TryWait() => _isOpen;
156-
157195
public void Open()
158196
{
159197
// Unblocks the reset event

0 commit comments

Comments
 (0)