134 lines
5.4 KiB
C#
134 lines
5.4 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.IO.Pipes;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Microsoft.AspNetCore.Razor.Tools
|
|
{
|
|
// Heavily influenced by:
|
|
// https://github.com/dotnet/roslyn/blob/14aed138a01c448143b9acf0fe77a662e3dfe2f4/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs#L17
|
|
internal abstract class ConnectionHost
|
|
{
|
|
// Size of the buffers to use: 64K
|
|
private const int PipeBufferSize = 0x10000;
|
|
|
|
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
|
|
{
|
|
public NamedPipeConnectionHost(string pipeName)
|
|
{
|
|
PipeName = pipeName;
|
|
}
|
|
|
|
public string PipeName { get; }
|
|
|
|
public async override 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.
|
|
//
|
|
// Also, note that we're waiting on CoreFx to implement some security features for us.
|
|
// https://github.com/dotnet/corefx/issues/24040
|
|
var pipeStream = new NamedPipeServerStream(
|
|
PipeName,
|
|
PipeDirection.InOut,
|
|
NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections.
|
|
PipeTransmissionMode.Byte,
|
|
PipeOptions.Asynchronous | PipeOptions.WriteThrough,
|
|
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 class NamedPipeConnection : Connection
|
|
{
|
|
public NamedPipeConnection(NamedPipeServerStream stream, string identifier)
|
|
{
|
|
Stream = stream;
|
|
Identifier = identifier;
|
|
}
|
|
|
|
public async override 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}.");
|
|
await Stream.ReadAsync(Array.Empty<byte>(), 0, 0, cancellationToken);
|
|
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 = string.Format($"Pipe {Identifier}: Error closing pipe.");
|
|
ServerLogger.LogException(ex, message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|