Skip to content

Commit 1059c22

Browse files
committed
Force services to wait for intialization before using the language server
1 parent af3cffa commit 1059c22

15 files changed

+246
-30
lines changed

src/PowerShellEditorServices/Extensions/Api/EditorContextService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using Microsoft.PowerShell.EditorServices.Handlers;
8+
using Microsoft.PowerShell.EditorServices.Server;
89
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
910

1011
namespace Microsoft.PowerShell.EditorServices.Extensions.Services
@@ -82,10 +83,10 @@ public interface IEditorContextService
8283

8384
internal class EditorContextService : IEditorContextService
8485
{
85-
private readonly ILanguageServerFacade _languageServer;
86+
private readonly ISafeLanguageServer _languageServer;
8687

8788
internal EditorContextService(
88-
ILanguageServerFacade languageServer)
89+
ISafeLanguageServer languageServer)
8990
{
9091
_languageServer = languageServer;
9192
}

src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq.Expressions;
66
using System.Reflection;
77
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.PowerShell.EditorServices.Server;
89
using Microsoft.PowerShell.EditorServices.Services;
910
using Microsoft.PowerShell.EditorServices.Utility;
1011
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
@@ -41,12 +42,13 @@ public class EditorExtensionServiceProvider
4142
internal EditorExtensionServiceProvider(IServiceProvider serviceProvider)
4243
{
4344
_serviceProvider = serviceProvider;
44-
LanguageServer = new LanguageServerService(_serviceProvider.GetService<ILanguageServerFacade>());
45+
var languageServer = _serviceProvider.GetService<ISafeLanguageServer>();
46+
LanguageServer = new LanguageServerService(languageServer);
4547
//DocumentSymbols = new DocumentSymbolService(_serviceProvider.GetService<SymbolsService>());
4648
ExtensionCommands = new ExtensionCommandService(_serviceProvider.GetService<ExtensionService>());
4749
Workspace = new WorkspaceService(_serviceProvider.GetService<InternalServices.WorkspaceService>());
48-
EditorContext = new EditorContextService(_serviceProvider.GetService<ILanguageServerFacade>());
49-
EditorUI = new EditorUIService(_serviceProvider.GetService<ILanguageServerFacade>());
50+
EditorContext = new EditorContextService(languageServer);
51+
EditorUI = new EditorUIService(languageServer);
5052
}
5153

5254
/// <summary>

src/PowerShellEditorServices/Extensions/Api/EditorUIService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.PowerShell.EditorServices.Server;
910
using Microsoft.PowerShell.EditorServices.Services.PowerShellContext;
1011
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
1112

@@ -101,9 +102,9 @@ internal class EditorUIService : IEditorUIService
101102
{
102103
private static string[] s_choiceResponseLabelSeparators = new[] { ", " };
103104

104-
private readonly ILanguageServerFacade _languageServer;
105+
private readonly ISafeLanguageServer _languageServer;
105106

106-
public EditorUIService(ILanguageServerFacade languageServer)
107+
public EditorUIService(ISafeLanguageServer languageServer)
107108
{
108109
_languageServer = languageServer;
109110
}

src/PowerShellEditorServices/Extensions/Api/LanguageServerService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using MediatR;
5+
using Microsoft.PowerShell.EditorServices.Server;
56
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
67
using System.Threading;
78
using System.Threading.Tasks;
@@ -64,9 +65,9 @@ public interface ILanguageServerService
6465

6566
internal class LanguageServerService : ILanguageServerService
6667
{
67-
private readonly ILanguageServerFacade _languageServer;
68+
private readonly ISafeLanguageServer _languageServer;
6869

69-
internal LanguageServerService(ILanguageServerFacade languageServer)
70+
internal LanguageServerService(ISafeLanguageServer languageServer)
7071
{
7172
_languageServer = languageServer;
7273
}

src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public PsesDebugServer CreateDebugServerForTempSession(Stream inputStream, Strea
123123
.AddSingleton<ILanguageServerFacade>(provider => null)
124124
.AddPsesLanguageServices(hostStartupInfo)
125125
// For a Temp session, there is no LanguageServer so just set it to null
126+
// TODO: Why are we doing this twice?
126127
.AddSingleton(
127128
typeof(ILanguageServerFacade),
128129
_ => null)

src/PowerShellEditorServices/Server/PsesLanguageServer.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public async Task StartAsync()
9393
.WithHandler<PsesSemanticTokensHandler>()
9494
.OnInitialize(
9595
// TODO: Either fix or ignore "method lacks 'await'" warning.
96-
async (languageServer, request, cancellationToken) =>
96+
(languageServer, request, cancellationToken) =>
9797
{
9898
var serviceProvider = languageServer.Services;
9999
var workspaceService = serviceProvider.GetService<WorkspaceService>();
@@ -113,6 +113,12 @@ public async Task StartAsync()
113113
break;
114114
}
115115
}
116+
117+
// Allow services to send requests and notifications now
118+
var safeLanguageServer = (SafeLanguageServer)serviceProvider.GetService<ISafeLanguageServer>();
119+
safeLanguageServer.SetReady();
120+
121+
return Task.CompletedTask;
116122
});
117123
}).ConfigureAwait(false);
118124

