Skip to content

Commit 0488a37

Browse files
committedJun 2, 2017
Establish new channel connection model using server listeners
This change refactors our existing channel model to move all connection logic outside of the ChannelBase implementations so that the language and debug client/service pairs can be simplified. This change also allows us to remove a long-standing hack in our Host unit tests which added an artifical delay to give the channel and MessageDispatcher time to get established.
1 parent 59db8dd commit 0488a37

18 files changed

+391
-277
lines changed
 

‎src/PowerShellEditorServices.Host/EditorServicesHost.cs

+63-50
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6-
using Microsoft.PowerShell.EditorServices.Console;
6+
using Microsoft.PowerShell.EditorServices.Extensions;
7+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
78
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
89
using Microsoft.PowerShell.EditorServices.Protocol.Server;
910
using Microsoft.PowerShell.EditorServices.Session;
@@ -12,10 +13,8 @@
1213
using System.Collections.Generic;
1314
using System.Diagnostics;
1415
using System.Management.Automation.Runspaces;
15-
using System.Management.Automation.Host;
1616
using System.Reflection;
17-
using System.Threading;
18-
using Microsoft.PowerShell.EditorServices.Extensions;
17+
using System.Threading.Tasks;
1918

2019
namespace Microsoft.PowerShell.EditorServices.Host
2120
{
@@ -36,12 +35,18 @@ public class EditorServicesHost
3635

3736
private bool enableConsoleRepl;
3837
private HostDetails hostDetails;
38+
private ProfilePaths profilePaths;
3939
private string bundledModulesPath;
4040
private DebugAdapter debugAdapter;
4141
private EditorSession editorSession;
4242
private HashSet<string> featureFlags;
4343
private LanguageServer languageServer;
4444

45+
private TcpSocketServerListener languageServiceListener;
46+
private TcpSocketServerListener debugServiceListener;
47+
48+
private TaskCompletionSource<bool> serverCompletedTask;
49+
4550
#endregion
4651

4752
#region Properties
@@ -152,25 +157,40 @@ public void StartLogging(string logFilePath, LogLevel logLevel)
152157
/// <param name="languageServicePort">The port number for the language service.</param>
153158
/// <param name="profilePaths">The object containing the profile paths to load for this session.</param>
154159
public void StartLanguageService(int languageServicePort, ProfilePaths profilePaths)
160+
{
161+
this.profilePaths = profilePaths;
162+
163+
this.languageServiceListener =
164+
new TcpSocketServerListener(
165+
MessageProtocolType.LanguageServer,
166+
languageServicePort);
167+
168+
this.languageServiceListener.ClientConnect += this.OnLanguageServiceClientConnect;
169+
this.languageServiceListener.Start();
170+
171+
Logger.Write(
172+
LogLevel.Normal,
173+
string.Format(
174+
"Language service started, listening on port {0}",
175+
languageServicePort));
176+
}
177+
178+
private async void OnLanguageServiceClientConnect(
179+
object sender,
180+
TcpSocketServerChannel serverChannel)
155181
{
156182
this.editorSession =
157183
CreateSession(
158184
this.hostDetails,
159-
profilePaths,
185+
this.profilePaths,
160186
this.enableConsoleRepl);
161187

162188
this.languageServer =
163189
new LanguageServer(
164190
this.editorSession,
165-
new TcpSocketServerChannel(languageServicePort));
166-
167-
this.languageServer.Start().Wait();
191+
serverChannel);
168192

169-
Logger.Write(
170-
LogLevel.Normal,
171-
string.Format(
172-
"Language service started, listening on port {0}",
173-
languageServicePort));
193+
await this.languageServer.Start();
174194
}
175195

