11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
33
4+ using System ;
5+ using System . Collections . Concurrent ;
46using System . Threading ;
57using System . Threading . Tasks ;
68using MediatR ;
1012
1113namespace 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