dotnet-installer/src/dotnet/commands/dotnet-projectmodel-server/Program.cs
Andrew Stanton-Nurse ef0ca39da1 Memory usage improvements in build (#2626)
* Use a WorkspaceContext in dotnet-build to cache project data across
multiple compilations in a single build action
* Dramatically reduce string and object duplication by introducing a
"Symbol Table" that shares instances of NuGetVersion, NuGetFramework,
VersionRange and string across multiple lock-file parses

Test Results:
* Testing was done by compiling Microsoft.AspNetCore.Mvc (and it's
dependencies) and taking memory snapshots after each compilation in
dotMemory
* We used to allocate ~3MB and deallocate ~2.5MB on EACH compilation in
a single build action. This has been reduced to ~120KB
allocated/deallocated
* After introducing WorkspaceContext, total memory usage spiked from 6MB
across the whole build action to about 13MB, introducing the symbol
table dropped it back to about 5-6MB.
2016-04-22 15:01:56 -07:00

171 lines
6.1 KiB
C#

// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.ProjectModel.Server
{
public class ProjectModelServerCommand
{
private readonly Dictionary<int, ProjectManager> _projects;
private readonly WorkspaceContext _workspaceContext;
private readonly ProtocolManager _protocolManager;
private readonly ILoggerFactory _loggerFactory;
private readonly string _hostName;
private readonly int _port;
private Socket _listenSocket;
public ProjectModelServerCommand(int port, string hostName, ILoggerFactory loggerFactory)
{
_port = port;
_hostName = hostName;
_loggerFactory = loggerFactory;
_protocolManager = new ProtocolManager(maxVersion: 4, loggerFactory: _loggerFactory);
_workspaceContext = WorkspaceContext.Create(designTime: true);
_projects = new Dictionary<int, ProjectManager>();
}
public static int Run(string[] args)
{
var app = new CommandLineApplication();
app.Name = "dotnet-projectmodel-server";
app.Description = ".NET Project Model Server";
app.FullName = ".NET Design Time Server";
app.Description = ".NET Design Time Server";
app.HelpOption("-?|-h|--help");
var verbose = app.Option("--verbose", "Verbose ouput", CommandOptionType.NoValue);
var hostpid = app.Option("--host-pid", "The process id of the host", CommandOptionType.SingleValue);
var hostname = app.Option("--host-name", "The name of the host", CommandOptionType.SingleValue);
var port = app.Option("--port", "The TCP port used for communication", CommandOptionType.SingleValue);
app.OnExecute(() =>
{
var loggerFactory = new LoggerFactory();
loggerFactory.AddConsole(verbose.HasValue() ? LogLevel.Debug : LogLevel.Information);
var logger = loggerFactory.CreateLogger<ProjectModelServerCommand>();
try
{
if (!MonitorHostProcess(hostpid, logger))
{
return 1;
}
var intPort = CheckPort(port, logger);
if (intPort == -1)
{
return 1;
}
if (!hostname.HasValue())
{
logger.LogError($"Option \"{hostname.LongName}\" is missing.");
return 1;
}
var program = new ProjectModelServerCommand(intPort, hostname.Value(), loggerFactory);
program.OpenChannel();
}
catch (Exception ex)
{
logger.LogCritical($"Unhandled exception in server main: {ex}");
throw;
}
return 0;
});
return app.Execute(args);
}
public void OpenChannel()
{
var logger = _loggerFactory.CreateLogger($"OpenChannel");
_listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, _port));
_listenSocket.Listen(10);
logger.LogInformation($"Process ID {Process.GetCurrentProcess().Id}");
logger.LogInformation($"Listening on port {_port}");
while (true)
{
var acceptSocket = _listenSocket.Accept();
logger.LogInformation($"Client accepted {acceptSocket.LocalEndPoint}");
var connection = new ConnectionContext(acceptSocket,
_hostName,
_protocolManager,
_workspaceContext,
_projects,
_loggerFactory);
connection.QueueStart();
}
}
public void Shutdown()
{
if (_listenSocket.Connected)
{
_listenSocket.Shutdown(SocketShutdown.Both);
}
}
private static int CheckPort(CommandOption port, ILogger logger)
{
if (!port.HasValue())
{
logger.LogError($"Option \"{port.LongName}\" is missing.");
}
int result;
if (int.TryParse(port.Value(), out result))
{
return result;
}
else
{
logger.LogError($"Option \"{port.LongName}\" is not a valid Int32 value.");
return -1;
}
}
private static bool MonitorHostProcess(CommandOption host, ILogger logger)
{
if (!host.HasValue())
{
logger.LogError($"Option \"{host.LongName}\" is missing.");
return false;
}
int hostPID;
if (int.TryParse(host.Value(), out hostPID))
{
var hostProcess = Process.GetProcessById(hostPID);
hostProcess.EnableRaisingEvents = true;
hostProcess.Exited += (s, e) =>
{
Process.GetCurrentProcess().Kill();
};
logger.LogDebug($"Server will exit when process {hostPID} exits.");
return true;
}
else
{
logger.LogError($"Option \"{host.LongName}\" is not a valid Int32 value.");
return false;
}
}
}
}