src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ public static IServiceCollection AddPsesLanguageServices(
1717
this IServiceCollection collection,
1818
HostStartupInfo hostStartupInfo)
1919
{
20-
return collection.AddSingleton<WorkspaceService>()
20+
return collection
21+
.AddSingleton<ISafeLanguageServer, SafeLanguageServer>()
22+
.AddSingleton<WorkspaceService>()
2123
.AddSingleton<SymbolsService>()
2224
.AddSingleton<ConfigurationService>()
2325
.AddSingleton<PowerShellContextService>(
2426
(provider) =>
2527
PowerShellContextService.Create(
2628
provider.GetService<ILoggerFactory>(),
27-
provider.GetService<OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade>(),
29+
provider.GetService<ISafeLanguageServer>(),
2830
hostStartupInfo))
2931
.AddSingleton<TemplateService>()
3032
.AddSingleton<EditorOperationsService>()
@@ -34,7 +36,7 @@ public static IServiceCollection AddPsesLanguageServices(
3436
{
3537
var extensionService = new ExtensionService(
3638
provider.GetService<PowerShellContextService>(),
37-
provider.GetService<OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade>());
39+
provider.GetService<ISafeLanguageServer>());
3840
extensionService.InitializeAsync(
3941
serviceProvider: provider,
4042
editorOperations: provider.GetService<EditorOperationsService>())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using MediatR;
7+
using Newtonsoft.Json.Linq;
8+
using OmniSharp.Extensions.JsonRpc;
9+
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
10+
11+
namespace Microsoft.PowerShell.EditorServices.Server
12+
{
13+
internal interface ISafeLanguageServer : IResponseRouter
14+
{
15+
ITextDocumentLanguageServer TextDocument { get; }
16+
17+
IClientLanguageServer Client { get; }
18+
19+
IGeneralLanguageServer General { get; }
20+
21+
IWindowLanguageServer Window { get; }
22+
23+
IWorkspaceLanguageServer Workspace { get; }
24+
}
25+
26+
internal class SafeLanguageServer : ISafeLanguageServer
27+
{
28+
private readonly ILanguageServerFacade _languageServer;
29+
30+
private readonly AsyncLatch _serverReady;
31+
32+
public ITextDocumentLanguageServer TextDocument
33+
{
34+
get
35+
{
36+
_serverReady.Wait();
37+
return _languageServer.TextDocument;
38+
}
39+
}
40+
41+
public IClientLanguageServer Client
42+
{
43+
get
44+
{
45+
_serverReady.Wait();
46+
return _languageServer.Client;
47+
}
48+
}
49+
50+
public IGeneralLanguageServer General
51+
{
52+
get
53+
{
54+
_serverReady.Wait();
55+
return _languageServer.General;
56+
}
57+
}
58+
59+
public IWindowLanguageServer Window
60+
{
61+
get
62+
{
63+
_serverReady.Wait();
64+
return _languageServer.Window;
65+
}
66+
}
67+
68+
public IWorkspaceLanguageServer Workspace
69+
{
70+
get
71+
{
72+
_serverReady.Wait();
73+
return _languageServer.Workspace;
74+
}
75+
}
76+
77+
public void SetReady()
78+
{
79+
_serverReady.Open();
80+
}
81+
82+
public SafeLanguageServer(ILanguageServerFacade languageServer)
83+
{
84+
_languageServer = languageServer;
85+
_serverReady = new AsyncLatch();
86+
}
87+
88+
public void SendNotification(string method)
89+
{
90+
_serverReady.Wait();
91+
_languageServer.SendNotification(method);
92+
}
93+
94+
public void SendNotification<T>(string method, T @params)
95+
{
96+
_serverReady.Wait();
97+
_languageServer.SendNotification(method, @params);
98+
}
99+
100+
public void SendNotification(IRequest request)
101+
{
102+
_serverReady.Wait();
103+
_languageServer.SendNotification(request);
104+
}
105+
106+
public IResponseRouterReturns SendRequest<T>(string method, T @params)
107+
{
108+
_serverReady.Wait();
109+
return _languageServer.SendRequest(method, @params);
110+
}
111+
112+
public IResponseRouterReturns SendRequest(string method)
113+
{
114+
_serverReady.Wait();
115+
return _languageServer.SendRequest(method);
116+
}
117+
118+
public async Task<TResponse> SendRequest<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken)
119+
{
120+
await _serverReady.WaitAsync();
121+
return await _languageServer.SendRequest(request, cancellationToken);
122+
}
123+
124+
public bool TryGetRequest(long id, out string method, out TaskCompletionSource<JToken> pendingTask)
125+
{
126+
if (!_serverReady.TryWait())
127+
{
128+
method = default;
129+
pendingTask = default;
130+
return false;
131+
}
132+
133+
return _languageServer.TryGetRequest(id, out method, out pendingTask);
134+
}
135+
136+
private class AsyncLatch
137+
{
138+
private readonly ManualResetEvent _resetEvent;
139+
140+
private readonly Task _awaitLatchOpened;
141+
142+
private volatile bool _isOpen;
143+
144+
public AsyncLatch()
145+
{
146+
_resetEvent = new ManualResetEvent(/* start in blocking state */ initialState: false);
147+
_awaitLatchOpened = CreateLatchOpenedAwaiterTask(_resetEvent);
148+
_isOpen = false;
149+
}
150+
151+
public void Wait() => _resetEvent.WaitOne();
152+
153+
public Task WaitAsync() => _awaitLatchOpened;
154+
155+
public bool TryWait() => _isOpen;
156+
157+
public void Open()
158+
{
159+
// Unblocks the reset event
160+
_resetEvent.Set();
161+
_isOpen = true;
162+
}
163+
164+
private static Task CreateLatchOpenedAwaiterTask(WaitHandle handle)
165+
{
166+
var tcs = new TaskCompletionSource<object>();
167+
168+
// In a dedicated waiter thread, wait for the reset event and then set the task completion source
169+
// to turn the reset event wait into a task.
170+
// From https://stackoverflow.com/a/18766131.
171+
RegisteredWaitHandle registration = ThreadPool.RegisterWaitForSingleObject(handle, (state, timedOut) =>
172+
{
173+
((TaskCompletionSource<object>)state).TrySetResult(result: null);
174+
}, tcs, Timeout.Infinite, executeOnlyOnce: true);
175+
176+
// Register an action to unregister the registration when the reset event task has completed.
177+
EnsureWaitHandleUnregistered(tcs.Task, registration);
178+
179+
return tcs.Task;
180+
}
181+
182+
private static async Task EnsureWaitHandleUnregistered(Task task, RegisteredWaitHandle handle)
183+
{
184+
try
185+
{
186+
await task;
187+
}
188+
finally
189+
{
190+
handle.Unregister(waitObject: null);
191+
}
192+
}
193+
}
194+
}
195+
}

src/PowerShellEditorServices/Services/Analysis/AnalysisService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Threading;
1212
using System.Threading.Tasks;
1313
using Microsoft.Extensions.Logging;
14+
using Microsoft.PowerShell.EditorServices.Server;
1415
using Microsoft.PowerShell.EditorServices.Services.Analysis;
1516
using Microsoft.PowerShell.EditorServices.Services.Configuration;
1617
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
@@ -82,7 +83,7 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
8283

8384
private readonly ILogger _logger;
8485

85-
private readonly ILanguageServerFacade _languageServer;
86+
private readonly ISafeLanguageServer _languageServer;
8687

8788
private readonly ConfigurationService _configurationService;
8889

@@ -107,7 +108,7 @@ internal static string GetUniqueIdFromDiagnostic(Diagnostic diagnostic)
107108
/// <param name="workspaceService">The workspace service for file handling within a workspace.</param>
108109
public AnalysisService(
109110
ILoggerFactory loggerFactory,
110-
ILanguageServerFacade languageServer,
111+
ISafeLanguageServer languageServer,
111112
ConfigurationService configurationService,
112113
WorkspaceService workspaceService)
113114
{

src/PowerShellEditorServices/Services/PowerShellContext/EditorOperationsService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.PowerShell.EditorServices.Extensions;
55
using Microsoft.PowerShell.EditorServices.Handlers;
6+
using Microsoft.PowerShell.EditorServices.Server;
67
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
78
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
89
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
@@ -17,12 +18,12 @@ internal class EditorOperationsService : IEditorOperations
1718

1819
private readonly WorkspaceService _workspaceService;
1920
private readonly PowerShellContextService _powerShellContextService;
20-
private readonly ILanguageServerFacade _languageServer;
21+
private readonly ISafeLanguageServer _languageServer;
2122

2223
public EditorOperationsService(
2324
WorkspaceService workspaceService,
2425
PowerShellContextService powerShellContextService,
25-
ILanguageServerFacade languageServer)
26+
ISafeLanguageServer languageServer)
2627
{
2728
_workspaceService = workspaceService;
2829
_powerShellContextService = powerShellContextService;

0 commit comments

Comments
 (0)