-
Notifications
You must be signed in to change notification settings - Fork 237
Adds ability to use separate pipes for reading and writing #785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
9db4d7e
ea65162
8d20b53
c47e80b
4e747a1
0189966
569c223
93a391f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,9 +37,15 @@ function Start-EditorServicesHost { | |
[string] | ||
$LanguageServiceNamedPipe, | ||
|
||
[string] | ||
$LanguageServiceWriteNamedPipe, | ||
|
||
[string] | ||
$DebugServiceNamedPipe, | ||
|
||
[string] | ||
$DebugServiceWriteNamedPipe, | ||
|
||
[ValidateNotNullOrEmpty()] | ||
[string] | ||
$BundledModulesPath, | ||
|
@@ -106,12 +112,24 @@ function Start-EditorServicesHost { | |
|
||
if ($LanguageServiceNamedPipe) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we used parameter sets then here we'd check if ($PSCmdlet.ParameterSetName -eq 'NamedPipeDuplex') and perhaps $LanguageServicePipe would be a mandatory parameter? Not sure about $DebugLanguageServicePipe, I suppose you could create one but not the other? |
||
$languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe | ||
$languageServiceConfig.Endpoint = "$LanguageServiceNamedPipe" | ||
if ($LanguageServiceWriteNamedPipe) { | ||
$languageServiceConfig.Endpoint = "$LanguageServiceNamedPipe;$LanguageServiceWriteNamedPipe" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This way of specifying two pipe names in a single string here works, but I'm not a huge fan of it. Getting around it without a breaking change doesn't seem all that simple, but I think we should revisit this soon. Otherwise all our config options are going to end up named something abstract and needing a bunch of preprocessing to unspool, even though we have a typed, in-memory .NET API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not suggesting this be changed in any way other than @tylerl0706's suggestion, but I don't think this should be the long term solution. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps something to re-visit for v2? |
||
} | ||
rkeithhill marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else | ||
{ | ||
$languageServiceConfig.Endpoint = "$LanguageServiceNamedPipe" | ||
} | ||
} | ||
|
||
if ($DebugServiceNamedPipe) { | ||
$debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe | ||
$debugServiceConfig.Endpoint = "$DebugServiceNamedPipe" | ||
if ($DebugServiceWriteNamedPipe) { | ||
$debugServiceConfig.Endpoint = "$DebugServiceNamedPipe;$DebugServiceWriteNamedPipe" | ||
} | ||
else | ||
{ | ||
$debugServiceConfig.Endpoint = "$DebugServiceNamedPipe" | ||
} | ||
} | ||
|
||
if ($DebugServiceOnly.IsPresent) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,6 +68,9 @@ param( | |
[switch] | ||
$Stdio, | ||
|
||
[switch] | ||
$SplitInOutPipes, | ||
|
||
[string] | ||
$LanguageServicePipeName = $null, | ||
|
||
|
@@ -173,8 +176,7 @@ function New-NamedPipeName { | |
|
||
# We try 10 times to find a valid pipe name | ||
for ($i = 0; $i -lt 10; $i++) { | ||
# add a guid to make the pipe unique | ||
$PipeName = "PSES_$([guid]::NewGuid())" | ||
$PipeName = "PSES_$([System.IO.Path]::GetRandomFileName())" | ||
TylerLeonhardt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if ((Test-NamedPipeName -PipeName $PipeName)) { | ||
return $PipeName | ||
|
@@ -269,6 +271,9 @@ try { | |
$languageServiceTransport = $null | ||
$debugServiceTransport = $null | ||
|
||
$LanguageServiceWritePipeName = $null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these lines here needed? Or would leaving them out have the same effect? Since they are PascalCased, I'm assuming they are script parameters, so the nulling could be done in the parameter definition if required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a script parameter. This variable is a local so it should be camelCased - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, missed this. Will make them as script parameters and move to a separate parameter set. |
||
$DebugServiceWritePipeName = $null | ||
|
||
if ($Stdio.IsPresent) { | ||
$languageServiceTransport = "Stdio" | ||
$debugServiceTransport = "Stdio" | ||
|
@@ -292,6 +297,11 @@ try { | |
ExitWithError "Pipe name supplied is already taken: $DebugServicePipeName" | ||
} | ||
} | ||
# If present create second pipe for writing | ||
if ($SplitInOutPipes.IsPresent) { | ||
$LanguageServiceWritePipeName = New-NamedPipeName | ||
$DebugServiceWritePipeName = New-NamedPipeName | ||
} | ||
} | ||
|
||
if ($EnableConsoleRepl) { | ||
|
@@ -309,7 +319,9 @@ try { | |
-LogLevel $LogLevel ` | ||
-AdditionalModules $AdditionalModules ` | ||
-LanguageServiceNamedPipe $LanguageServicePipeName ` | ||
-LanguageServiceWriteNamedPipe $LanguageServiceWritePipeName ` | ||
-DebugServiceNamedPipe $DebugServicePipeName ` | ||
-DebugServiceWriteNamedPipe $DebugServiceWritePipeName ` | ||
-Stdio:$Stdio.IsPresent` | ||
-BundledModulesPath $BundledModulesPath ` | ||
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent ` | ||
|
@@ -326,15 +338,35 @@ try { | |
}; | ||
|
||
if ($LanguageServicePipeName) { | ||
$resultDetails["languageServicePipeName"] = Get-NamedPipePath -PipeName $LanguageServicePipeName | ||
if ($IsLinux -or $IsMacOS) { | ||
Set-NamedPipeMode -PipeFile $resultDetails["languageServicePipeName"] | ||
if ($LanguageServiceWritePipeName) { | ||
$resultDetails["languageServiceReadPipeName"] = Get-NamedPipePath -PipeName $LanguageServicePipeName | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this pattern is repeated 3 times. Would it be possible to make it a function? |
||
$resultDetails["languageServiceWritePipeName"] = Get-NamedPipePath -PipeName $LanguageServiceWritePipeName | ||
if ($IsLinux -or $IsMacOS) { | ||
Set-NamedPipeMode -PipeFile $resultDetails["languageServiceReadPipeName"] | ||
Set-NamedPipeMode -PipeFile $resultDetails["languageServiceWritePipeName"] | ||
} | ||
} | ||
else { | ||
$resultDetails["languageServicePipeName"] = Get-NamedPipePath -PipeName $LanguageServicePipeName | ||
if ($IsLinux -or $IsMacOS) { | ||
Set-NamedPipeMode -PipeFile $resultDetails["languageServicePipeName"] | ||
} | ||
} | ||
} | ||
if ($DebugServicePipeName) { | ||
$resultDetails["debugServicePipeName"] = Get-NamedPipePath -PipeName $DebugServicePipeName | ||
if ($IsLinux -or $IsMacOS) { | ||
Set-NamedPipeMode -PipeFile $resultDetails["debugServicePipeName"] | ||
if ($DebugServiceWritePipeName) { | ||
$resultDetails["debugServiceReadPipeName"] = Get-NamedPipePath -PipeName $DebugServicePipeName | ||
$resultDetails["debugServiceWritePipeName"] = Get-NamedPipePath -PipeName $DebugServiceWritePipeName | ||
if ($IsLinux -or $IsMacOS) { | ||
Set-NamedPipeMode -PipeFile $resultDetails["debugServiceReadPipeName"] | ||
Set-NamedPipeMode -PipeFile $resultDetails["debugServiceWritePipeName"] | ||
} | ||
} | ||
else { | ||
$resultDetails["debugServicePipeName"] = Get-NamedPipePath -PipeName $DebugServicePipeName | ||
if ($IsLinux -or $IsMacOS) { | ||
Set-NamedPipeMode -PipeFile $resultDetails["debugServicePipeName"] | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -463,7 +463,19 @@ private IServerListener CreateServiceListener(MessageProtocolType protocol, Edit | |
|
||
case EditorServiceTransportType.NamedPipe: | ||
{ | ||
return new NamedPipeServerListener(protocol, config.Endpoint, this.logger); | ||
string endpoint = config.Endpoint; | ||
int splitIndex = endpoint.IndexOf(';'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm more inclined to say that the character that separates the two file names should be the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I spoke to @rjmholt about the use of I think I'm ok if you don't want to do this in this PR - but it's pretty important so if you want to take it on I'd really appreciate it. |
||
if (splitIndex > 0) | ||
{ | ||
string readPipeName = endpoint.Substring(0, splitIndex); | ||
string writePipeName = endpoint.Substring(splitIndex + 1); | ||
this.logger.Write(LogLevel.Verbose, $"Creating NamedPipeServerListener for ${protocol} protocol with two pipes: Read: '" + readPipeName + "'. Write: '" + writePipeName + "'"); | ||
return new NamedPipeServerListener(protocol, readPipeName, writePipeName, this.logger); | ||
} | ||
else | ||
{ | ||
return new NamedPipeServerListener(protocol, endpoint, this.logger); | ||
} | ||
} | ||
|
||
default: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
using Microsoft.PowerShell.EditorServices.Utility; | ||
using Microsoft.Win32.SafeHandles; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.IO.Pipes; | ||
using System.Runtime.InteropServices; | ||
|
@@ -19,7 +20,9 @@ public class NamedPipeServerListener : ServerListenerBase<NamedPipeServerChannel | |
{ | ||
private ILogger logger; | ||
private string pipeName; | ||
private readonly string writePipeName; | ||
private NamedPipeServerStream pipeServer; | ||
private NamedPipeServerStream writePipeServer; | ||
|
||
public NamedPipeServerListener( | ||
MessageProtocolType messageProtocolType, | ||
|
@@ -29,6 +32,19 @@ public NamedPipeServerListener( | |
{ | ||
this.logger = logger; | ||
this.pipeName = pipeName; | ||
this.writePipeName = null; | ||
} | ||
|
||
public NamedPipeServerListener( | ||
MessageProtocolType messageProtocolType, | ||
string readPipeName, | ||
string writePipeName, | ||
ILogger logger) | ||
: base(messageProtocolType) | ||
{ | ||
this.logger = logger; | ||
this.pipeName = readPipeName; | ||
this.writePipeName = writePipeName; | ||
} | ||
|
||
public override void Start() | ||
|
@@ -63,6 +79,10 @@ public override void Start() | |
// 99% of this code was borrowed from PowerShell here: | ||
// https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs#L124-L256 | ||
this.pipeServer = NamedPipeNative.CreateNamedPipe(pipeName, pipeSecurity); | ||
if (this.writePipeName != null) | ||
{ | ||
this.writePipeServer = NamedPipeNative.CreateNamedPipe(writePipeName, pipeSecurity); | ||
} | ||
} | ||
else | ||
{ | ||
|
@@ -74,6 +94,15 @@ public override void Start() | |
maxNumberOfServerInstances: 1, | ||
transmissionMode: PipeTransmissionMode.Byte, | ||
options: PipeOptions.Asynchronous); | ||
if (this.writePipeName != null) | ||
{ | ||
this.writePipeServer = new NamedPipeServerStream( | ||
pipeName: writePipeName, | ||
direction: PipeDirection.InOut, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the direction here be just |
||
maxNumberOfServerInstances: 1, | ||
transmissionMode: PipeTransmissionMode.Byte, | ||
options: PipeOptions.None); | ||
} | ||
} | ||
ListenForConnection(); | ||
} | ||
|
@@ -97,39 +126,50 @@ public override void Stop() | |
|
||
this.logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); | ||
} | ||
if (this.writePipeServer != null) | ||
{ | ||
this.logger.Write(LogLevel.Verbose, $"Named write pipe server {writePipeServer} shutting down..."); | ||
|
||
this.writePipeServer.Dispose(); | ||
|
||
this.logger.Write(LogLevel.Verbose, $"Named write pipe server {writePipeServer} has been disposed."); | ||
} | ||
} | ||
|
||
private void ListenForConnection() | ||
{ | ||
Task.Factory.StartNew( | ||
async () => | ||
List<Task> connectionTasks = new List<Task> {WaitForConnectionAsync(this.pipeServer)}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yeah the coding style in this project isn't great at the moment -- we've been trying to move it over gradually to being less redundant and verbose. |
||
if (this.writePipeServer != null) | ||
{ | ||
connectionTasks.Add(WaitForConnectionAsync(this.writePipeServer)); | ||
} | ||
|
||
Task.Run(async () => | ||
{ | ||
try | ||
{ | ||
try | ||
{ | ||
await Task.WhenAll(connectionTasks); | ||
this.OnClientConnect(new NamedPipeServerChannel(this.pipeServer, this.writePipeServer, this.logger)); | ||
} | ||
catch (Exception e) | ||
{ | ||
this.logger.WriteException( | ||
"An unhandled exception occurred while listening for a named pipe client connection", | ||
e); | ||
|
||
throw e; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rethrow should never specify the exception - you'll lose stack info. Use Ideally our logger methods would return false so you could use C# exception filters e.g.:
But we don't do that - yet. @rjmholt Any chance we could modify the log methods to return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we could definitely modify the methods. Or else write wrapper methods that explain the intended caller context: catch (Exception e) when (_logger.WriteExceptionWithoutUnwinding("...", e))
{
} Bit of a mouthful -- just some name that says "I return false for use with exception filters". |
||
} | ||
}); | ||
} | ||
|
||
private static async Task WaitForConnectionAsync(NamedPipeServerStream pipeServerStream) | ||
{ | ||
#if CoreCLR | ||
await this.pipeServer.WaitForConnectionAsync(); | ||
await pipeServerStream.WaitForConnectionAsync(); | ||
#else | ||
await Task.Factory.FromAsync( | ||
this.pipeServer.BeginWaitForConnection, | ||
this.pipeServer.EndWaitForConnection, null); | ||
await Task.Factory.FromAsync(pipeServerStream.BeginWaitForConnection, pipeServerStream.EndWaitForConnection, null); | ||
#endif | ||
|
||
await this.pipeServer.FlushAsync(); | ||
|
||
this.OnClientConnect( | ||
new NamedPipeServerChannel( | ||
this.pipeServer, | ||
this.logger)); | ||
} | ||
catch (Exception e) | ||
{ | ||
this.logger.WriteException( | ||
"An unhandled exception occurred while listening for a named pipe client connection", | ||
e); | ||
|
||
throw e; | ||
} | ||
}); | ||
await pipeServerStream.FlushAsync(); | ||
} | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.