176196
/// <summary>
@@ -182,12 +202,29 @@ public void StartDebugService(
182202
ProfilePaths profilePaths,
183203
bool useExistingSession)
184204
{
185-
if (this.enableConsoleRepl && useExistingSession)
205+
this.debugServiceListener =
206+
new TcpSocketServerListener(
207+
MessageProtocolType.LanguageServer,
208+
debugServicePort);
209+
210+
this.debugServiceListener.ClientConnect += OnDebugServiceClientConnect;
211+
this.debugServiceListener.Start();
212+
213+
Logger.Write(
214+
LogLevel.Normal,
215+
string.Format(
216+
"Debug service started, listening on port {0}",
217+
debugServicePort));
218+
}
219+
220+
private async void OnDebugServiceClientConnect(object sender, TcpSocketServerChannel serverChannel)
221+
{
222+
if (this.enableConsoleRepl)
186223
{
187224
this.debugAdapter =
188225
new DebugAdapter(
189226
this.editorSession,
190-
new TcpSocketServerChannel(debugServicePort),
227+
serverChannel,
191228
false);
192229
}
193230
else
@@ -196,42 +233,26 @@ public void StartDebugService(
196233
this.CreateDebugSession(
197234
this.hostDetails,
198235
profilePaths,
199-
this.languageServer.EditorOperations);
236+
this.languageServer?.EditorOperations);
200237

201238
this.debugAdapter =
202239
new DebugAdapter(
203240
debugSession,
204-
new TcpSocketServerChannel(debugServicePort),
241+
serverChannel,
205242
true);
206243
}
207244

208245
this.debugAdapter.SessionEnded +=
209246
(obj, args) =>
210247
{
211-
// Only restart if we're reusing the existing session
212-
// or if we're not using the console REPL, otherwise
213-
// the process should terminate
214-
if (useExistingSession)
215-
{
216-
Logger.Write(
217-
LogLevel.Normal,
218-
"Previous debug session ended, restarting debug service...");
219-
220-
this.StartDebugService(debugServicePort, profilePaths, true);
221-
}
222-
else if (!this.enableConsoleRepl)
223-
{
224-
this.StartDebugService(debugServicePort, profilePaths, false);
225-
}
226-
};
248+
Logger.Write(
249+
LogLevel.Normal,
250+
"Previous debug session ended, restarting debug service listener...");
227251

228-
this.debugAdapter.Start().Wait();
252+
this.debugServiceListener.Start();
253+
};
229254

230-
Logger.Write(
231-
LogLevel.Normal,
232-
string.Format(
233-
"Debug service started, listening on port {0}",
234-
debugServicePort));
255+
await this.debugAdapter.Start();
235256
}
236257

237258
/// <summary>
@@ -251,17 +272,9 @@ public void StopServices()
251272
/// </summary>
252273
public void WaitForCompletion()
253274
{
254-
// Wait based on which server is started. If the language server
255-
// hasn't been started then we may only need to wait on the debug
256-
// adapter to complete.
257-
if (this.languageServer != null)
258-
{
259-
this.languageServer.WaitForExit();
260-
}
261-
else if (this.debugAdapter != null)
262-
{
263-
this.debugAdapter.WaitForExit();
264-
}
275+
// TODO: We need a way to know when to complete this task!
276+
this.serverCompletedTask = new TaskCompletionSource<bool>();
277+
this.serverCompletedTask.Task.Wait();
265278
}
266279

267280
#endregion

‎src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs

-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ await this.SendRequest(
3232
}
3333

