1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
+ using System ;
5
+ using System . Collections . Concurrent ;
4
6
using System . Threading ;
5
7
using System . Threading . Tasks ;
6
8
using MediatR ;
10
12
11
13
namespace Microsoft . PowerShell . EditorServices . Server
12
14
{
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>
13
19
internal interface ISafeLanguageServer : IResponseRouter
14
20
{
15
21
ITextDocumentLanguageServer TextDocument { get ; }
@@ -23,12 +29,25 @@ internal interface ISafeLanguageServer : IResponseRouter
23
29
IWorkspaceLanguageServer Workspace { get ; }
24
30
}
25
31
32
+ /// <summary>
33
+ /// An implementation around Omnisharp's LSP server to ensure
34
+ /// messages are not sent before initialization has completed.
35
+ /// </summary>
26
36
internal class SafeLanguageServer : ISafeLanguageServer
27
37
{
28
38
private readonly ILanguageServerFacade _languageServer ;
29
39
30
40
private readonly AsyncLatch _serverReady ;
31
41
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
+
32
51
public ITextDocumentLanguageServer TextDocument
33
52
{
34
53
get
@@ -77,29 +96,44 @@ public IWorkspaceLanguageServer Workspace
77
96
public void SetReady ( )
78
97
{
79
98
_serverReady . Open ( ) ;
80
- }
81
99
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
+ }
86
105
}
87
106
88
107
public void SendNotification ( string method )
89
108
{
90
- _serverReady . Wait ( ) ;
109
+ if ( ! _serverReady . IsReady )
110
+ {
111
+ _notificationQueue . Enqueue ( ( ) => _languageServer . SendNotification ( method ) ) ;
112
+ return ;
113
+ }
114
+
91
115
_languageServer . SendNotification ( method ) ;
92
116
}
93
117
94
118
public void SendNotification < T > ( string method , T @params )
95
119
{
96
- _serverReady . Wait ( ) ;
120
+ if ( ! _serverReady . IsReady )
121
+ {
122
+ _notificationQueue . Enqueue ( ( ) => _languageServer . SendNotification ( method , @params ) ) ;
123
+ return ;
124
+ }
125
+
97
126
_languageServer . SendNotification ( method , @params ) ;
98
127
}
99
128
100
129
public void SendNotification ( IRequest request )
101
130
{
102
- _serverReady . Wait ( ) ;
131
+ if ( ! _serverReady . IsReady )
132
+ {
133
+ _notificationQueue . Enqueue ( ( ) => _languageServer . SendNotification ( request ) ) ;
134
+ return ;
135
+ }
136
+
103
137
_languageServer . SendNotification ( request ) ;
104
138
}
105
139
@@ -123,7 +157,7 @@ public async Task<TResponse> SendRequest<TResponse>(IRequest<TResponse> request,
123
157
124
158
public bool TryGetRequest ( long id , out string method , out TaskCompletionSource < JToken > pendingTask )
125
159
{
126
- if ( ! _serverReady . TryWait ( ) )
160
+ if ( ! _serverReady . IsReady )
127
161
{
128
162
method = default ;
129
163
pendingTask = default ;
@@ -133,6 +167,10 @@ public bool TryGetRequest(long id, out string method, out TaskCompletionSource<J
133
167
return _languageServer . TryGetRequest ( id , out method , out pendingTask ) ;
134
168
}
135
169
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>
136
174
private class AsyncLatch
137
175
{
138
176
private readonly ManualResetEvent _resetEvent ;
@@ -148,12 +186,12 @@ public AsyncLatch()
148
186
_isOpen = false ;
149
187
}
150
188
189
+ public bool IsReady => _isOpen ;
190
+
151
191
public void Wait ( ) => _resetEvent . WaitOne ( ) ;
152
192
153
193
public Task WaitAsync ( ) => _awaitLatchOpened ;
154
194
155
- public bool TryWait ( ) => _isOpen ;
156
-
157
195
public void Open ( )
158
196
{
159
197
// Unblocks the reset event
0 commit comments