EnvDTE.Host/ConnectionManager.cs (81 lines of code) (raw):
using System;
using System.Net;
using JetBrains.Annotations;
using JetBrains.Collections.Viewable;
using JetBrains.EnvDTE.Host.Callback;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
using JetBrains.Rd;
using JetBrains.Rd.Impl;
using JetBrains.Rider.Model;
namespace JetBrains.EnvDTE.Host
{
[PublicAPI]
public class ConnectionManager
{
private const string Host = "EnvDTE Communication Host";
private const string Protocol = "EnvDTE Communication Protocol";
public int Port { get; private set; }
public ConnectionManager(Lifetime lifetime, [NotNull] ISolution solution) => SetupModel(lifetime, solution);
private void SetupModel(Lifetime lifetime, ISolution solution)
{
SingleThreadScheduler.RunOnSeparateThread(lifetime, Host, scheduler =>
{
var serverFactory = new ProtocolFactory(lifetime, scheduler, Host);
Port = serverFactory.localPort;
serverFactory.connected.View(lifetime, (connectionLifetime, protocol) =>
{
scheduler.Queue(() =>
{
var model = new DteProtocolModel(connectionLifetime, protocol);
RegisterCallbacks(model, scheduler, solution);
});
});
});
}
private static void RegisterCallbacks([NotNull] DteProtocolModel model, [NotNull] IScheduler scheduler, [NotNull] ISolution solution)
{
foreach (var provider in solution.GetComponents2<IEnvDteCallbackProvider>())
{
provider.RegisterCallbacks(model, scheduler);
}
}
}
// TODO: Move to a common place and make sure that reusing the protocol this way is safe
public class ProtocolFactory
{
public readonly int localPort;
public readonly IViewableSet<Protocol> connected = new ViewableSet<Protocol>();
public ProtocolFactory(Lifetime lifetime, IScheduler scheduler, string id, IPEndPoint? endpoint = null)
: this(lifetime, () => new SocketWire.WireParameters(scheduler, id), endpoint) {}
public ProtocolFactory(
Lifetime lifetime,
Func<SocketWire.WireParameters> wireParametersFactory,
IPEndPoint? endpoint = null
)
{
var serverSocket = SocketWire.Server.CreateServerSocket(endpoint);
var serverSocketLifetimeDef = new LifetimeDefinition(lifetime);
serverSocketLifetimeDef.Lifetime.OnTermination(() =>
{
SocketWire.Base.CloseSocket(serverSocket);
});
localPort = ((IPEndPoint) serverSocket.LocalEndPoint).Port;
void Rec()
{
lifetime.TryExecute(() =>
{
var (scheduler, id) = wireParametersFactory();
var s = new SocketWire.Server(lifetime, scheduler, serverSocket, id);
// Create one protocol per server, this way server instances can be reused for multiple client connections
var proto = new Protocol($"{s.Id}-Protocol", new Serializers(), new Identities(IdKind.Server), scheduler, s, lifetime);
// Each server will spawn a thread that will be waiting in serverSocket.Accept method. When lifetime
// termination is invoked, these threads synchronously join the termination thread. Since these Thread.Join
// calls are located deeper in the Lifetime termination stack we have to place this socket termination call
// after each server creation.
lifetime.OnTermination(() => serverSocketLifetimeDef.Terminate());
s.Connected.WhenTrue(lifetime, lt =>
{
connected.AddLifetimed(lt, proto);
Rec();
});
});
}
Rec();
}
}
}