3434
protected override Task OnStart()
35-
{
36-
return Task.FromResult(true);
37-
}
38-
39-
protected override Task OnConnect()
4035
{
4136
// Initialize the debug adapter
4237
return this.SendRequest(

‎src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs

-5
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ protected override Task Initialize()
2828
// Add handlers for common events
2929
this.SetEventHandler(PublishDiagnosticsNotification.Type, HandlePublishDiagnosticsEvent);
3030

31-
return Task.FromResult(true);
32-
}
33-
34-
protected override Task OnConnect()
35-
{
3631
// Send the 'initialize' request and wait for the response
3732
var initializeParams = new InitializeParams
3833
{

‎src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/ChannelBase.cs

-13
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
1414
/// </summary>
1515
public abstract class ChannelBase
1616
{
17-
/// <summary>
18-
/// Gets a boolean that is true if the channel is connected or false if not.
19-
/// </summary>
20-
public bool IsConnected { get; protected set; }
21-
2217
/// <summary>
2318
/// Gets the MessageReader for reading messages from the channel.
2419
/// </summary>
@@ -48,14 +43,6 @@ public void Start(MessageProtocolType messageProtocolType)
4843
this.Initialize(messageSerializer);
4944
}
5045

51-
/// <summary>
52-
/// Returns a Task that allows the consumer of the ChannelBase
53-
/// implementation to wait until a connection has been made to
54-
/// the opposite endpoint whether it's a client or server.
55-
/// </summary>
56-
/// <returns>A Task to be awaited until a connection is made.</returns>
57-
public abstract Task WaitForConnection();
58-
5946
/// <summary>
6047
/// Stops the channel.
6148
/// </summary>

‎src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs

+37-38
Original file line numberDiff line numberDiff line change
@@ -11,51 +11,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
1111
{
1212
public class NamedPipeClientChannel : ChannelBase
1313
{
14-
private string pipeName;
1514
private NamedPipeClientStream pipeClient;
1615

17-
public NamedPipeClientChannel(string pipeName)
16+
public NamedPipeClientChannel(NamedPipeClientStream pipeClient)
1817
{
19-
this.pipeName = pipeName;
20-
}
21-
22-
public override async Task WaitForConnection()
23-
{
24-
#if CoreCLR
25-
await this.pipeClient.ConnectAsync();
26-
#else
27-
this.IsConnected = false;
28-
29-
while (!this.IsConnected)
30-
{
31-
try
32-
{
33-
// Wait for 500 milliseconds so that we don't tie up the thread
34-
this.pipeClient.Connect(500);
35-
this.IsConnected = this.pipeClient.IsConnected;
36-
}
37-
catch (TimeoutException)
38-
{
39-
// Connect timed out, wait and try again
40-
await Task.Delay(1000);
41-
continue;
42-
}
43-
}
44-
#endif
45-
46-
// If we've reached this point, we're connected
47-
this.IsConnected = true;
18+
this.pipeClient = pipeClient;
4819
}
4920

5021
protected override void Initialize(IMessageSerializer messageSerializer)
5122
{
52-
this.pipeClient =
53-
new NamedPipeClientStream(
54-
".",
55-
this.pipeName,
56-
PipeDirection.InOut,
57-
PipeOptions.Asynchronous);
58-
5923
this.MessageReader =
6024
new MessageReader(
6125
this.pipeClient,
@@ -74,6 +38,41 @@ protected override void Shutdown()
7438
this.pipeClient.Dispose();
7539
}
7640
}
41+
42+
public static async Task<NamedPipeClientChannel> Connect(
43+
string pipeName,
44+
MessageProtocolType messageProtocolType)
45+
{
46+
var pipeClient =
47+
new NamedPipeClientStream(
48+
".",
49+
pipeName,
50+
PipeDirection.InOut,
51+
PipeOptions.Asynchronous);
52+
53+
#if CoreCLR
54+
await pipeClient.ConnectAsync();
55+
#else
56+
while (!pipeClient.IsConnected)
57+
{
58+
try
59+
{
60+
// Wait for 500 milliseconds so that we don't tie up the thread
61+
pipeClient.Connect(500);
62+
}
63+
catch (TimeoutException)
64+
{
65+
// Connect timed out, wait and try again
66+
await Task.Delay(1000);
67+
continue;
68+
}
69+
}
70+
#endif
71+
var clientChannel = new NamedPipeClientChannel(pipeClient);
72+
clientChannel.Start(messageProtocolType);
73+
74+
return clientChannel;
75+
}
7776
}
7877
}
7978

0 commit comments

Comments
 (0)
Please sign in to comment.