-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
ConnectionHost.cs
146 lines (123 loc) · 5.92 KB
/
ConnectionHost.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO.Pipes;
namespace Microsoft.NET.Sdk.Razor.Tool
{
// Heavily influenced by:
// https://github.com/dotnet/roslyn/blob/14aed138a01c448143b9acf0fe77a662e3dfe2f4/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs#L17
internal abstract class ConnectionHost
{
private static int counter;
public abstract Task<Connection> WaitForConnectionAsync(CancellationToken cancellationToken);
public static ConnectionHost Create(string pipeName)
{
return new NamedPipeConnectionHost(pipeName);
}
private static string GetNextIdentifier()
{
var id = Interlocked.Increment(ref counter);
return "connection-" + id;
}
private class NamedPipeConnectionHost : ConnectionHost
{
// Size of the buffers to use: 64K
private const int PipeBufferSize = 0x10000;
// From https://github.com/dotnet/corefx/blob/29cd6a0b0ac2993cee23ebaf36ca3d4bce6dd75f/src/System.IO.Pipes/ref/System.IO.Pipes.cs#L93.
// Using the enum value directly as this option is not available in netstandard.
private const PipeOptions PipeOptionCurrentUserOnly = (PipeOptions)536870912;
private static readonly PipeOptions _pipeOptions = GetPipeOptions();
public NamedPipeConnectionHost(string pipeName)
{
PipeName = pipeName;
}
public string PipeName { get; }
public override async Task<Connection> WaitForConnectionAsync(CancellationToken cancellationToken)
{
// Create the pipe and begin waiting for a connection. This doesn't block, but could fail
// in certain circumstances, such as the OS refusing to create the pipe for some reason
// or the pipe was disconnected before we starting listening.
var pipeStream = new NamedPipeServerStream(
PipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections.
PipeTransmissionMode.Byte,
_pipeOptions,
PipeBufferSize, // Default input buffer
PipeBufferSize);// Default output buffer
ServerLogger.Log("Waiting for new connection");
await pipeStream.WaitForConnectionAsync(cancellationToken);
ServerLogger.Log("Pipe connection detected.");
if (Environment.Is64BitProcess || Memory.IsMemoryAvailable())
{
ServerLogger.Log("Memory available - accepting connection");
return new NamedPipeConnection(pipeStream, GetNextIdentifier());
}
pipeStream.Close();
throw new Exception("Insufficient resources to process new connection.");
}
private static PipeOptions GetPipeOptions()
{
var options = PipeOptions.Asynchronous | PipeOptions.WriteThrough;
if (Enum.IsDefined(typeof(PipeOptions), PipeOptionCurrentUserOnly))
{
return options | PipeOptionCurrentUserOnly;
}
return options;
}
}
private class NamedPipeConnection : Connection
{
public NamedPipeConnection(NamedPipeServerStream stream, string identifier)
{
Stream = stream;
Identifier = identifier;
}
public override async Task WaitForDisconnectAsync(CancellationToken cancellationToken)
{
if (!(Stream is PipeStream pipeStream))
{
return;
}
// We have to poll for disconnection by reading, PipeStream.IsConnected isn't reliable unless you
// actually do a read - which will cause it to update its state.
while (!cancellationToken.IsCancellationRequested && pipeStream.IsConnected)
{
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
try
{
ServerLogger.Log($"Before poking pipe {Identifier}.");
#pragma warning disable CA2022 // Avoid inexact read
await Stream.ReadAsync(Array.Empty<byte>(), 0, 0, cancellationToken);
#pragma warning restore CA2022
ServerLogger.Log($"After poking pipe {Identifier}.");
}
catch (OperationCanceledException)
{
}
catch (Exception e)
{
// It is okay for this call to fail. Errors will be reflected in the
// IsConnected property which will be read on the next iteration.
ServerLogger.LogException(e, $"Error poking pipe {Identifier}.");
}
}
}
protected override void Dispose(bool disposing)
{
ServerLogger.Log($"Pipe {Identifier}: Closing.");
try
{
Stream.Dispose();
}
catch (Exception ex)
{
// The client connection failing to close isn't fatal to the server process. It is simply a client
// for which we can no longer communicate and that's okay because the Close method indicates we are
// done with the client already.
var message = $"Pipe {Identifier}: Error closing pipe.";
ServerLogger.LogException(ex, message);
}
}
}
}
}