diff --git a/Agent/Phantom.Agent.Rpc/ControllerConnection.cs b/Agent/Phantom.Agent.Rpc/ControllerConnection.cs index 61f232c..387e920 100644 --- a/Agent/Phantom.Agent.Rpc/ControllerConnection.cs +++ b/Agent/Phantom.Agent.Rpc/ControllerConnection.cs @@ -8,9 +8,9 @@ namespace Phantom.Agent.Rpc; public sealed class ControllerConnection { private static readonly ILogger Logger = PhantomLogger.Create(nameof(ControllerConnection)); - private readonly RpcConnectionToServer<IMessageToControllerListener> connection; + private readonly RpcConnectionToServer<IMessageToController> connection; - public ControllerConnection(RpcConnectionToServer<IMessageToControllerListener> connection) { + public ControllerConnection(RpcConnectionToServer<IMessageToController> connection) { this.connection = connection; Logger.Information("Connection ready."); } diff --git a/Agent/Phantom.Agent.Rpc/KeepAliveLoop.cs b/Agent/Phantom.Agent.Rpc/KeepAliveLoop.cs index 47913b9..80bb12b 100644 --- a/Agent/Phantom.Agent.Rpc/KeepAliveLoop.cs +++ b/Agent/Phantom.Agent.Rpc/KeepAliveLoop.cs @@ -11,10 +11,10 @@ sealed class KeepAliveLoop { private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromSeconds(10); - private readonly RpcConnectionToServer<IMessageToControllerListener> connection; + private readonly RpcConnectionToServer<IMessageToController> connection; private readonly CancellationTokenSource cancellationTokenSource = new (); - public KeepAliveLoop(RpcConnectionToServer<IMessageToControllerListener> connection) { + public KeepAliveLoop(RpcConnectionToServer<IMessageToController> connection) { this.connection = connection; Task.Run(Run); } diff --git a/Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs b/Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs index fe9141a..c5c689a 100644 --- a/Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs +++ b/Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs @@ -3,20 +3,21 @@ using NetMQ.Sockets; using Phantom.Common.Messages.Agent; using Phantom.Common.Messages.Agent.BiDirectional; using Phantom.Common.Messages.Agent.ToController; +using Phantom.Utils.Actor; using Phantom.Utils.Rpc.Runtime; using Phantom.Utils.Rpc.Sockets; using Serilog; namespace Phantom.Agent.Rpc; -public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> { - public static Task Launch(RpcClientSocket<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> socket, IMessageToAgentListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) { - return new RpcClientRuntime(socket, messageListener, disconnectSemaphore, receiveCancellationToken).Launch(); +public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToAgent, IMessageToController, ReplyMessage> { + public static Task Launch(RpcClientSocket<IMessageToAgent, IMessageToController, ReplyMessage> socket, ActorRef<IMessageToAgent> handlerActorRef, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) { + return new RpcClientRuntime(socket, handlerActorRef, disconnectSemaphore, receiveCancellationToken).Launch(); } - private RpcClientRuntime(RpcClientSocket<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> socket, IMessageToAgentListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket, messageListener, disconnectSemaphore, receiveCancellationToken) {} + private RpcClientRuntime(RpcClientSocket<IMessageToAgent, IMessageToController, ReplyMessage> socket, ActorRef<IMessageToAgent> handlerActor, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket, handlerActor, disconnectSemaphore, receiveCancellationToken) {} - protected override async Task RunWithConnection(ClientSocket socket, RpcConnectionToServer<IMessageToControllerListener> connection) { + protected override async Task RunWithConnection(ClientSocket socket, RpcConnectionToServer<IMessageToController> connection) { var keepAliveLoop = new KeepAliveLoop(connection); try { await base.RunWithConnection(socket, connection); diff --git a/Agent/Phantom.Agent.Services/Rpc/MessageListener.cs b/Agent/Phantom.Agent.Services/Rpc/ControllerMessageHandlerActor.cs similarity index 53% rename from Agent/Phantom.Agent.Services/Rpc/MessageListener.cs rename to Agent/Phantom.Agent.Services/Rpc/ControllerMessageHandlerActor.cs index 22b9a62..91e4cc9 100644 --- a/Agent/Phantom.Agent.Services/Rpc/MessageListener.cs +++ b/Agent/Phantom.Agent.Services/Rpc/ControllerMessageHandlerActor.cs @@ -4,27 +4,41 @@ using Phantom.Common.Messages.Agent; using Phantom.Common.Messages.Agent.BiDirectional; using Phantom.Common.Messages.Agent.ToAgent; using Phantom.Common.Messages.Agent.ToController; +using Phantom.Utils.Actor; using Phantom.Utils.Logging; -using Phantom.Utils.Rpc.Message; using Phantom.Utils.Rpc.Runtime; using Serilog; namespace Phantom.Agent.Services.Rpc; -public sealed class MessageListener : IMessageToAgentListener { - private static ILogger Logger { get; } = PhantomLogger.Create<MessageListener>(); +public sealed class ControllerMessageHandlerActor : ReceiveActor<IMessageToAgent> { + private static ILogger Logger { get; } = PhantomLogger.Create<ControllerMessageHandlerActor>(); - private readonly RpcConnectionToServer<IMessageToControllerListener> connection; + public readonly record struct Init(RpcConnectionToServer<IMessageToController> Connection, AgentServices Agent, CancellationTokenSource ShutdownTokenSource); + + public static Props<IMessageToAgent> Factory(Init init) { + return Props<IMessageToAgent>.Create(() => new ControllerMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); + } + + private readonly RpcConnectionToServer<IMessageToController> connection; private readonly AgentServices agent; private readonly CancellationTokenSource shutdownTokenSource; - public MessageListener(RpcConnectionToServer<IMessageToControllerListener> connection, AgentServices agent, CancellationTokenSource shutdownTokenSource) { - this.connection = connection; - this.agent = agent; - this.shutdownTokenSource = shutdownTokenSource; + private ControllerMessageHandlerActor(Init init) { + this.connection = init.Connection; + this.agent = init.Agent; + this.shutdownTokenSource = init.ShutdownTokenSource; + + ReceiveAsync<RegisterAgentSuccessMessage>(HandleRegisterAgentSuccess); + Receive<RegisterAgentFailureMessage>(HandleRegisterAgentFailure); + ReceiveAndReplyLater<ConfigureInstanceMessage, InstanceActionResult<ConfigureInstanceResult>>(HandleConfigureInstance); + ReceiveAndReplyLater<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(HandleLaunchInstance); + ReceiveAndReplyLater<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(HandleStopInstance); + ReceiveAndReplyLater<SendCommandToInstanceMessage, InstanceActionResult<SendCommandToInstanceResult>>(HandleSendCommandToInstance); + Receive<ReplyMessage>(HandleReply); } - public async Task<NoReply> HandleRegisterAgentSuccess(RegisterAgentSuccessMessage message) { + private async Task HandleRegisterAgentSuccess(RegisterAgentSuccessMessage message) { Logger.Information("Agent authentication successful."); void ShutdownAfterConfigurationFailed(Guid instanceGuid, InstanceConfiguration configuration) { @@ -36,7 +50,7 @@ public sealed class MessageListener : IMessageToAgentListener { var result = await HandleConfigureInstance(configureInstanceMessage, alwaysReportStatus: true); if (!result.Is(ConfigureInstanceResult.Success)) { ShutdownAfterConfigurationFailed(configureInstanceMessage.InstanceGuid, configureInstanceMessage.Configuration); - return NoReply.Instance; + return; } } @@ -44,11 +58,9 @@ public sealed class MessageListener : IMessageToAgentListener { await connection.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All)); await agent.InstanceSessionManager.RefreshAgentStatus(); - - return NoReply.Instance; } - public Task<NoReply> HandleRegisterAgentFailure(RegisterAgentFailureMessage message) { + private void HandleRegisterAgentFailure(RegisterAgentFailureMessage message) { string errorMessage = message.FailureKind switch { RegisterAgentFailure.ConnectionAlreadyHasAnAgent => "This connection already has an associated agent.", RegisterAgentFailure.InvalidToken => "Invalid token.", @@ -59,32 +71,29 @@ public sealed class MessageListener : IMessageToAgentListener { PhantomLogger.Dispose(); Environment.Exit(1); - - return Task.FromResult(NoReply.Instance); } private Task<InstanceActionResult<ConfigureInstanceResult>> HandleConfigureInstance(ConfigureInstanceMessage message, bool alwaysReportStatus) { return agent.InstanceSessionManager.Configure(message.InstanceGuid, message.Configuration, message.LaunchProperties, message.LaunchNow, alwaysReportStatus); } - public async Task<InstanceActionResult<ConfigureInstanceResult>> HandleConfigureInstance(ConfigureInstanceMessage message) { + private async Task<InstanceActionResult<ConfigureInstanceResult>> HandleConfigureInstance(ConfigureInstanceMessage message) { return await HandleConfigureInstance(message, alwaysReportStatus: false); } - public async Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message) { + private async Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message) { return await agent.InstanceSessionManager.Launch(message.InstanceGuid); } - public async Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message) { + private async Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message) { return await agent.InstanceSessionManager.Stop(message.InstanceGuid, message.StopStrategy); } - public async Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) { + private async Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) { return await agent.InstanceSessionManager.SendCommand(message.InstanceGuid, message.Command); } - public Task<NoReply> HandleReply(ReplyMessage message) { + private void HandleReply(ReplyMessage message) { connection.Receive(message); - return Task.FromResult(NoReply.Instance); } } diff --git a/Agent/Phantom.Agent/Program.cs b/Agent/Phantom.Agent/Program.cs index 8fa74ab..e4156a9 100644 --- a/Agent/Phantom.Agent/Program.cs +++ b/Agent/Phantom.Agent/Program.cs @@ -7,6 +7,7 @@ using Phantom.Agent.Services.Rpc; using Phantom.Common.Data.Agent; using Phantom.Common.Messages.Agent; using Phantom.Common.Messages.Agent.ToController; +using Phantom.Utils.Actor; using Phantom.Utils.Logging; using Phantom.Utils.Rpc; using Phantom.Utils.Rpc.Sockets; @@ -51,15 +52,19 @@ try { PhantomLogger.Root.InformationHeading("Launching Phantom Panel agent..."); - var rpcConfiguration = new RpcConfiguration("Rpc", controllerHost, controllerPort, controllerCertificate); + var rpcConfiguration = new RpcConfiguration("Agent", controllerHost, controllerPort, controllerCertificate); var rpcSocket = RpcClientSocket.Connect(rpcConfiguration, AgentMessageRegistries.Definitions, new RegisterAgentMessage(agentToken, agentInfo)); var agentServices = new AgentServices(agentInfo, folders, new AgentServiceConfiguration(maxConcurrentBackupCompressionTasks), new ControllerConnection(rpcSocket.Connection)); await agentServices.Initialize(); + using var actorSystem = ActorSystemFactory.Create("Agent"); + + var rpcMessageHandlerInit = new ControllerMessageHandlerActor.Init(rpcSocket.Connection, agentServices, shutdownCancellationTokenSource); + var rpcMessageHandlerActor = actorSystem.ActorOf(ControllerMessageHandlerActor.Factory(rpcMessageHandlerInit), "ControllerMessageHandler"); + var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1); - var rpcMessageListener = new MessageListener(rpcSocket.Connection, agentServices, shutdownCancellationTokenSource); - var rpcTask = RpcClientRuntime.Launch(rpcSocket, rpcMessageListener, rpcDisconnectSemaphore, shutdownCancellationToken); + var rpcTask = RpcClientRuntime.Launch(rpcSocket, rpcMessageHandlerActor, rpcDisconnectSemaphore, shutdownCancellationToken); try { await rpcTask.WaitAsync(shutdownCancellationToken); } finally { diff --git a/Common/Phantom.Common.Messages.Agent/AgentMessageRegistries.cs b/Common/Phantom.Common.Messages.Agent/AgentMessageRegistries.cs index a6607f7..c93cb57 100644 --- a/Common/Phantom.Common.Messages.Agent/AgentMessageRegistries.cs +++ b/Common/Phantom.Common.Messages.Agent/AgentMessageRegistries.cs @@ -8,10 +8,10 @@ using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent; public static class AgentMessageRegistries { - public static MessageRegistry<IMessageToAgentListener> ToAgent { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToAgent))); - public static MessageRegistry<IMessageToControllerListener> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController))); + public static MessageRegistry<IMessageToAgent> ToAgent { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToAgent))); + public static MessageRegistry<IMessageToController> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController))); - public static IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> Definitions { get; } = new MessageDefinitions(); + public static IMessageDefinitions<IMessageToAgent, IMessageToController, ReplyMessage> Definitions { get; } = new MessageDefinitions(); static AgentMessageRegistries() { ToAgent.Add<RegisterAgentSuccessMessage>(0); @@ -33,13 +33,9 @@ public static class AgentMessageRegistries { ToController.Add<ReplyMessage>(127); } - private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> { - public MessageRegistry<IMessageToAgentListener> ToClient => ToAgent; - public MessageRegistry<IMessageToControllerListener> ToServer => ToController; - - public bool IsRegistrationMessage(Type messageType) { - return messageType == typeof(RegisterAgentMessage); - } + private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgent, IMessageToController, ReplyMessage> { + public MessageRegistry<IMessageToAgent> ToClient => ToAgent; + public MessageRegistry<IMessageToController> ToServer => ToController; public ReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply) { return new ReplyMessage(sequenceId, serializedReply); diff --git a/Common/Phantom.Common.Messages.Agent/BiDirectional/ReplyMessage.cs b/Common/Phantom.Common.Messages.Agent/BiDirectional/ReplyMessage.cs index b24c9c6..972c254 100644 --- a/Common/Phantom.Common.Messages.Agent/BiDirectional/ReplyMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/BiDirectional/ReplyMessage.cs @@ -7,17 +7,4 @@ namespace Phantom.Common.Messages.Agent.BiDirectional; public sealed partial record ReplyMessage( [property: MemoryPackOrder(0)] uint SequenceId, [property: MemoryPackOrder(1)] byte[] SerializedReply -) : IMessageToController, IMessageToAgent, IReply { - private static readonly MessageQueueKey MessageQueueKey = new ("Reply"); - - [MemoryPackIgnore] - public MessageQueueKey QueueKey => MessageQueueKey; - - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleReply(this); - } - - public Task<NoReply> Accept(IMessageToAgentListener listener) { - return listener.HandleReply(this); - } -} +) : IMessageToController, IMessageToAgent, IReply; diff --git a/Common/Phantom.Common.Messages.Agent/IMessageToAgent.cs b/Common/Phantom.Common.Messages.Agent/IMessageToAgent.cs index 07ef707..17c076a 100644 --- a/Common/Phantom.Common.Messages.Agent/IMessageToAgent.cs +++ b/Common/Phantom.Common.Messages.Agent/IMessageToAgent.cs @@ -1,12 +1,3 @@ -using Phantom.Utils.Rpc.Message; +namespace Phantom.Common.Messages.Agent; -namespace Phantom.Common.Messages.Agent; - -public interface IMessageToAgent<TReply> : IMessage<IMessageToAgentListener, TReply> { - MessageQueueKey IMessage<IMessageToAgentListener, TReply>.QueueKey => IMessageToAgent.DefaultQueueKey; -} - -public interface IMessageToAgent : IMessageToAgent<NoReply> { - internal static readonly MessageQueueKey DefaultQueueKey = new ("Agent.Default"); - MessageQueueKey IMessage<IMessageToAgentListener, NoReply>.QueueKey => DefaultQueueKey; -} +public interface IMessageToAgent {} diff --git a/Common/Phantom.Common.Messages.Agent/IMessageToAgentListener.cs b/Common/Phantom.Common.Messages.Agent/IMessageToAgentListener.cs deleted file mode 100644 index 5ee7b51..0000000 --- a/Common/Phantom.Common.Messages.Agent/IMessageToAgentListener.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Phantom.Common.Data.Replies; -using Phantom.Common.Messages.Agent.BiDirectional; -using Phantom.Common.Messages.Agent.ToAgent; -using Phantom.Utils.Rpc.Message; - -namespace Phantom.Common.Messages.Agent; - -public interface IMessageToAgentListener { - Task<NoReply> HandleRegisterAgentSuccess(RegisterAgentSuccessMessage message); - Task<NoReply> HandleRegisterAgentFailure(RegisterAgentFailureMessage message); - Task<InstanceActionResult<ConfigureInstanceResult>> HandleConfigureInstance(ConfigureInstanceMessage message); - Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message); - Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message); - Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message); - Task<NoReply> HandleReply(ReplyMessage message); -} diff --git a/Common/Phantom.Common.Messages.Agent/IMessageToController.cs b/Common/Phantom.Common.Messages.Agent/IMessageToController.cs index a9bff1d..d1df318 100644 --- a/Common/Phantom.Common.Messages.Agent/IMessageToController.cs +++ b/Common/Phantom.Common.Messages.Agent/IMessageToController.cs @@ -1,12 +1,3 @@ -using Phantom.Utils.Rpc.Message; +namespace Phantom.Common.Messages.Agent; -namespace Phantom.Common.Messages.Agent; - -public interface IMessageToController<TReply> : IMessage<IMessageToControllerListener, TReply> { - MessageQueueKey IMessage<IMessageToControllerListener, TReply>.QueueKey => IMessageToController.DefaultQueueKey; -} - -public interface IMessageToController : IMessageToController<NoReply> { - internal static readonly MessageQueueKey DefaultQueueKey = new ("Agent.Default"); - MessageQueueKey IMessage<IMessageToControllerListener, NoReply>.QueueKey => DefaultQueueKey; -} +public interface IMessageToController {} diff --git a/Common/Phantom.Common.Messages.Agent/IMessageToControllerListener.cs b/Common/Phantom.Common.Messages.Agent/IMessageToControllerListener.cs deleted file mode 100644 index 76ebf8d..0000000 --- a/Common/Phantom.Common.Messages.Agent/IMessageToControllerListener.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Phantom.Common.Messages.Agent.BiDirectional; -using Phantom.Common.Messages.Agent.ToController; -using Phantom.Utils.Rpc.Message; - -namespace Phantom.Common.Messages.Agent; - -public interface IMessageToControllerListener { - Task<NoReply> HandleRegisterAgent(RegisterAgentMessage message); - Task<NoReply> HandleUnregisterAgent(UnregisterAgentMessage message); - Task<NoReply> HandleAgentIsAlive(AgentIsAliveMessage message); - Task<NoReply> HandleAdvertiseJavaRuntimes(AdvertiseJavaRuntimesMessage message); - Task<NoReply> HandleReportAgentStatus(ReportAgentStatusMessage message); - Task<NoReply> HandleReportInstanceStatus(ReportInstanceStatusMessage message); - Task<NoReply> HandleReportInstanceEvent(ReportInstanceEventMessage message); - Task<NoReply> HandleInstanceOutput(InstanceOutputMessage message); - Task<NoReply> HandleReply(ReplyMessage message); -} diff --git a/Common/Phantom.Common.Messages.Agent/ToAgent/ConfigureInstanceMessage.cs b/Common/Phantom.Common.Messages.Agent/ToAgent/ConfigureInstanceMessage.cs index cc13f29..8d4e69f 100644 --- a/Common/Phantom.Common.Messages.Agent/ToAgent/ConfigureInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToAgent/ConfigureInstanceMessage.cs @@ -1,6 +1,7 @@ using MemoryPack; using Phantom.Common.Data.Instance; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Agent.ToAgent; @@ -10,8 +11,4 @@ public sealed partial record ConfigureInstanceMessage( [property: MemoryPackOrder(1)] InstanceConfiguration Configuration, [property: MemoryPackOrder(2)] InstanceLaunchProperties LaunchProperties, [property: MemoryPackOrder(3)] bool LaunchNow = false -) : IMessageToAgent<InstanceActionResult<ConfigureInstanceResult>> { - public Task<InstanceActionResult<ConfigureInstanceResult>> Accept(IMessageToAgentListener listener) { - return listener.HandleConfigureInstance(this); - } -} +) : IMessageToAgent, ICanReply<InstanceActionResult<ConfigureInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Agent/ToAgent/LaunchInstanceMessage.cs b/Common/Phantom.Common.Messages.Agent/ToAgent/LaunchInstanceMessage.cs index 3e45552..072cb1b 100644 --- a/Common/Phantom.Common.Messages.Agent/ToAgent/LaunchInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToAgent/LaunchInstanceMessage.cs @@ -1,13 +1,10 @@ using MemoryPack; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Agent.ToAgent; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record LaunchInstanceMessage( [property: MemoryPackOrder(0)] Guid InstanceGuid -) : IMessageToAgent<InstanceActionResult<LaunchInstanceResult>> { - public Task<InstanceActionResult<LaunchInstanceResult>> Accept(IMessageToAgentListener listener) { - return listener.HandleLaunchInstance(this); - } -} +) : IMessageToAgent, ICanReply<InstanceActionResult<LaunchInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentFailureMessage.cs b/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentFailureMessage.cs index 7779df0..194b21b 100644 --- a/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentFailureMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentFailureMessage.cs @@ -1,14 +1,9 @@ using MemoryPack; using Phantom.Common.Data.Replies; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToAgent; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record RegisterAgentFailureMessage( [property: MemoryPackOrder(0)] RegisterAgentFailure FailureKind -) : IMessageToAgent { - public Task<NoReply> Accept(IMessageToAgentListener listener) { - return listener.HandleRegisterAgentFailure(this); - } -} +) : IMessageToAgent; diff --git a/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentSuccessMessage.cs b/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentSuccessMessage.cs index 95cbf0d..c8ac82a 100644 --- a/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentSuccessMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToAgent/RegisterAgentSuccessMessage.cs @@ -1,14 +1,9 @@ using System.Collections.Immutable; using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToAgent; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record RegisterAgentSuccessMessage( [property: MemoryPackOrder(0)] ImmutableArray<ConfigureInstanceMessage> InitialInstanceConfigurations -) : IMessageToAgent { - public Task<NoReply> Accept(IMessageToAgentListener listener) { - return listener.HandleRegisterAgentSuccess(this); - } -} +) : IMessageToAgent; diff --git a/Common/Phantom.Common.Messages.Agent/ToAgent/SendCommandToInstanceMessage.cs b/Common/Phantom.Common.Messages.Agent/ToAgent/SendCommandToInstanceMessage.cs index 4322e1c..26fccef 100644 --- a/Common/Phantom.Common.Messages.Agent/ToAgent/SendCommandToInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToAgent/SendCommandToInstanceMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Agent.ToAgent; @@ -7,8 +8,4 @@ namespace Phantom.Common.Messages.Agent.ToAgent; public sealed partial record SendCommandToInstanceMessage( [property: MemoryPackOrder(0)] Guid InstanceGuid, [property: MemoryPackOrder(1)] string Command -) : IMessageToAgent<InstanceActionResult<SendCommandToInstanceResult>> { - public Task<InstanceActionResult<SendCommandToInstanceResult>> Accept(IMessageToAgentListener listener) { - return listener.HandleSendCommandToInstance(this); - } -} +) : IMessageToAgent, ICanReply<InstanceActionResult<SendCommandToInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Agent/ToAgent/StopInstanceMessage.cs b/Common/Phantom.Common.Messages.Agent/ToAgent/StopInstanceMessage.cs index d268992..55b1e20 100644 --- a/Common/Phantom.Common.Messages.Agent/ToAgent/StopInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToAgent/StopInstanceMessage.cs @@ -1,6 +1,7 @@ using MemoryPack; using Phantom.Common.Data.Minecraft; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Agent.ToAgent; @@ -8,8 +9,4 @@ namespace Phantom.Common.Messages.Agent.ToAgent; public sealed partial record StopInstanceMessage( [property: MemoryPackOrder(0)] Guid InstanceGuid, [property: MemoryPackOrder(1)] MinecraftStopStrategy StopStrategy -) : IMessageToAgent<InstanceActionResult<StopInstanceResult>> { - public Task<InstanceActionResult<StopInstanceResult>> Accept(IMessageToAgentListener listener) { - return listener.HandleStopInstance(this); - } -} +) : IMessageToAgent, ICanReply<InstanceActionResult<StopInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/AdvertiseJavaRuntimesMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/AdvertiseJavaRuntimesMessage.cs index 83bb5d6..71e8d5b 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/AdvertiseJavaRuntimesMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/AdvertiseJavaRuntimesMessage.cs @@ -1,15 +1,10 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Java; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record AdvertiseJavaRuntimesMessage( [property: MemoryPackOrder(0)] ImmutableArray<TaggedJavaRuntime> Runtimes -) : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleAdvertiseJavaRuntimes(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/AgentIsAliveMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/AgentIsAliveMessage.cs index 4923c9f..b394b5f 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/AgentIsAliveMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/AgentIsAliveMessage.cs @@ -1,11 +1,6 @@ using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record AgentIsAliveMessage : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleAgentIsAlive(this); - } -} +public sealed partial record AgentIsAliveMessage : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/InstanceOutputMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/InstanceOutputMessage.cs index 424288b..5613549 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/InstanceOutputMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/InstanceOutputMessage.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; @@ -8,13 +7,4 @@ namespace Phantom.Common.Messages.Agent.ToController; public sealed partial record InstanceOutputMessage( [property: MemoryPackOrder(0)] Guid InstanceGuid, [property: MemoryPackOrder(1)] ImmutableArray<string> Lines -) : IMessageToController { - private static readonly MessageQueueKey MessageQueueKey = new ("Agent.InstanceOutput"); - - [MemoryPackIgnore] - public MessageQueueKey QueueKey => MessageQueueKey; - - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleInstanceOutput(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/RegisterAgentMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/RegisterAgentMessage.cs index fbadb61..3b7d8eb 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/RegisterAgentMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/RegisterAgentMessage.cs @@ -1,7 +1,6 @@ using MemoryPack; using Phantom.Common.Data; using Phantom.Common.Data.Agent; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; @@ -9,8 +8,4 @@ namespace Phantom.Common.Messages.Agent.ToController; public sealed partial record RegisterAgentMessage( [property: MemoryPackOrder(0)] AuthToken AuthToken, [property: MemoryPackOrder(1)] AgentInfo AgentInfo -) : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleRegisterAgent(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/ReportAgentStatusMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/ReportAgentStatusMessage.cs index 2092c4f..4560e0a 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/ReportAgentStatusMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/ReportAgentStatusMessage.cs @@ -1,6 +1,5 @@ using MemoryPack; using Phantom.Common.Data; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; @@ -8,8 +7,4 @@ namespace Phantom.Common.Messages.Agent.ToController; public sealed partial record ReportAgentStatusMessage( [property: MemoryPackOrder(0)] int RunningInstanceCount, [property: MemoryPackOrder(1)] RamAllocationUnits RunningInstanceMemory -) : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleReportAgentStatus(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceEventMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceEventMessage.cs index 2aba0c3..836c7f7 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceEventMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceEventMessage.cs @@ -1,6 +1,5 @@ using MemoryPack; using Phantom.Common.Data.Instance; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; @@ -10,8 +9,4 @@ public sealed partial record ReportInstanceEventMessage( [property: MemoryPackOrder(1)] DateTime UtcTime, [property: MemoryPackOrder(2)] Guid InstanceGuid, [property: MemoryPackOrder(3)] IInstanceEvent Event -) : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleReportInstanceEvent(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceStatusMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceStatusMessage.cs index ca763a0..845eecf 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceStatusMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/ReportInstanceStatusMessage.cs @@ -1,6 +1,5 @@ using MemoryPack; using Phantom.Common.Data.Instance; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; @@ -8,8 +7,4 @@ namespace Phantom.Common.Messages.Agent.ToController; public sealed partial record ReportInstanceStatusMessage( [property: MemoryPackOrder(0)] Guid InstanceGuid, [property: MemoryPackOrder(1)] IInstanceStatus InstanceStatus -) : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleReportInstanceStatus(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Agent/ToController/UnregisterAgentMessage.cs b/Common/Phantom.Common.Messages.Agent/ToController/UnregisterAgentMessage.cs index bdb8b98..a0f4d2d 100644 --- a/Common/Phantom.Common.Messages.Agent/ToController/UnregisterAgentMessage.cs +++ b/Common/Phantom.Common.Messages.Agent/ToController/UnregisterAgentMessage.cs @@ -1,11 +1,6 @@ using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Agent.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record UnregisterAgentMessage : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleUnregisterAgent(this); - } -} +public sealed partial record UnregisterAgentMessage : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Web/BiDirectional/ReplyMessage.cs b/Common/Phantom.Common.Messages.Web/BiDirectional/ReplyMessage.cs index a0e9ca1..4e47258 100644 --- a/Common/Phantom.Common.Messages.Web/BiDirectional/ReplyMessage.cs +++ b/Common/Phantom.Common.Messages.Web/BiDirectional/ReplyMessage.cs @@ -7,12 +7,4 @@ namespace Phantom.Common.Messages.Web.BiDirectional; public sealed partial record ReplyMessage( [property: MemoryPackOrder(0)] uint SequenceId, [property: MemoryPackOrder(1)] byte[] SerializedReply -) : IMessageToController, IMessageToWeb, IReply { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleReply(this); - } - - public Task<NoReply> Accept(IMessageToWebListener listener) { - return listener.HandleReply(this); - } -} +) : IMessageToController, IMessageToWeb, IReply; diff --git a/Common/Phantom.Common.Messages.Web/IMessageToController.cs b/Common/Phantom.Common.Messages.Web/IMessageToController.cs index 73ef744..f80c43a 100644 --- a/Common/Phantom.Common.Messages.Web/IMessageToController.cs +++ b/Common/Phantom.Common.Messages.Web/IMessageToController.cs @@ -1,12 +1,3 @@ -using Phantom.Utils.Rpc.Message; +namespace Phantom.Common.Messages.Web; -namespace Phantom.Common.Messages.Web; - -public interface IMessageToController<TReply> : IMessage<IMessageToControllerListener, TReply> { - MessageQueueKey IMessage<IMessageToControllerListener, TReply>.QueueKey => IMessageToController.DefaultQueueKey; -} - -public interface IMessageToController : IMessageToController<NoReply> { - internal static readonly MessageQueueKey DefaultQueueKey = new ("Web.Default"); - MessageQueueKey IMessage<IMessageToControllerListener, NoReply>.QueueKey => DefaultQueueKey; -} +public interface IMessageToController {} diff --git a/Common/Phantom.Common.Messages.Web/IMessageToControllerListener.cs b/Common/Phantom.Common.Messages.Web/IMessageToControllerListener.cs deleted file mode 100644 index 49048e7..0000000 --- a/Common/Phantom.Common.Messages.Web/IMessageToControllerListener.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Immutable; -using Phantom.Common.Data.Java; -using Phantom.Common.Data.Minecraft; -using Phantom.Common.Data.Replies; -using Phantom.Common.Data.Web.AuditLog; -using Phantom.Common.Data.Web.EventLog; -using Phantom.Common.Data.Web.Instance; -using Phantom.Common.Data.Web.Users; -using Phantom.Common.Messages.Web.BiDirectional; -using Phantom.Common.Messages.Web.ToController; -using Phantom.Utils.Rpc.Message; - -namespace Phantom.Common.Messages.Web; - -public interface IMessageToControllerListener { - Task<NoReply> HandleRegisterWeb(RegisterWebMessage message); - Task<NoReply> HandleUnregisterWeb(UnregisterWebMessage message); - Task<LogInSuccess?> HandleLogIn(LogInMessage message); - Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message); - Task<CreateUserResult> HandleCreateUser(CreateUserMessage message); - Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message); - Task<ImmutableArray<RoleInfo>> HandleGetRoles(GetRolesMessage message); - Task<ImmutableDictionary<Guid, ImmutableArray<Guid>>> HandleGetUserRoles(GetUserRolesMessage message); - Task<ChangeUserRolesResult> HandleChangeUserRoles(ChangeUserRolesMessage message); - Task<DeleteUserResult> HandleDeleteUser(DeleteUserMessage message); - Task<InstanceActionResult<CreateOrUpdateInstanceResult>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message); - Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message); - Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message); - Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message); - Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message); - Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> HandleGetAgentJavaRuntimes(GetAgentJavaRuntimesMessage message); - Task<ImmutableArray<AuditLogItem>> HandleGetAuditLog(GetAuditLogMessage message); - Task<ImmutableArray<EventLogItem>> HandleGetEventLog(GetEventLogMessage message); - Task<NoReply> HandleReply(ReplyMessage message); -} diff --git a/Common/Phantom.Common.Messages.Web/IMessageToWeb.cs b/Common/Phantom.Common.Messages.Web/IMessageToWeb.cs index e7b95ce..420a3d1 100644 --- a/Common/Phantom.Common.Messages.Web/IMessageToWeb.cs +++ b/Common/Phantom.Common.Messages.Web/IMessageToWeb.cs @@ -1,12 +1,3 @@ -using Phantom.Utils.Rpc.Message; +namespace Phantom.Common.Messages.Web; -namespace Phantom.Common.Messages.Web; - -public interface IMessageToWeb<TReply> : IMessage<IMessageToWebListener, TReply> { - MessageQueueKey IMessage<IMessageToWebListener, TReply>.QueueKey => IMessageToWeb.DefaultQueueKey; -} - -public interface IMessageToWeb : IMessageToWeb<NoReply> { - internal static readonly MessageQueueKey DefaultQueueKey = new ("Web.Default"); - MessageQueueKey IMessage<IMessageToWebListener, NoReply>.QueueKey => DefaultQueueKey; -} +public interface IMessageToWeb {} diff --git a/Common/Phantom.Common.Messages.Web/IMessageToWebListener.cs b/Common/Phantom.Common.Messages.Web/IMessageToWebListener.cs deleted file mode 100644 index eef5392..0000000 --- a/Common/Phantom.Common.Messages.Web/IMessageToWebListener.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Phantom.Common.Messages.Web.BiDirectional; -using Phantom.Common.Messages.Web.ToWeb; -using Phantom.Utils.Rpc.Message; - -namespace Phantom.Common.Messages.Web; - -public interface IMessageToWebListener { - Task<NoReply> HandleRegisterWebResult(RegisterWebResultMessage message); - Task<NoReply> HandleRefreshAgents(RefreshAgentsMessage message); - Task<NoReply> HandleRefreshInstances(RefreshInstancesMessage message); - Task<NoReply> HandleInstanceOutput(InstanceOutputMessage message); - Task<NoReply> HandleReply(ReplyMessage message); -} diff --git a/Common/Phantom.Common.Messages.Web/ToController/ChangeUserRolesMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/ChangeUserRolesMessage.cs index 9738705..20b267e 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/ChangeUserRolesMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/ChangeUserRolesMessage.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -10,8 +11,4 @@ public sealed partial record ChangeUserRolesMessage( [property: MemoryPackOrder(1)] Guid SubjectUserGuid, [property: MemoryPackOrder(2)] ImmutableHashSet<Guid> AddToRoleGuids, [property: MemoryPackOrder(3)] ImmutableHashSet<Guid> RemoveFromRoleGuids -) : IMessageToController<ChangeUserRolesResult> { - public Task<ChangeUserRolesResult> Accept(IMessageToControllerListener listener) { - return listener.HandleChangeUserRoles(this); - } -} +) : IMessageToController, ICanReply<ChangeUserRolesResult>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateAdministratorUserMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateAdministratorUserMessage.cs index 0e9c607..c464aef 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateAdministratorUserMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateAdministratorUserMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -7,8 +8,4 @@ namespace Phantom.Common.Messages.Web.ToController; public sealed partial record CreateOrUpdateAdministratorUserMessage( [property: MemoryPackOrder(0)] string Username, [property: MemoryPackOrder(1)] string Password -) : IMessageToController<CreateOrUpdateAdministratorUserResult> { - public Task<CreateOrUpdateAdministratorUserResult> Accept(IMessageToControllerListener listener) { - return listener.HandleCreateOrUpdateAdministratorUser(this); - } -} +) : IMessageToController, ICanReply<CreateOrUpdateAdministratorUserResult>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateInstanceMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateInstanceMessage.cs index fc3fea4..8150fe1 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/CreateOrUpdateInstanceMessage.cs @@ -2,6 +2,7 @@ using Phantom.Common.Data.Instance; using Phantom.Common.Data.Replies; using Phantom.Common.Data.Web.Instance; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -10,8 +11,4 @@ public sealed partial record CreateOrUpdateInstanceMessage( [property: MemoryPackOrder(0)] Guid LoggedInUserGuid, [property: MemoryPackOrder(1)] Guid InstanceGuid, [property: MemoryPackOrder(2)] InstanceConfiguration Configuration -) : IMessageToController<InstanceActionResult<CreateOrUpdateInstanceResult>> { - public Task<InstanceActionResult<CreateOrUpdateInstanceResult>> Accept(IMessageToControllerListener listener) { - return listener.HandleCreateOrUpdateInstance(this); - } -} +) : IMessageToController, ICanReply<InstanceActionResult<CreateOrUpdateInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/CreateUserMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/CreateUserMessage.cs index 85bd86e..dbbdc86 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/CreateUserMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/CreateUserMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -8,8 +9,4 @@ public sealed partial record CreateUserMessage( [property: MemoryPackOrder(0)] Guid LoggedInUserGuid, [property: MemoryPackOrder(1)] string Username, [property: MemoryPackOrder(2)] string Password -) : IMessageToController<CreateUserResult> { - public Task<CreateUserResult> Accept(IMessageToControllerListener listener) { - return listener.HandleCreateUser(this); - } -} +) : IMessageToController, ICanReply<CreateUserResult>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/DeleteUserMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/DeleteUserMessage.cs index 35728eb..378904b 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/DeleteUserMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/DeleteUserMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -7,8 +8,4 @@ namespace Phantom.Common.Messages.Web.ToController; public sealed partial record DeleteUserMessage( [property: MemoryPackOrder(0)] Guid LoggedInUserGuid, [property: MemoryPackOrder(1)] Guid SubjectUserGuid -) : IMessageToController<DeleteUserResult> { - public Task<DeleteUserResult> Accept(IMessageToControllerListener listener) { - return listener.HandleDeleteUser(this); - } -} +) : IMessageToController, ICanReply<DeleteUserResult>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetAgentJavaRuntimesMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetAgentJavaRuntimesMessage.cs index 4cead49..6b673e7 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetAgentJavaRuntimesMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetAgentJavaRuntimesMessage.cs @@ -1,12 +1,9 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Java; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record GetAgentJavaRuntimesMessage : IMessageToController<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> { - public Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetAgentJavaRuntimes(this); - } -} +public sealed partial record GetAgentJavaRuntimesMessage : IMessageToController, ICanReply<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetAuditLogMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetAuditLogMessage.cs index ad42f4a..2e09594 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetAuditLogMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetAuditLogMessage.cs @@ -1,14 +1,11 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.AuditLog; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record GetAuditLogMessage( [property: MemoryPackOrder(0)] int Count -) : IMessageToController<ImmutableArray<AuditLogItem>> { - public Task<ImmutableArray<AuditLogItem>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetAuditLog(this); - } -} +) : IMessageToController, ICanReply<ImmutableArray<AuditLogItem>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetEventLogMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetEventLogMessage.cs index 95e5b8d..58a1dbb 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetEventLogMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetEventLogMessage.cs @@ -1,14 +1,11 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.EventLog; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record GetEventLogMessage( [property: MemoryPackOrder(0)] int Count -) : IMessageToController<ImmutableArray<EventLogItem>> { - public Task<ImmutableArray<EventLogItem>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetEventLog(this); - } -} +) : IMessageToController, ICanReply<ImmutableArray<EventLogItem>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetMinecraftVersionsMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetMinecraftVersionsMessage.cs index 6e4b67a..af07056 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetMinecraftVersionsMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetMinecraftVersionsMessage.cs @@ -1,12 +1,9 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Minecraft; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record GetMinecraftVersionsMessage : IMessageToController<ImmutableArray<MinecraftVersion>> { - public Task<ImmutableArray<MinecraftVersion>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetMinecraftVersions(this); - } -} +public sealed partial record GetMinecraftVersionsMessage : IMessageToController, ICanReply<ImmutableArray<MinecraftVersion>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetRolesMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetRolesMessage.cs index d5b359e..b59d86e 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetRolesMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetRolesMessage.cs @@ -1,12 +1,9 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record GetRolesMessage : IMessageToController<ImmutableArray<RoleInfo>> { - public Task<ImmutableArray<RoleInfo>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetRoles(this); - } -} +public sealed partial record GetRolesMessage : IMessageToController, ICanReply<ImmutableArray<RoleInfo>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetUserRolesMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetUserRolesMessage.cs index b8d6575..f644bc3 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetUserRolesMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetUserRolesMessage.cs @@ -1,13 +1,10 @@ using System.Collections.Immutable; using MemoryPack; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record GetUserRolesMessage( [property: MemoryPackOrder(0)] ImmutableHashSet<Guid> UserGuids -) : IMessageToController<ImmutableDictionary<Guid, ImmutableArray<Guid>>> { - public Task<ImmutableDictionary<Guid, ImmutableArray<Guid>>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetUserRoles(this); - } -} +) : IMessageToController, ICanReply<ImmutableDictionary<Guid, ImmutableArray<Guid>>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/GetUsersMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/GetUsersMessage.cs index 05b6ee1..51c185b 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/GetUsersMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/GetUsersMessage.cs @@ -1,12 +1,9 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record GetUsersMessage : IMessageToController<ImmutableArray<UserInfo>> { - public Task<ImmutableArray<UserInfo>> Accept(IMessageToControllerListener listener) { - return listener.HandleGetUsers(this); - } -} +public sealed partial record GetUsersMessage : IMessageToController, ICanReply<ImmutableArray<UserInfo>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/LaunchInstanceMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/LaunchInstanceMessage.cs index a30e98c..deed50b 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/LaunchInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/LaunchInstanceMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -8,8 +9,4 @@ public sealed partial record LaunchInstanceMessage( [property: MemoryPackOrder(0)] Guid LoggedInUserGuid, [property: MemoryPackOrder(1)] Guid AgentGuid, [property: MemoryPackOrder(2)] Guid InstanceGuid -) : IMessageToController<InstanceActionResult<LaunchInstanceResult>> { - public Task<InstanceActionResult<LaunchInstanceResult>> Accept(IMessageToControllerListener listener) { - return listener.HandleLaunchInstance(this); - } -} +) : IMessageToController, ICanReply<InstanceActionResult<LaunchInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/LogInMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/LogInMessage.cs index 5b94a0b..d3722d1 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/LogInMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/LogInMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Web.Users; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -7,8 +8,4 @@ namespace Phantom.Common.Messages.Web.ToController; public sealed partial record LogInMessage( [property: MemoryPackOrder(0)] string Username, [property: MemoryPackOrder(1)] string Password -) : IMessageToController<LogInSuccess?> { - public Task<LogInSuccess?> Accept(IMessageToControllerListener listener) { - return listener.HandleLogIn(this); - } -} +) : IMessageToController, ICanReply<LogInSuccess?>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/RegisterWebMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/RegisterWebMessage.cs index 6dfb29b..74e990e 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/RegisterWebMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/RegisterWebMessage.cs @@ -1,14 +1,9 @@ using MemoryPack; using Phantom.Common.Data; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record RegisterWebMessage( [property: MemoryPackOrder(0)] AuthToken AuthToken -) : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleRegisterWeb(this); - } -} +) : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Web/ToController/SendCommandToInstanceMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/SendCommandToInstanceMessage.cs index dc677d1..490aee9 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/SendCommandToInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/SendCommandToInstanceMessage.cs @@ -1,5 +1,6 @@ using MemoryPack; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -9,8 +10,4 @@ public sealed partial record SendCommandToInstanceMessage( [property: MemoryPackOrder(1)] Guid AgentGuid, [property: MemoryPackOrder(2)] Guid InstanceGuid, [property: MemoryPackOrder(3)] string Command -) : IMessageToController<InstanceActionResult<SendCommandToInstanceResult>> { - public Task<InstanceActionResult<SendCommandToInstanceResult>> Accept(IMessageToControllerListener listener) { - return listener.HandleSendCommandToInstance(this); - } -} +) : IMessageToController, ICanReply<InstanceActionResult<SendCommandToInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/StopInstanceMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/StopInstanceMessage.cs index c147afd..585c3e9 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/StopInstanceMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/StopInstanceMessage.cs @@ -1,6 +1,7 @@ using MemoryPack; using Phantom.Common.Data.Minecraft; using Phantom.Common.Data.Replies; +using Phantom.Utils.Actor; namespace Phantom.Common.Messages.Web.ToController; @@ -10,8 +11,4 @@ public sealed partial record StopInstanceMessage( [property: MemoryPackOrder(1)] Guid AgentGuid, [property: MemoryPackOrder(2)] Guid InstanceGuid, [property: MemoryPackOrder(3)] MinecraftStopStrategy StopStrategy -) : IMessageToController<InstanceActionResult<StopInstanceResult>> { - public Task<InstanceActionResult<StopInstanceResult>> Accept(IMessageToControllerListener listener) { - return listener.HandleStopInstance(this); - } -} +) : IMessageToController, ICanReply<InstanceActionResult<StopInstanceResult>>; diff --git a/Common/Phantom.Common.Messages.Web/ToController/UnregisterWebMessage.cs b/Common/Phantom.Common.Messages.Web/ToController/UnregisterWebMessage.cs index 1142448..523fef5 100644 --- a/Common/Phantom.Common.Messages.Web/ToController/UnregisterWebMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToController/UnregisterWebMessage.cs @@ -1,11 +1,6 @@ using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web.ToController; [MemoryPackable(GenerateType.VersionTolerant)] -public sealed partial record UnregisterWebMessage : IMessageToController { - public Task<NoReply> Accept(IMessageToControllerListener listener) { - return listener.HandleUnregisterWeb(this); - } -} +public sealed partial record UnregisterWebMessage : IMessageToController; diff --git a/Common/Phantom.Common.Messages.Web/ToWeb/InstanceOutputMessage.cs b/Common/Phantom.Common.Messages.Web/ToWeb/InstanceOutputMessage.cs index 5a32d72..f1618a8 100644 --- a/Common/Phantom.Common.Messages.Web/ToWeb/InstanceOutputMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToWeb/InstanceOutputMessage.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web.ToWeb; @@ -8,13 +7,4 @@ namespace Phantom.Common.Messages.Web.ToWeb; public sealed partial record InstanceOutputMessage( [property: MemoryPackOrder(0)] Guid InstanceGuid, [property: MemoryPackOrder(1)] ImmutableArray<string> Lines -) : IMessageToWeb { - private static readonly MessageQueueKey MessageQueueKey = new ("Web.InstanceOutput"); - - [MemoryPackIgnore] - public MessageQueueKey QueueKey => MessageQueueKey; - - public Task<NoReply> Accept(IMessageToWebListener listener) { - return listener.HandleInstanceOutput(this); - } -} +) : IMessageToWeb; diff --git a/Common/Phantom.Common.Messages.Web/ToWeb/RefreshAgentsMessage.cs b/Common/Phantom.Common.Messages.Web/ToWeb/RefreshAgentsMessage.cs index e718d96..76549d4 100644 --- a/Common/Phantom.Common.Messages.Web/ToWeb/RefreshAgentsMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToWeb/RefreshAgentsMessage.cs @@ -1,15 +1,10 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.Agent; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web.ToWeb; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record RefreshAgentsMessage( [property: MemoryPackOrder(0)] ImmutableArray<Agent> Agents -) : IMessageToWeb { - public Task<NoReply> Accept(IMessageToWebListener listener) { - return listener.HandleRefreshAgents(this); - } -} +) : IMessageToWeb; diff --git a/Common/Phantom.Common.Messages.Web/ToWeb/RefreshInstancesMessage.cs b/Common/Phantom.Common.Messages.Web/ToWeb/RefreshInstancesMessage.cs index d5ce8f8..c060588 100644 --- a/Common/Phantom.Common.Messages.Web/ToWeb/RefreshInstancesMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToWeb/RefreshInstancesMessage.cs @@ -1,15 +1,10 @@ using System.Collections.Immutable; using MemoryPack; using Phantom.Common.Data.Web.Instance; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web.ToWeb; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record RefreshInstancesMessage( [property: MemoryPackOrder(0)] ImmutableArray<Instance> Instances -) : IMessageToWeb { - public Task<NoReply> Accept(IMessageToWebListener listener) { - return listener.HandleRefreshInstances(this); - } -} +) : IMessageToWeb; diff --git a/Common/Phantom.Common.Messages.Web/ToWeb/RegisterWebResultMessage.cs b/Common/Phantom.Common.Messages.Web/ToWeb/RegisterWebResultMessage.cs index 807b2ec..04a43fb 100644 --- a/Common/Phantom.Common.Messages.Web/ToWeb/RegisterWebResultMessage.cs +++ b/Common/Phantom.Common.Messages.Web/ToWeb/RegisterWebResultMessage.cs @@ -1,13 +1,8 @@ using MemoryPack; -using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web.ToWeb; [MemoryPackable(GenerateType.VersionTolerant)] public sealed partial record RegisterWebResultMessage( [property: MemoryPackOrder(0)] bool Success -) : IMessageToWeb { - public Task<NoReply> Accept(IMessageToWebListener listener) { - return listener.HandleRegisterWebResult(this); - } -} +) : IMessageToWeb; diff --git a/Common/Phantom.Common.Messages.Web/WebMessageRegistries.cs b/Common/Phantom.Common.Messages.Web/WebMessageRegistries.cs index 972289c..cb97742 100644 --- a/Common/Phantom.Common.Messages.Web/WebMessageRegistries.cs +++ b/Common/Phantom.Common.Messages.Web/WebMessageRegistries.cs @@ -15,10 +15,10 @@ using Phantom.Utils.Rpc.Message; namespace Phantom.Common.Messages.Web; public static class WebMessageRegistries { - public static MessageRegistry<IMessageToControllerListener> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController))); - public static MessageRegistry<IMessageToWebListener> ToWeb { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToWeb))); + public static MessageRegistry<IMessageToController> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController))); + public static MessageRegistry<IMessageToWeb> ToWeb { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToWeb))); - public static IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener, ReplyMessage> Definitions { get; } = new MessageDefinitions(); + public static IMessageDefinitions<IMessageToWeb, IMessageToController, ReplyMessage> Definitions { get; } = new MessageDefinitions(); static WebMessageRegistries() { ToController.Add<RegisterWebMessage>(0); @@ -48,13 +48,9 @@ public static class WebMessageRegistries { ToWeb.Add<ReplyMessage>(127); } - private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener, ReplyMessage> { - public MessageRegistry<IMessageToWebListener> ToClient => ToWeb; - public MessageRegistry<IMessageToControllerListener> ToServer => ToController; - - public bool IsRegistrationMessage(Type messageType) { - return messageType == typeof(RegisterWebMessage); - } + private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWeb, IMessageToController, ReplyMessage> { + public MessageRegistry<IMessageToWeb> ToClient => ToWeb; + public MessageRegistry<IMessageToController> ToServer => ToController; public ReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply) { return new ReplyMessage(sequenceId, serializedReply); diff --git a/Controller/Phantom.Controller.Services/Agents/AgentActor.cs b/Controller/Phantom.Controller.Services/Agents/AgentActor.cs index 3c01cb6..e7984af 100644 --- a/Controller/Phantom.Controller.Services/Agents/AgentActor.cs +++ b/Controller/Phantom.Controller.Services/Agents/AgentActor.cs @@ -169,9 +169,9 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> { private sealed record InitializeCommand : ICommand; - public sealed record RegisterCommand(AgentConfiguration Configuration, RpcConnectionToClient<IMessageToAgentListener> Connection) : ICommand, ICanReply<ImmutableArray<ConfigureInstanceMessage>>; + public sealed record RegisterCommand(AgentConfiguration Configuration, RpcConnectionToClient<IMessageToAgent> Connection) : ICommand, ICanReply<ImmutableArray<ConfigureInstanceMessage>>; - public sealed record UnregisterCommand(RpcConnectionToClient<IMessageToAgentListener> Connection) : ICommand; + public sealed record UnregisterCommand(RpcConnectionToClient<IMessageToAgent> Connection) : ICommand; private sealed record RefreshConnectionStatusCommand : ICommand; diff --git a/Controller/Phantom.Controller.Services/Agents/AgentConnection.cs b/Controller/Phantom.Controller.Services/Agents/AgentConnection.cs index dec070e..999f9aa 100644 --- a/Controller/Phantom.Controller.Services/Agents/AgentConnection.cs +++ b/Controller/Phantom.Controller.Services/Agents/AgentConnection.cs @@ -1,4 +1,5 @@ using Phantom.Common.Messages.Agent; +using Phantom.Utils.Actor; using Phantom.Utils.Logging; using Phantom.Utils.Rpc.Runtime; using Serilog; @@ -11,14 +12,14 @@ sealed class AgentConnection { private readonly Guid agentGuid; private string agentName; - private RpcConnectionToClient<IMessageToAgentListener>? connection; + private RpcConnectionToClient<IMessageToAgent>? connection; public AgentConnection(Guid agentGuid, string agentName) { this.agentName = agentName; this.agentGuid = agentGuid; } - public void UpdateConnection(RpcConnectionToClient<IMessageToAgentListener> newConnection, string newAgentName) { + public void UpdateConnection(RpcConnectionToClient<IMessageToAgent> newConnection, string newAgentName) { lock (this) { connection?.Close(); connection = newConnection; @@ -26,7 +27,7 @@ sealed class AgentConnection { } } - public bool CloseIfSame(RpcConnectionToClient<IMessageToAgentListener> expected) { + public bool CloseIfSame(RpcConnectionToClient<IMessageToAgent> expected) { lock (this) { if (connection != null && connection.IsSame(expected)) { connection.Close(); @@ -48,7 +49,7 @@ sealed class AgentConnection { } } - public Task<TReply?> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToAgent<TReply> where TReply : class { + public Task<TReply?> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToAgent, ICanReply<TReply> where TReply : class { lock (this) { if (connection == null) { LogAgentOffline(); diff --git a/Controller/Phantom.Controller.Services/Agents/AgentManager.cs b/Controller/Phantom.Controller.Services/Agents/AgentManager.cs index 977da30..abb37b8 100644 --- a/Controller/Phantom.Controller.Services/Agents/AgentManager.cs +++ b/Controller/Phantom.Controller.Services/Agents/AgentManager.cs @@ -18,7 +18,7 @@ namespace Phantom.Controller.Services.Agents; sealed class AgentManager { private static readonly ILogger Logger = PhantomLogger.Create<AgentManager>(); - private readonly ActorSystem actorSystem; + private readonly IActorRefFactory actorSystem; private readonly AuthToken authToken; private readonly ControllerState controllerState; private readonly MinecraftVersions minecraftVersions; @@ -28,7 +28,7 @@ sealed class AgentManager { private readonly ConcurrentDictionary<Guid, ActorRef<AgentActor.ICommand>> agentsByGuid = new (); private readonly Func<Guid, AgentConfiguration, ActorRef<AgentActor.ICommand>> addAgentActorFactory; - public AgentManager(ActorSystem actorSystem, AuthToken authToken, ControllerState controllerState, MinecraftVersions minecraftVersions, IDbContextProvider dbProvider, CancellationToken cancellationToken) { + public AgentManager(IActorRefFactory actorSystem, AuthToken authToken, ControllerState controllerState, MinecraftVersions minecraftVersions, IDbContextProvider dbProvider, CancellationToken cancellationToken) { this.actorSystem = actorSystem; this.authToken = authToken; this.controllerState = controllerState; @@ -58,15 +58,15 @@ sealed class AgentManager { } } - public async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, RpcConnectionToClient<IMessageToAgentListener> connection) { + public async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, RpcConnectionToClient<IMessageToAgent> connection) { if (!this.authToken.FixedTimeEquals(authToken)) { await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.InvalidToken)); return false; } var agentProperties = AgentConfiguration.From(agentInfo); - var agentActorRef = agentsByGuid.GetOrAdd(agentInfo.AgentGuid, addAgentActorFactory, agentProperties); - var configureInstanceMessages = await agentActorRef.Request(new AgentActor.RegisterCommand(agentProperties, connection), cancellationToken); + var agentActor = agentsByGuid.GetOrAdd(agentInfo.AgentGuid, addAgentActorFactory, agentProperties); + var configureInstanceMessages = await agentActor.Request(new AgentActor.RegisterCommand(agentProperties, connection), cancellationToken); await connection.Send(new RegisterAgentSuccessMessage(configureInstanceMessages)); return true; diff --git a/Controller/Phantom.Controller.Services/ControllerServices.cs b/Controller/Phantom.Controller.Services/ControllerServices.cs index fc154ac..e3839ad 100644 --- a/Controller/Phantom.Controller.Services/ControllerServices.cs +++ b/Controller/Phantom.Controller.Services/ControllerServices.cs @@ -1,7 +1,9 @@ using Akka.Actor; using Phantom.Common.Data; using Phantom.Common.Messages.Agent; +using Phantom.Common.Messages.Agent.ToController; using Phantom.Common.Messages.Web; +using Phantom.Common.Messages.Web.ToController; using Phantom.Controller.Database; using Phantom.Controller.Minecraft; using Phantom.Controller.Services.Agents; @@ -13,10 +15,14 @@ using Phantom.Utils.Actor; using Phantom.Utils.Logging; using Phantom.Utils.Rpc.Runtime; using Phantom.Utils.Tasks; +using IMessageFromAgentToController = Phantom.Common.Messages.Agent.IMessageToController; +using IMessageFromWebToController = Phantom.Common.Messages.Web.IMessageToController; namespace Phantom.Controller.Services; -public sealed class ControllerServices : IAsyncDisposable { +public sealed class ControllerServices : IDisposable { + public ActorSystem ActorSystem { get; } + private TaskManager TaskManager { get; } private ControllerState ControllerState { get; } private MinecraftVersions MinecraftVersions { get; } @@ -32,24 +38,24 @@ public sealed class ControllerServices : IAsyncDisposable { private UserRoleManager UserRoleManager { get; } private UserLoginManager UserLoginManager { get; } private AuditLogManager AuditLogManager { get; } - - private readonly ActorSystem actorSystem; + + public IRegistrationHandler<IMessageToAgent, IMessageFromAgentToController, RegisterAgentMessage> AgentRegistrationHandler { get; } + public IRegistrationHandler<IMessageToWeb, IMessageFromWebToController, RegisterWebMessage> WebRegistrationHandler { get; } + private readonly IDbContextProvider dbProvider; - private readonly AuthToken webAuthToken; private readonly CancellationToken cancellationToken; - + public ControllerServices(IDbContextProvider dbProvider, AuthToken agentAuthToken, AuthToken webAuthToken, CancellationToken shutdownCancellationToken) { this.dbProvider = dbProvider; - this.webAuthToken = webAuthToken; this.cancellationToken = shutdownCancellationToken; - this.actorSystem = ActorSystemFactory.Create("Controller"); + this.ActorSystem = ActorSystemFactory.Create("Controller"); this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, ControllerServices>()); this.ControllerState = new ControllerState(); this.MinecraftVersions = new MinecraftVersions(); - this.AgentManager = new AgentManager(actorSystem, agentAuthToken, ControllerState, MinecraftVersions, dbProvider, cancellationToken); + this.AgentManager = new AgentManager(ActorSystem, agentAuthToken, ControllerState, MinecraftVersions, dbProvider, cancellationToken); this.InstanceLogManager = new InstanceLogManager(); this.UserManager = new UserManager(dbProvider); @@ -60,14 +66,9 @@ public sealed class ControllerServices : IAsyncDisposable { this.UserLoginManager = new UserLoginManager(UserManager, PermissionManager); this.AuditLogManager = new AuditLogManager(dbProvider); this.EventLogManager = new EventLogManager(dbProvider, TaskManager, shutdownCancellationToken); - } - - public AgentMessageListener CreateAgentMessageListener(RpcConnectionToClient<IMessageToAgentListener> connection) { - return new AgentMessageListener(connection, AgentManager, InstanceLogManager, EventLogManager, cancellationToken); - } - - public WebMessageListener CreateWebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection) { - return new WebMessageListener(actorSystem, connection, webAuthToken, ControllerState, UserManager, RoleManager, UserRoleManager, UserLoginManager, AuditLogManager, AgentManager, InstanceLogManager, MinecraftVersions, EventLogManager); + + this.AgentRegistrationHandler = new AgentRegistrationHandler(AgentManager, InstanceLogManager, EventLogManager); + this.WebRegistrationHandler = new WebRegistrationHandler(webAuthToken, ControllerState, InstanceLogManager, UserManager, RoleManager, UserRoleManager, UserLoginManager, AuditLogManager, AgentManager, MinecraftVersions, EventLogManager); } public async Task Initialize() { @@ -77,8 +78,7 @@ public sealed class ControllerServices : IAsyncDisposable { await RoleManager.Initialize(); } - public async ValueTask DisposeAsync() { - await actorSystem.Terminate(); - actorSystem.Dispose(); + public void Dispose() { + ActorSystem.Dispose(); } } diff --git a/Controller/Phantom.Controller.Services/Instances/InstanceActor.cs b/Controller/Phantom.Controller.Services/Instances/InstanceActor.cs index f70d8f1..1f5f22a 100644 --- a/Controller/Phantom.Controller.Services/Instances/InstanceActor.cs +++ b/Controller/Phantom.Controller.Services/Instances/InstanceActor.cs @@ -11,13 +11,13 @@ using Phantom.Utils.Actor; namespace Phantom.Controller.Services.Instances; sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> { - public readonly record struct Init(Instance Instance, ActorRef<AgentActor.ICommand> AgentActorRef, AgentConnection AgentConnection, IDbContextProvider DbProvider, CancellationToken CancellationToken); + public readonly record struct Init(Instance Instance, ActorRef<AgentActor.ICommand> AgentActor, AgentConnection AgentConnection, IDbContextProvider DbProvider, CancellationToken CancellationToken); public static Props<ICommand> Factory(Init init) { return Props<ICommand>.Create(() => new InstanceActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); } - private readonly ActorRef<AgentActor.ICommand> agentActorRef; + private readonly ActorRef<AgentActor.ICommand> agentActor; private readonly AgentConnection agentConnection; private readonly CancellationToken cancellationToken; @@ -30,7 +30,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> { private readonly ActorRef<InstanceDatabaseStorageActor.ICommand> databaseStorageActor; private InstanceActor(Init init) { - this.agentActorRef = init.AgentActorRef; + this.agentActor = init.AgentActor; this.agentConnection = init.AgentConnection; this.cancellationToken = init.CancellationToken; @@ -46,7 +46,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> { } private void NotifyInstanceUpdated() { - agentActorRef.Tell(new AgentActor.ReceiveInstanceDataCommand(new Instance(instanceGuid, configuration, status, launchAutomatically))); + agentActor.Tell(new AgentActor.ReceiveInstanceDataCommand(new Instance(instanceGuid, configuration, status, launchAutomatically))); } private void SetLaunchAutomatically(bool newValue) { @@ -56,7 +56,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> { } } - private async Task<InstanceActionResult<TReply>> SendInstanceActionMessage<TMessage, TReply>(TMessage message) where TMessage : IMessageToAgent<InstanceActionResult<TReply>> { + private async Task<InstanceActionResult<TReply>> SendInstanceActionMessage<TMessage, TReply>(TMessage message) where TMessage : IMessageToAgent, ICanReply<InstanceActionResult<TReply>> { var reply = await agentConnection.Send<TMessage, InstanceActionResult<TReply>>(message, TimeSpan.FromSeconds(10), cancellationToken); return reply.DidNotReplyIfNull(); } diff --git a/Controller/Phantom.Controller.Services/Phantom.Controller.Services.csproj b/Controller/Phantom.Controller.Services/Phantom.Controller.Services.csproj index 41dbfac..7eaaadf 100644 --- a/Controller/Phantom.Controller.Services/Phantom.Controller.Services.csproj +++ b/Controller/Phantom.Controller.Services/Phantom.Controller.Services.csproj @@ -6,7 +6,6 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Akka" /> <PackageReference Include="BCrypt.Net-Next.StrongName" /> </ItemGroup> diff --git a/Controller/Phantom.Controller.Services/Rpc/AgentMessageHandlerActor.cs b/Controller/Phantom.Controller.Services/Rpc/AgentMessageHandlerActor.cs new file mode 100644 index 0000000..e4d2a8f --- /dev/null +++ b/Controller/Phantom.Controller.Services/Rpc/AgentMessageHandlerActor.cs @@ -0,0 +1,91 @@ +using Akka.Actor; +using Phantom.Common.Data.Replies; +using Phantom.Common.Messages.Agent; +using Phantom.Common.Messages.Agent.BiDirectional; +using Phantom.Common.Messages.Agent.ToAgent; +using Phantom.Common.Messages.Agent.ToController; +using Phantom.Controller.Services.Agents; +using Phantom.Controller.Services.Events; +using Phantom.Controller.Services.Instances; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Runtime; + +namespace Phantom.Controller.Services.Rpc; + +sealed class AgentMessageHandlerActor : ReceiveActor<IMessageToController> { + public readonly record struct Init(Guid AgentGuid, RpcConnectionToClient<IMessageToAgent> Connection, AgentRegistrationHandler AgentRegistrationHandler, AgentManager AgentManager, InstanceLogManager InstanceLogManager, EventLogManager EventLogManager); + + public static Props<IMessageToController> Factory(Init init) { + return Props<IMessageToController>.Create(() => new AgentMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); + } + + public IStash Stash { get; set; } = null!; + + private readonly Guid agentGuid; + private readonly RpcConnectionToClient<IMessageToAgent> connection; + private readonly AgentRegistrationHandler agentRegistrationHandler; + private readonly AgentManager agentManager; + private readonly InstanceLogManager instanceLogManager; + private readonly EventLogManager eventLogManager; + + private AgentMessageHandlerActor(Init init) { + this.agentGuid = init.AgentGuid; + this.connection = init.Connection; + this.agentRegistrationHandler = init.AgentRegistrationHandler; + this.agentManager = init.AgentManager; + this.instanceLogManager = init.InstanceLogManager; + this.eventLogManager = init.EventLogManager; + + ReceiveAsync<RegisterAgentMessage>(HandleRegisterAgent); + Receive<UnregisterAgentMessage>(HandleUnregisterAgent); + Receive<AgentIsAliveMessage>(HandleAgentIsAlive); + Receive<AdvertiseJavaRuntimesMessage>(HandleAdvertiseJavaRuntimes); + Receive<ReportAgentStatusMessage>(HandleReportAgentStatus); + Receive<ReportInstanceStatusMessage>(HandleReportInstanceStatus); + Receive<ReportInstanceEventMessage>(HandleReportInstanceEvent); + Receive<InstanceOutputMessage>(HandleInstanceOutput); + Receive<ReplyMessage>(HandleReply); + } + + private async Task HandleRegisterAgent(RegisterAgentMessage message) { + if (agentGuid != message.AgentInfo.AgentGuid) { + await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.ConnectionAlreadyHasAnAgent)); + } + else { + await agentRegistrationHandler.TryRegisterImpl(connection, message); + } + } + + private void HandleUnregisterAgent(UnregisterAgentMessage message) { + agentManager.TellAgent(agentGuid, new AgentActor.UnregisterCommand(connection)); + connection.Close(); + } + + private void HandleAgentIsAlive(AgentIsAliveMessage message) { + agentManager.TellAgent(agentGuid, new AgentActor.NotifyIsAliveCommand()); + } + + private void HandleAdvertiseJavaRuntimes(AdvertiseJavaRuntimesMessage message) { + agentManager.TellAgent(agentGuid, new AgentActor.UpdateJavaRuntimesCommand(message.Runtimes)); + } + + private void HandleReportAgentStatus(ReportAgentStatusMessage message) { + agentManager.TellAgent(agentGuid, new AgentActor.UpdateStatsCommand(message.RunningInstanceCount, message.RunningInstanceMemory)); + } + + private void HandleReportInstanceStatus(ReportInstanceStatusMessage message) { + agentManager.TellAgent(agentGuid, new AgentActor.UpdateInstanceStatusCommand(message.InstanceGuid, message.InstanceStatus)); + } + + private void HandleReportInstanceEvent(ReportInstanceEventMessage message) { + message.Event.Accept(eventLogManager.CreateInstanceEventVisitor(message.EventGuid, message.UtcTime, agentGuid, message.InstanceGuid)); + } + + private void HandleInstanceOutput(InstanceOutputMessage message) { + instanceLogManager.ReceiveLines(message.InstanceGuid, message.Lines); + } + + private void HandleReply(ReplyMessage message) { + connection.Receive(message); + } +} diff --git a/Controller/Phantom.Controller.Services/Rpc/AgentMessageListener.cs b/Controller/Phantom.Controller.Services/Rpc/AgentMessageListener.cs deleted file mode 100644 index 77bd6e0..0000000 --- a/Controller/Phantom.Controller.Services/Rpc/AgentMessageListener.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Phantom.Common.Data.Replies; -using Phantom.Common.Messages.Agent; -using Phantom.Common.Messages.Agent.BiDirectional; -using Phantom.Common.Messages.Agent.ToAgent; -using Phantom.Common.Messages.Agent.ToController; -using Phantom.Controller.Services.Agents; -using Phantom.Controller.Services.Events; -using Phantom.Controller.Services.Instances; -using Phantom.Utils.Rpc.Message; -using Phantom.Utils.Rpc.Runtime; -using Phantom.Utils.Tasks; - -namespace Phantom.Controller.Services.Rpc; - -public sealed class AgentMessageListener : IMessageToControllerListener { - private readonly RpcConnectionToClient<IMessageToAgentListener> connection; - private readonly AgentManager agentManager; - private readonly InstanceLogManager instanceLogManager; - private readonly EventLogManager eventLogManager; - private readonly CancellationToken cancellationToken; - - private readonly TaskCompletionSource<Guid> agentGuidWaiter = AsyncTasks.CreateCompletionSource<Guid>(); - - internal AgentMessageListener(RpcConnectionToClient<IMessageToAgentListener> connection, AgentManager agentManager, InstanceLogManager instanceLogManager, EventLogManager eventLogManager, CancellationToken cancellationToken) { - this.connection = connection; - this.agentManager = agentManager; - this.instanceLogManager = instanceLogManager; - this.eventLogManager = eventLogManager; - this.cancellationToken = cancellationToken; - } - - public async Task<NoReply> HandleRegisterAgent(RegisterAgentMessage message) { - if (agentGuidWaiter.Task.IsCompleted && agentGuidWaiter.Task.Result != message.AgentInfo.AgentGuid) { - connection.SetAuthorizationResult(false); - await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.ConnectionAlreadyHasAnAgent)); - } - else if (await agentManager.RegisterAgent(message.AuthToken, message.AgentInfo, connection)) { - connection.SetAuthorizationResult(true); - agentGuidWaiter.SetResult(message.AgentInfo.AgentGuid); - } - - return NoReply.Instance; - } - - private async Task<Guid> WaitForAgentGuid() { - return await agentGuidWaiter.Task.WaitAsync(cancellationToken); - } - - public Task<NoReply> HandleUnregisterAgent(UnregisterAgentMessage message) { - if (agentGuidWaiter.Task.IsCompleted) { - agentManager.TellAgent(agentGuidWaiter.Task.Result, new AgentActor.UnregisterCommand(connection)); - } - - connection.Close(); - return Task.FromResult(NoReply.Instance); - } - - public async Task<NoReply> HandleAgentIsAlive(AgentIsAliveMessage message) { - agentManager.TellAgent(await WaitForAgentGuid(), new AgentActor.NotifyIsAliveCommand()); - return NoReply.Instance; - } - - public async Task<NoReply> HandleAdvertiseJavaRuntimes(AdvertiseJavaRuntimesMessage message) { - agentManager.TellAgent(await WaitForAgentGuid(), new AgentActor.UpdateJavaRuntimesCommand(message.Runtimes)); - return NoReply.Instance; - } - - public async Task<NoReply> HandleReportAgentStatus(ReportAgentStatusMessage message) { - agentManager.TellAgent(await WaitForAgentGuid(), new AgentActor.UpdateStatsCommand(message.RunningInstanceCount, message.RunningInstanceMemory)); - return NoReply.Instance; - } - - public async Task<NoReply> HandleReportInstanceStatus(ReportInstanceStatusMessage message) { - agentManager.TellAgent(await WaitForAgentGuid(), new AgentActor.UpdateInstanceStatusCommand(message.InstanceGuid, message.InstanceStatus)); - return NoReply.Instance; - } - - public async Task<NoReply> HandleReportInstanceEvent(ReportInstanceEventMessage message) { - message.Event.Accept(eventLogManager.CreateInstanceEventVisitor(message.EventGuid, message.UtcTime, await WaitForAgentGuid(), message.InstanceGuid)); - return NoReply.Instance; - } - - public Task<NoReply> HandleInstanceOutput(InstanceOutputMessage message) { - instanceLogManager.ReceiveLines(message.InstanceGuid, message.Lines); - return Task.FromResult(NoReply.Instance); - } - - public Task<NoReply> HandleReply(ReplyMessage message) { - connection.Receive(message); - return Task.FromResult(NoReply.Instance); - } -} diff --git a/Controller/Phantom.Controller.Services/Rpc/AgentRegistrationHandler.cs b/Controller/Phantom.Controller.Services/Rpc/AgentRegistrationHandler.cs new file mode 100644 index 0000000..e18f91d --- /dev/null +++ b/Controller/Phantom.Controller.Services/Rpc/AgentRegistrationHandler.cs @@ -0,0 +1,34 @@ +using Phantom.Common.Messages.Agent; +using Phantom.Common.Messages.Agent.ToController; +using Phantom.Controller.Services.Agents; +using Phantom.Controller.Services.Events; +using Phantom.Controller.Services.Instances; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Runtime; + +namespace Phantom.Controller.Services.Rpc; + +sealed class AgentRegistrationHandler : IRegistrationHandler<IMessageToAgent, IMessageToController, RegisterAgentMessage> { + private readonly AgentManager agentManager; + private readonly InstanceLogManager instanceLogManager; + private readonly EventLogManager eventLogManager; + + public AgentRegistrationHandler(AgentManager agentManager, InstanceLogManager instanceLogManager, EventLogManager eventLogManager) { + this.agentManager = agentManager; + this.instanceLogManager = instanceLogManager; + this.eventLogManager = eventLogManager; + } + + async Task<Props<IMessageToController>?> IRegistrationHandler<IMessageToAgent, IMessageToController, RegisterAgentMessage>.TryRegister(RpcConnectionToClient<IMessageToAgent> connection, RegisterAgentMessage message) { + return await TryRegisterImpl(connection, message) ? CreateMessageHandlerActorProps(message.AgentInfo.AgentGuid, connection) : null; + } + + public Task<bool> TryRegisterImpl(RpcConnectionToClient<IMessageToAgent> connection, RegisterAgentMessage message) { + return agentManager.RegisterAgent(message.AuthToken, message.AgentInfo, connection); + } + + private Props<IMessageToController> CreateMessageHandlerActorProps(Guid agentGuid, RpcConnectionToClient<IMessageToAgent> connection) { + var init = new AgentMessageHandlerActor.Init(agentGuid, connection, this, agentManager, instanceLogManager, eventLogManager); + return AgentMessageHandlerActor.Factory(init); + } +} diff --git a/Controller/Phantom.Controller.Services/Rpc/WebMessageDataUpdateSenderActor.cs b/Controller/Phantom.Controller.Services/Rpc/WebMessageDataUpdateSenderActor.cs new file mode 100644 index 0000000..9787a5d --- /dev/null +++ b/Controller/Phantom.Controller.Services/Rpc/WebMessageDataUpdateSenderActor.cs @@ -0,0 +1,72 @@ +using System.Collections.Immutable; +using Phantom.Common.Data.Web.Agent; +using Phantom.Common.Data.Web.Instance; +using Phantom.Common.Messages.Web; +using Phantom.Common.Messages.Web.ToWeb; +using Phantom.Controller.Services.Instances; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Runtime; + +namespace Phantom.Controller.Services.Rpc; + +sealed class WebMessageDataUpdateSenderActor : ReceiveActor<WebMessageDataUpdateSenderActor.ICommand> { + public readonly record struct Init(RpcConnectionToClient<IMessageToWeb> Connection, ControllerState ControllerState, InstanceLogManager InstanceLogManager); + + public static Props<ICommand> Factory(Init init) { + return Props<ICommand>.Create(() => new WebMessageDataUpdateSenderActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); + } + + private readonly RpcConnectionToClient<IMessageToWeb> connection; + private readonly ControllerState controllerState; + private readonly InstanceLogManager instanceLogManager; + private readonly ActorRef<ICommand> selfCached; + + private WebMessageDataUpdateSenderActor(Init init) { + this.connection = init.Connection; + this.controllerState = init.ControllerState; + this.instanceLogManager = init.InstanceLogManager; + this.selfCached = SelfTyped; + + ReceiveAsync<RefreshAgentsCommand>(RefreshAgents); + ReceiveAsync<RefreshInstancesCommand>(RefreshInstances); + ReceiveAsync<ReceiveInstanceLogsCommand>(ReceiveInstanceLogs); + } + + protected override void PreStart() { + controllerState.AgentsByGuidReceiver.Register(SelfTyped, static state => new RefreshAgentsCommand(state)); + controllerState.InstancesByGuidReceiver.Register(SelfTyped, static state => new RefreshInstancesCommand(state)); + + instanceLogManager.LogsReceived += OnInstanceLogsReceived; + } + + protected override void PostStop() { + instanceLogManager.LogsReceived -= OnInstanceLogsReceived; + + controllerState.AgentsByGuidReceiver.Unregister(SelfTyped); + controllerState.InstancesByGuidReceiver.Unregister(SelfTyped); + } + + private void OnInstanceLogsReceived(object? sender, InstanceLogManager.Event e) { + selfCached.Tell(new ReceiveInstanceLogsCommand(e.InstanceGuid, e.Lines)); + } + + public interface ICommand {} + + private sealed record RefreshAgentsCommand(ImmutableDictionary<Guid, Agent> Agents) : ICommand; + + private sealed record RefreshInstancesCommand(ImmutableDictionary<Guid, Instance> Instances) : ICommand; + + private sealed record ReceiveInstanceLogsCommand(Guid InstanceGuid, ImmutableArray<string> Lines) : ICommand; + + private Task RefreshAgents(RefreshAgentsCommand command) { + return connection.Send(new RefreshAgentsMessage(command.Agents.Values.ToImmutableArray())); + } + + private Task RefreshInstances(RefreshInstancesCommand command) { + return connection.Send(new RefreshInstancesMessage(command.Instances.Values.ToImmutableArray())); + } + + private Task ReceiveInstanceLogs(ReceiveInstanceLogsCommand command) { + return connection.Send(new InstanceOutputMessage(command.InstanceGuid, command.Lines)); + } +} diff --git a/Controller/Phantom.Controller.Services/Rpc/WebMessageHandlerActor.cs b/Controller/Phantom.Controller.Services/Rpc/WebMessageHandlerActor.cs new file mode 100644 index 0000000..881de92 --- /dev/null +++ b/Controller/Phantom.Controller.Services/Rpc/WebMessageHandlerActor.cs @@ -0,0 +1,166 @@ +using System.Collections.Immutable; +using Phantom.Common.Data.Java; +using Phantom.Common.Data.Minecraft; +using Phantom.Common.Data.Replies; +using Phantom.Common.Data.Web.AuditLog; +using Phantom.Common.Data.Web.EventLog; +using Phantom.Common.Data.Web.Instance; +using Phantom.Common.Data.Web.Users; +using Phantom.Common.Messages.Agent.BiDirectional; +using Phantom.Common.Messages.Web; +using Phantom.Common.Messages.Web.ToController; +using Phantom.Controller.Minecraft; +using Phantom.Controller.Services.Agents; +using Phantom.Controller.Services.Events; +using Phantom.Controller.Services.Instances; +using Phantom.Controller.Services.Users; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Runtime; + +namespace Phantom.Controller.Services.Rpc; + +sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> { + public readonly record struct Init( + RpcConnectionToClient<IMessageToWeb> Connection, + WebRegistrationHandler WebRegistrationHandler, + ControllerState ControllerState, + InstanceLogManager InstanceLogManager, + UserManager UserManager, + RoleManager RoleManager, + UserRoleManager UserRoleManager, + UserLoginManager UserLoginManager, + AuditLogManager AuditLogManager, + AgentManager AgentManager, + MinecraftVersions MinecraftVersions, + EventLogManager EventLogManager + ); + + public static Props<IMessageToController> Factory(Init init) { + return Props<IMessageToController>.Create(() => new WebMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); + } + + private readonly RpcConnectionToClient<IMessageToWeb> connection; + private readonly WebRegistrationHandler webRegistrationHandler; + private readonly ControllerState controllerState; + private readonly UserManager userManager; + private readonly RoleManager roleManager; + private readonly UserRoleManager userRoleManager; + private readonly UserLoginManager userLoginManager; + private readonly AuditLogManager auditLogManager; + private readonly AgentManager agentManager; + private readonly MinecraftVersions minecraftVersions; + private readonly EventLogManager eventLogManager; + + private WebMessageHandlerActor(Init init) { + this.connection = init.Connection; + this.webRegistrationHandler = init.WebRegistrationHandler; + this.controllerState = init.ControllerState; + this.userManager = init.UserManager; + this.roleManager = init.RoleManager; + this.userRoleManager = init.UserRoleManager; + this.userLoginManager = init.UserLoginManager; + this.auditLogManager = init.AuditLogManager; + this.agentManager = init.AgentManager; + this.minecraftVersions = init.MinecraftVersions; + this.eventLogManager = init.EventLogManager; + + var senderActorInit = new WebMessageDataUpdateSenderActor.Init(connection, controllerState, init.InstanceLogManager); + Context.ActorOf(WebMessageDataUpdateSenderActor.Factory(senderActorInit), "DataUpdateSender"); + + ReceiveAsync<RegisterWebMessage>(HandleRegisterWeb); + Receive<UnregisterWebMessage>(HandleUnregisterWeb); + ReceiveAndReplyLater<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(HandleCreateOrUpdateAdministratorUser); + ReceiveAndReplyLater<CreateUserMessage, CreateUserResult>(HandleCreateUser); + ReceiveAndReplyLater<GetUsersMessage, ImmutableArray<UserInfo>>(HandleGetUsers); + ReceiveAndReplyLater<GetRolesMessage, ImmutableArray<RoleInfo>>(HandleGetRoles); + ReceiveAndReplyLater<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(HandleGetUserRoles); + ReceiveAndReplyLater<ChangeUserRolesMessage, ChangeUserRolesResult>(HandleChangeUserRoles); + ReceiveAndReplyLater<DeleteUserMessage, DeleteUserResult>(HandleDeleteUser); + ReceiveAndReplyLater<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(HandleCreateOrUpdateInstance); + ReceiveAndReplyLater<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(HandleLaunchInstance); + ReceiveAndReplyLater<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(HandleStopInstance); + ReceiveAndReplyLater<SendCommandToInstanceMessage, InstanceActionResult<SendCommandToInstanceResult>>(HandleSendCommandToInstance); + ReceiveAndReplyLater<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(HandleGetMinecraftVersions); + ReceiveAndReply<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(HandleGetAgentJavaRuntimes); + ReceiveAndReplyLater<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(HandleGetAuditLog); + ReceiveAndReplyLater<GetEventLogMessage, ImmutableArray<EventLogItem>>(HandleGetEventLog); + ReceiveAndReplyLater<LogInMessage, LogInSuccess?>(HandleLogIn); + Receive<ReplyMessage>(HandleReply); + } + + private async Task HandleRegisterWeb(RegisterWebMessage message) { + await webRegistrationHandler.TryRegisterImpl(connection, message); + } + + private void HandleUnregisterWeb(UnregisterWebMessage message) { + connection.Close(); + } + + private Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message) { + return userManager.CreateOrUpdateAdministrator(message.Username, message.Password); + } + + private Task<CreateUserResult> HandleCreateUser(CreateUserMessage message) { + return userManager.Create(message.LoggedInUserGuid, message.Username, message.Password); + } + + private Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message) { + return userManager.GetAll(); + } + + private Task<ImmutableArray<RoleInfo>> HandleGetRoles(GetRolesMessage message) { + return roleManager.GetAll(); + } + + private Task<ImmutableDictionary<Guid, ImmutableArray<Guid>>> HandleGetUserRoles(GetUserRolesMessage message) { + return userRoleManager.GetUserRoles(message.UserGuids); + } + + private Task<ChangeUserRolesResult> HandleChangeUserRoles(ChangeUserRolesMessage message) { + return userRoleManager.ChangeUserRoles(message.LoggedInUserGuid, message.SubjectUserGuid, message.AddToRoleGuids, message.RemoveFromRoleGuids); + } + + private Task<DeleteUserResult> HandleDeleteUser(DeleteUserMessage message) { + return userManager.DeleteByGuid(message.LoggedInUserGuid, message.SubjectUserGuid); + } + + private Task<InstanceActionResult<CreateOrUpdateInstanceResult>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message) { + return agentManager.DoInstanceAction<AgentActor.CreateOrUpdateInstanceCommand, CreateOrUpdateInstanceResult>(message.Configuration.AgentGuid, new AgentActor.CreateOrUpdateInstanceCommand(message.LoggedInUserGuid, message.InstanceGuid, message.Configuration)); + } + + private Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message) { + return agentManager.DoInstanceAction<AgentActor.LaunchInstanceCommand, LaunchInstanceResult>(message.AgentGuid, new AgentActor.LaunchInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid)); + } + + private Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message) { + return agentManager.DoInstanceAction<AgentActor.StopInstanceCommand, StopInstanceResult>(message.AgentGuid, new AgentActor.StopInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid, message.StopStrategy)); + } + + private Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) { + return agentManager.DoInstanceAction<AgentActor.SendCommandToInstanceCommand, SendCommandToInstanceResult>(message.AgentGuid, new AgentActor.SendCommandToInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid, message.Command)); + } + + private Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message) { + return minecraftVersions.GetVersions(CancellationToken.None); + } + + private ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>> HandleGetAgentJavaRuntimes(GetAgentJavaRuntimesMessage message) { + return controllerState.AgentJavaRuntimesByGuid; + } + + private Task<ImmutableArray<AuditLogItem>> HandleGetAuditLog(GetAuditLogMessage message) { + return auditLogManager.GetMostRecentItems(message.Count); + } + + private Task<ImmutableArray<EventLogItem>> HandleGetEventLog(GetEventLogMessage message) { + return eventLogManager.GetMostRecentItems(message.Count); + } + + private Task<LogInSuccess?> HandleLogIn(LogInMessage message) { + return userLoginManager.LogIn(message.Username, message.Password); + } + + private void HandleReply(ReplyMessage message) { + connection.Receive(message); + } +} diff --git a/Controller/Phantom.Controller.Services/Rpc/WebMessageListener.cs b/Controller/Phantom.Controller.Services/Rpc/WebMessageListener.cs deleted file mode 100644 index 9f60e4d..0000000 --- a/Controller/Phantom.Controller.Services/Rpc/WebMessageListener.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System.Collections.Immutable; -using Akka.Actor; -using Phantom.Common.Data; -using Phantom.Common.Data.Java; -using Phantom.Common.Data.Minecraft; -using Phantom.Common.Data.Replies; -using Phantom.Common.Data.Web.AuditLog; -using Phantom.Common.Data.Web.EventLog; -using Phantom.Common.Data.Web.Instance; -using Phantom.Common.Data.Web.Users; -using Phantom.Common.Messages.Web; -using Phantom.Common.Messages.Web.BiDirectional; -using Phantom.Common.Messages.Web.ToController; -using Phantom.Common.Messages.Web.ToWeb; -using Phantom.Controller.Minecraft; -using Phantom.Controller.Services.Agents; -using Phantom.Controller.Services.Events; -using Phantom.Controller.Services.Instances; -using Phantom.Controller.Services.Users; -using Phantom.Utils.Actor; -using Phantom.Utils.Logging; -using Phantom.Utils.Rpc.Message; -using Phantom.Utils.Rpc.Runtime; -using Serilog; -using Agent = Phantom.Common.Data.Web.Agent.Agent; - -namespace Phantom.Controller.Services.Rpc; - -public sealed class WebMessageListener : IMessageToControllerListener { - private static readonly ILogger Logger = PhantomLogger.Create<WebMessageListener>(); - - private static int listenerSequenceId = 0; - - private readonly ActorRef<ICommand> actor; - private readonly RpcConnectionToClient<IMessageToWebListener> connection; - private readonly AuthToken authToken; - private readonly ControllerState controllerState; - private readonly UserManager userManager; - private readonly RoleManager roleManager; - private readonly UserRoleManager userRoleManager; - private readonly UserLoginManager userLoginManager; - private readonly AuditLogManager auditLogManager; - private readonly AgentManager agentManager; - private readonly InstanceLogManager instanceLogManager; - private readonly MinecraftVersions minecraftVersions; - private readonly EventLogManager eventLogManager; - - internal WebMessageListener( - IActorRefFactory actorSystem, - RpcConnectionToClient<IMessageToWebListener> connection, - AuthToken authToken, - ControllerState controllerState, - UserManager userManager, - RoleManager roleManager, - UserRoleManager userRoleManager, - UserLoginManager userLoginManager, - AuditLogManager auditLogManager, - AgentManager agentManager, - InstanceLogManager instanceLogManager, - MinecraftVersions minecraftVersions, - EventLogManager eventLogManager - ) { - this.actor = actorSystem.ActorOf(Actor.Factory(this), "Web-" + Interlocked.Increment(ref listenerSequenceId)); - this.connection = connection; - this.authToken = authToken; - this.controllerState = controllerState; - this.userManager = userManager; - this.roleManager = roleManager; - this.userRoleManager = userRoleManager; - this.userLoginManager = userLoginManager; - this.auditLogManager = auditLogManager; - this.agentManager = agentManager; - this.instanceLogManager = instanceLogManager; - this.minecraftVersions = minecraftVersions; - this.eventLogManager = eventLogManager; - } - - private sealed class Actor : ReceiveActor<ICommand> { - public static Props<ICommand> Factory(WebMessageListener listener) { - return Props<ICommand>.Create(() => new Actor(listener), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); - } - - private readonly WebMessageListener listener; - - private Actor(WebMessageListener listener) { - this.listener = listener; - - Receive<StartConnectionCommand>(StartConnection); - Receive<StopConnectionCommand>(StopConnection); - Receive<RefreshAgentsCommand>(RefreshAgents); - Receive<RefreshInstancesCommand>(RefreshInstances); - } - - private void StartConnection(StartConnectionCommand command) { - listener.controllerState.AgentsByGuidReceiver.Register(SelfTyped, static state => new RefreshAgentsCommand(state)); - listener.controllerState.InstancesByGuidReceiver.Register(SelfTyped, static state => new RefreshInstancesCommand(state)); - - listener.instanceLogManager.LogsReceived += HandleInstanceLogsReceived; - } - - private void StopConnection(StopConnectionCommand command) { - listener.instanceLogManager.LogsReceived -= HandleInstanceLogsReceived; - - listener.controllerState.AgentsByGuidReceiver.Unregister(SelfTyped); - listener.controllerState.InstancesByGuidReceiver.Unregister(SelfTyped); - } - - private void RefreshAgents(RefreshAgentsCommand command) { - var message = new RefreshAgentsMessage(command.Agents.Values.ToImmutableArray()); - listener.connection.Send(message); - } - - private void RefreshInstances(RefreshInstancesCommand command) { - var message = new RefreshInstancesMessage(command.Instances.Values.ToImmutableArray()); - listener.connection.Send(message); - } - - private void HandleInstanceLogsReceived(object? sender, InstanceLogManager.Event e) { - listener.connection.Send(new InstanceOutputMessage(e.InstanceGuid, e.Lines)); - } - } - - private interface ICommand {} - - private sealed record StartConnectionCommand : ICommand; - - private sealed record StopConnectionCommand : ICommand; - - private sealed record RefreshAgentsCommand(ImmutableDictionary<Guid, Agent> Agents) : ICommand; - - private sealed record RefreshInstancesCommand(ImmutableDictionary<Guid, Instance> Instances) : ICommand; - - public async Task<NoReply> HandleRegisterWeb(RegisterWebMessage message) { - if (authToken.FixedTimeEquals(message.AuthToken)) { - Logger.Information("Web authorized successfully."); - connection.SetAuthorizationResult(true); - await connection.Send(new RegisterWebResultMessage(true)); - } - else { - Logger.Warning("Web failed to authorize, invalid token."); - connection.SetAuthorizationResult(false); - await connection.Send(new RegisterWebResultMessage(false)); - } - - if (!connection.IsClosed) { - actor.Tell(new StartConnectionCommand()); - } - - return NoReply.Instance; - } - - public Task<NoReply> HandleUnregisterWeb(UnregisterWebMessage message) { - if (!connection.IsClosed) { - connection.Close(); - actor.Tell(new StopConnectionCommand()); - } - - return Task.FromResult(NoReply.Instance); - } - - public Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message) { - return userManager.CreateOrUpdateAdministrator(message.Username, message.Password); - } - - public Task<CreateUserResult> HandleCreateUser(CreateUserMessage message) { - return userManager.Create(message.LoggedInUserGuid, message.Username, message.Password); - } - - public Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message) { - return userManager.GetAll(); - } - - public Task<ImmutableArray<RoleInfo>> HandleGetRoles(GetRolesMessage message) { - return roleManager.GetAll(); - } - - public Task<ImmutableDictionary<Guid, ImmutableArray<Guid>>> HandleGetUserRoles(GetUserRolesMessage message) { - return userRoleManager.GetUserRoles(message.UserGuids); - } - - public Task<ChangeUserRolesResult> HandleChangeUserRoles(ChangeUserRolesMessage message) { - return userRoleManager.ChangeUserRoles(message.LoggedInUserGuid, message.SubjectUserGuid, message.AddToRoleGuids, message.RemoveFromRoleGuids); - } - - public Task<DeleteUserResult> HandleDeleteUser(DeleteUserMessage message) { - return userManager.DeleteByGuid(message.LoggedInUserGuid, message.SubjectUserGuid); - } - - public Task<InstanceActionResult<CreateOrUpdateInstanceResult>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message) { - return agentManager.DoInstanceAction<AgentActor.CreateOrUpdateInstanceCommand, CreateOrUpdateInstanceResult>(message.Configuration.AgentGuid, new AgentActor.CreateOrUpdateInstanceCommand(message.LoggedInUserGuid, message.InstanceGuid, message.Configuration)); - } - - public Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message) { - return agentManager.DoInstanceAction<AgentActor.LaunchInstanceCommand, LaunchInstanceResult>(message.AgentGuid, new AgentActor.LaunchInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid)); - } - - public Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message) { - return agentManager.DoInstanceAction<AgentActor.StopInstanceCommand, StopInstanceResult>(message.AgentGuid, new AgentActor.StopInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid, message.StopStrategy)); - } - - public Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) { - return agentManager.DoInstanceAction<AgentActor.SendCommandToInstanceCommand, SendCommandToInstanceResult>(message.AgentGuid, new AgentActor.SendCommandToInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid, message.Command)); - } - - public Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message) { - return minecraftVersions.GetVersions(CancellationToken.None); - } - - public Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> HandleGetAgentJavaRuntimes(GetAgentJavaRuntimesMessage message) { - return Task.FromResult(controllerState.AgentJavaRuntimesByGuid); - } - - public Task<ImmutableArray<AuditLogItem>> HandleGetAuditLog(GetAuditLogMessage message) { - return auditLogManager.GetMostRecentItems(message.Count); - } - - public Task<ImmutableArray<EventLogItem>> HandleGetEventLog(GetEventLogMessage message) { - return eventLogManager.GetMostRecentItems(message.Count); - } - - public Task<LogInSuccess?> HandleLogIn(LogInMessage message) { - return userLoginManager.LogIn(message.Username, message.Password); - } - - public Task<NoReply> HandleReply(ReplyMessage message) { - connection.Receive(message); - return Task.FromResult(NoReply.Instance); - } -} diff --git a/Controller/Phantom.Controller.Services/Rpc/WebRegistrationHandler.cs b/Controller/Phantom.Controller.Services/Rpc/WebRegistrationHandler.cs new file mode 100644 index 0000000..3c3319c --- /dev/null +++ b/Controller/Phantom.Controller.Services/Rpc/WebRegistrationHandler.cs @@ -0,0 +1,67 @@ +using Phantom.Common.Data; +using Phantom.Common.Messages.Web; +using Phantom.Common.Messages.Web.ToController; +using Phantom.Common.Messages.Web.ToWeb; +using Phantom.Controller.Minecraft; +using Phantom.Controller.Services.Agents; +using Phantom.Controller.Services.Events; +using Phantom.Controller.Services.Instances; +using Phantom.Controller.Services.Users; +using Phantom.Utils.Actor; +using Phantom.Utils.Logging; +using Phantom.Utils.Rpc.Runtime; +using Serilog; + +namespace Phantom.Controller.Services.Rpc; + +sealed class WebRegistrationHandler : IRegistrationHandler<IMessageToWeb, IMessageToController, RegisterWebMessage> { + private static readonly ILogger Logger = PhantomLogger.Create<WebRegistrationHandler>(); + + private readonly AuthToken webAuthToken; + private readonly ControllerState controllerState; + private readonly InstanceLogManager instanceLogManager; + private readonly UserManager userManager; + private readonly RoleManager roleManager; + private readonly UserRoleManager userRoleManager; + private readonly UserLoginManager userLoginManager; + private readonly AuditLogManager auditLogManager; + private readonly AgentManager agentManager; + private readonly MinecraftVersions minecraftVersions; + private readonly EventLogManager eventLogManager; + + public WebRegistrationHandler(AuthToken webAuthToken, ControllerState controllerState, InstanceLogManager instanceLogManager, UserManager userManager, RoleManager roleManager, UserRoleManager userRoleManager, UserLoginManager userLoginManager, AuditLogManager auditLogManager, AgentManager agentManager, MinecraftVersions minecraftVersions, EventLogManager eventLogManager) { + this.webAuthToken = webAuthToken; + this.controllerState = controllerState; + this.userManager = userManager; + this.roleManager = roleManager; + this.userRoleManager = userRoleManager; + this.userLoginManager = userLoginManager; + this.auditLogManager = auditLogManager; + this.agentManager = agentManager; + this.minecraftVersions = minecraftVersions; + this.eventLogManager = eventLogManager; + this.instanceLogManager = instanceLogManager; + } + + async Task<Props<IMessageToController>?> IRegistrationHandler<IMessageToWeb, IMessageToController, RegisterWebMessage>.TryRegister(RpcConnectionToClient<IMessageToWeb> connection, RegisterWebMessage message) { + return await TryRegisterImpl(connection, message) ? CreateMessageHandlerActorProps(connection) : null; + } + + public async Task<bool> TryRegisterImpl(RpcConnectionToClient<IMessageToWeb> connection, RegisterWebMessage message) { + if (webAuthToken.FixedTimeEquals(message.AuthToken)) { + Logger.Information("Web authorized successfully."); + await connection.Send(new RegisterWebResultMessage(true)); + return true; + } + else { + Logger.Warning("Web failed to authorize, invalid token."); + await connection.Send(new RegisterWebResultMessage(false)); + return false; + } + } + + private Props<IMessageToController> CreateMessageHandlerActorProps(RpcConnectionToClient<IMessageToWeb> connection) { + var init = new WebMessageHandlerActor.Init(connection, this, controllerState, instanceLogManager, userManager, roleManager, userRoleManager, userLoginManager, auditLogManager, agentManager, minecraftVersions, eventLogManager); + return WebMessageHandlerActor.Factory(init); + } +} diff --git a/Controller/Phantom.Controller/Program.cs b/Controller/Phantom.Controller/Program.cs index af60303..70a051c 100644 --- a/Controller/Phantom.Controller/Program.cs +++ b/Controller/Phantom.Controller/Program.cs @@ -54,24 +54,23 @@ try { PhantomLogger.Root.InformationHeading("Launching Phantom Panel server..."); var dbContextFactory = new ApplicationDbContextFactory(sqlConnectionString); - - await using (var controllerServices = new ControllerServices(dbContextFactory, agentKeyData.AuthToken, webKeyData.AuthToken, shutdownCancellationToken)) { - await controllerServices.Initialize(); - static RpcConfiguration ConfigureRpc(string serviceName, string host, ushort port, ConnectionKeyData connectionKey) { - return new RpcConfiguration("Rpc:" + serviceName, host, port, connectionKey.Certificate); - } + using var controllerServices = new ControllerServices(dbContextFactory, agentKeyData.AuthToken, webKeyData.AuthToken, shutdownCancellationToken); + await controllerServices.Initialize(); - var rpcTaskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Rpc")); - try { - await Task.WhenAll( - RpcServerRuntime.Launch(ConfigureRpc("Agent", agentRpcServerHost, agentRpcServerPort, agentKeyData), AgentMessageRegistries.Definitions, controllerServices.CreateAgentMessageListener, shutdownCancellationToken), - RpcServerRuntime.Launch(ConfigureRpc("Web", webRpcServerHost, webRpcServerPort, webKeyData), WebMessageRegistries.Definitions, controllerServices.CreateWebMessageListener, shutdownCancellationToken) - ); - } finally { - await rpcTaskManager.Stop(); - NetMQConfig.Cleanup(); - } + static RpcConfiguration ConfigureRpc(string serviceName, string host, ushort port, ConnectionKeyData connectionKey) { + return new RpcConfiguration(serviceName, host, port, connectionKey.Certificate); + } + + var rpcTaskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Rpc")); + try { + await Task.WhenAll( + RpcServerRuntime.Launch(ConfigureRpc("Agent", agentRpcServerHost, agentRpcServerPort, agentKeyData), AgentMessageRegistries.Definitions, controllerServices.AgentRegistrationHandler, controllerServices.ActorSystem, shutdownCancellationToken), + RpcServerRuntime.Launch(ConfigureRpc("Web", webRpcServerHost, webRpcServerPort, webKeyData), WebMessageRegistries.Definitions, controllerServices.WebRegistrationHandler, controllerServices.ActorSystem, shutdownCancellationToken) + ); + } finally { + await rpcTaskManager.Stop(); + NetMQConfig.Cleanup(); } return 0; diff --git a/Utils/Phantom.Utils.Actor/ActorConfiguration.cs b/Utils/Phantom.Utils.Actor/ActorConfiguration.cs index 1c6a808..0719540 100644 --- a/Utils/Phantom.Utils.Actor/ActorConfiguration.cs +++ b/Utils/Phantom.Utils.Actor/ActorConfiguration.cs @@ -5,6 +5,7 @@ namespace Phantom.Utils.Actor; public readonly struct ActorConfiguration { public SupervisorStrategy? SupervisorStrategy { get; init; } public string? MailboxType { get; init; } + public int? StashCapacity { get; init; } internal Props Apply(Props props) { if (SupervisorStrategy != null) { @@ -14,6 +15,10 @@ public readonly struct ActorConfiguration { if (MailboxType != null) { props = props.WithMailbox(MailboxType); } + + if (StashCapacity != null) { + props = props.WithStashCapacity(StashCapacity.Value); + } return props; } diff --git a/Utils/Phantom.Utils.Actor/ActorRef.cs b/Utils/Phantom.Utils.Actor/ActorRef.cs index a84053d..89444a0 100644 --- a/Utils/Phantom.Utils.Actor/ActorRef.cs +++ b/Utils/Phantom.Utils.Actor/ActorRef.cs @@ -28,4 +28,8 @@ public readonly struct ActorRef<TMessage> { public Task<TReply> Request<TReply>(ICanReply<TReply> message, CancellationToken cancellationToken = default) { return Request(message, timeout: null, cancellationToken); } + + public Task<bool> Stop(TimeSpan? timeout = null) { + return actorRef.GracefulStop(timeout ?? Timeout.InfiniteTimeSpan); + } } diff --git a/Utils/Phantom.Utils.Rpc/Message/IMessage.cs b/Utils/Phantom.Utils.Rpc/Message/IMessage.cs deleted file mode 100644 index 8d6532e..0000000 --- a/Utils/Phantom.Utils.Rpc/Message/IMessage.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Phantom.Utils.Rpc.Message; - -public interface IMessage<TListener, TReply> { - MessageQueueKey QueueKey { get; } - Task<TReply> Accept(TListener listener); -} diff --git a/Utils/Phantom.Utils.Rpc/Message/IMessageDefinitions.cs b/Utils/Phantom.Utils.Rpc/Message/IMessageDefinitions.cs index e215b6e..00b4ec9 100644 --- a/Utils/Phantom.Utils.Rpc/Message/IMessageDefinitions.cs +++ b/Utils/Phantom.Utils.Rpc/Message/IMessageDefinitions.cs @@ -1,9 +1,6 @@ namespace Phantom.Utils.Rpc.Message; -public interface IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> where TReplyMessage : IMessage<TClientListener, NoReply>, IMessage<TServerListener, NoReply> { - MessageRegistry<TClientListener> ToClient { get; } - MessageRegistry<TServerListener> ToServer { get; } - - bool IsRegistrationMessage(Type messageType); - TReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply); +public interface IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> : IReplyMessageFactory<TReplyMessage> where TReplyMessage : TClientMessage, TServerMessage { + MessageRegistry<TClientMessage> ToClient { get; } + MessageRegistry<TServerMessage> ToServer { get; } } diff --git a/Utils/Phantom.Utils.Rpc/Message/IReplyMessageFactory.cs b/Utils/Phantom.Utils.Rpc/Message/IReplyMessageFactory.cs new file mode 100644 index 0000000..b4a951b --- /dev/null +++ b/Utils/Phantom.Utils.Rpc/Message/IReplyMessageFactory.cs @@ -0,0 +1,5 @@ +namespace Phantom.Utils.Rpc.Message; + +public interface IReplyMessageFactory<TReplyMessage> { + TReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply); +} diff --git a/Utils/Phantom.Utils.Rpc/Message/IReplySender.cs b/Utils/Phantom.Utils.Rpc/Message/IReplySender.cs new file mode 100644 index 0000000..3a35356 --- /dev/null +++ b/Utils/Phantom.Utils.Rpc/Message/IReplySender.cs @@ -0,0 +1,5 @@ +namespace Phantom.Utils.Rpc.Message; + +interface IReplySender { + Task SendReply(uint sequenceId, byte[] serializedReply); +} diff --git a/Utils/Phantom.Utils.Rpc/Message/MessageHandler.cs b/Utils/Phantom.Utils.Rpc/Message/MessageHandler.cs index 303ee71..c92a514 100644 --- a/Utils/Phantom.Utils.Rpc/Message/MessageHandler.cs +++ b/Utils/Phantom.Utils.Rpc/Message/MessageHandler.cs @@ -1,41 +1,35 @@ -using Phantom.Utils.Logging; +using Phantom.Utils.Actor; +using Phantom.Utils.Logging; using Serilog; namespace Phantom.Utils.Rpc.Message; -abstract class MessageHandler<TListener> { - protected ILogger Logger { get; } +sealed class MessageHandler<TMessageBase> { + private readonly ILogger logger; + private readonly ActorRef<TMessageBase> handlerActor; + private readonly IReplySender replySender; - private readonly TListener listener; - private readonly MessageQueues messageQueues; + public MessageHandler(string loggerName, ActorRef<TMessageBase> handlerActor, IReplySender replySender) { + this.logger = PhantomLogger.Create("MessageHandler", loggerName); + this.handlerActor = handlerActor; + this.replySender = replySender; + } - protected MessageHandler(string loggerName, TListener listener) { - this.Logger = PhantomLogger.Create("MessageHandler", loggerName); - this.listener = listener; - this.messageQueues = new MessageQueues(loggerName + ":Receive"); + public void Tell(TMessageBase message) { + handlerActor.Tell(message); } - internal void Enqueue<TMessage, TReply>(uint sequenceId, TMessage message) where TMessage : IMessage<TListener, TReply> { - messageQueues.Enqueue(message.QueueKey, () => TryHandle<TMessage, TReply>(sequenceId, message)); - } - - private async Task TryHandle<TMessage, TReply>(uint sequenceId, TMessage message) where TMessage : IMessage<TListener, TReply> { - TReply reply; - try { - reply = await message.Accept(listener); - } catch (Exception e) { - Logger.Error(e, "Failed to handle message {Type}.", message.GetType().Name); - return; - } - - if (reply is not NoReply) { - await SendReply(sequenceId, MessageSerializer.Serialize(reply)); - } - } - - protected abstract Task SendReply(uint sequenceId, byte[] serializedReply); - - internal Task StopReceiving() { - return messageQueues.Stop(); + public Task TellAndReply<TMessage, TReply>(TMessage message, uint sequenceId) where TMessage : ICanReply<TReply> { + return handlerActor.Request(message).ContinueWith(task => { + if (task.IsCompletedSuccessfully) { + return replySender.SendReply(sequenceId, MessageSerializer.Serialize(task.Result)); + } + + if (task.IsFaulted) { + logger.Error(task.Exception, "Failed to handle message {Type}.", message.GetType().Name); + } + + return task; + }, TaskScheduler.Default); } } diff --git a/Utils/Phantom.Utils.Rpc/Message/MessageQueueKey.cs b/Utils/Phantom.Utils.Rpc/Message/MessageQueueKey.cs deleted file mode 100644 index 397e5c2..0000000 --- a/Utils/Phantom.Utils.Rpc/Message/MessageQueueKey.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Phantom.Utils.Rpc.Message; - -public sealed class MessageQueueKey { - public string Name { get; } - - public MessageQueueKey(string name) { - Name = name; - } -} diff --git a/Utils/Phantom.Utils.Rpc/Message/MessageQueues.cs b/Utils/Phantom.Utils.Rpc/Message/MessageQueues.cs deleted file mode 100644 index c43e1fd..0000000 --- a/Utils/Phantom.Utils.Rpc/Message/MessageQueues.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Phantom.Utils.Logging; -using Phantom.Utils.Tasks; -using Serilog; - -namespace Phantom.Utils.Rpc.Message; - -sealed class MessageQueues { - private readonly ILogger logger; - private readonly TaskManager taskManager; - private readonly Dictionary<MessageQueueKey, RpcQueue> queues = new (); - - private Task? stopTask; - - public MessageQueues(string loggerName) { - this.logger = PhantomLogger.Create<MessageQueues>(loggerName); - this.taskManager = new TaskManager(PhantomLogger.Create<TaskManager>(loggerName)); - } - - private RpcQueue GetOrCreateQueue(MessageQueueKey key) { - if (!queues.TryGetValue(key, out var queue)) { - queues[key] = queue = new RpcQueue(taskManager, "Message queue for " + key.Name); - } - - return queue; - } - - public Task Enqueue(MessageQueueKey key, Func<Task> task) { - lock (this) { - return stopTask == null ? GetOrCreateQueue(key).Enqueue(task) : Task.FromException(new OperationCanceledException()); - } - } - - public Task<T> Enqueue<T>(MessageQueueKey key, Func<Task<T>> task) { - lock (this) { - return stopTask == null ? GetOrCreateQueue(key).Enqueue(task) : Task.FromException<T>(new OperationCanceledException()); - } - } - - internal Task Stop() { - lock (this) { - if (stopTask == null) { - logger.Debug("Stopping " + queues.Count + " message queue(s)..."); - - stopTask = Task.WhenAll(queues.Values.Select(static queue => queue.Stop())) - .ContinueWith(_ => logger.Debug("All queues stopped.")); - - queues.Clear(); - } - - return stopTask; - } - } -} diff --git a/Utils/Phantom.Utils.Rpc/Message/MessageRegistry.cs b/Utils/Phantom.Utils.Rpc/Message/MessageRegistry.cs index d6d0f63..4f3a66a 100644 --- a/Utils/Phantom.Utils.Rpc/Message/MessageRegistry.cs +++ b/Utils/Phantom.Utils.Rpc/Message/MessageRegistry.cs @@ -1,41 +1,49 @@ using System.Buffers; using System.Diagnostics.CodeAnalysis; +using Phantom.Utils.Actor; using Serilog; using Serilog.Events; namespace Phantom.Utils.Rpc.Message; -public sealed class MessageRegistry<TListener> { +public sealed class MessageRegistry<TMessageBase> { private const int DefaultBufferSize = 512; private readonly ILogger logger; private readonly Dictionary<Type, ushort> typeToCodeMapping = new (); private readonly Dictionary<ushort, Type> codeToTypeMapping = new (); - private readonly Dictionary<ushort, Action<ReadOnlyMemory<byte>, ushort, MessageHandler<TListener>>> codeToHandlerMapping = new (); + private readonly Dictionary<ushort, Action<ReadOnlyMemory<byte>, ushort, MessageHandler<TMessageBase>>> codeToHandlerMapping = new (); public MessageRegistry(ILogger logger) { this.logger = logger; } - public void Add<TMessage>(ushort code) where TMessage : IMessage<TListener, NoReply> { - AddTypeCodeMapping<TMessage, NoReply>(code); + public void Add<TMessage>(ushort code) where TMessage : TMessageBase { + if (HasReplyType(typeof(TMessage))) { + throw new ArgumentException("This overload is for messages without a reply"); + } + + AddTypeCodeMapping<TMessage>(code); codeToHandlerMapping.Add(code, DeserializationHandler<TMessage>); } - public void Add<TMessage, TReply>(ushort code) where TMessage : IMessage<TListener, TReply> { - if (typeof(TReply) == typeof(NoReply)) { - throw new InvalidOperationException("This overload of Add must not be used with NoReply as the reply type!"); - } - - AddTypeCodeMapping<TMessage, TReply>(code); + public void Add<TMessage, TReply>(ushort code) where TMessage : TMessageBase, ICanReply<TReply> { + AddTypeCodeMapping<TMessage>(code); codeToHandlerMapping.Add(code, DeserializationHandler<TMessage, TReply>); } - private void AddTypeCodeMapping<TMessage, TReply>(ushort code) where TMessage : IMessage<TListener, TReply> { + private void AddTypeCodeMapping<TMessage>(ushort code) where TMessage : TMessageBase { typeToCodeMapping.Add(typeof(TMessage), code); codeToTypeMapping.Add(code, typeof(TMessage)); } + private bool HasReplyType(Type messageType) { + string replyInterfaceName = typeof(ICanReply<object>).FullName!; + replyInterfaceName = replyInterfaceName[..(replyInterfaceName.IndexOf('`') + 1)]; + + return messageType.GetInterfaces().Any(type => type.FullName is {} name && name.StartsWith(replyInterfaceName, StringComparison.Ordinal)); + } + internal bool TryGetType(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out Type? type) { try { var code = MessageSerializer.ReadCode(ref data); @@ -46,13 +54,27 @@ public sealed class MessageRegistry<TListener> { } } - public ReadOnlySpan<byte> Write<TMessage>(TMessage message) where TMessage : IMessage<TListener, NoReply> { - return Write<TMessage, NoReply>(0, message); + public ReadOnlySpan<byte> Write<TMessage>(TMessage message) where TMessage : TMessageBase { + if (!GetMessageCode<TMessage>(out var code)) { + return default; + } + + var buffer = new ArrayBufferWriter<byte>(DefaultBufferSize); + + try { + MessageSerializer.WriteCode(buffer, code); + MessageSerializer.Serialize(buffer, message); + + CheckWrittenBufferLength<TMessage>(buffer); + return buffer.WrittenSpan; + } catch (Exception e) { + LogWriteFailure<TMessage>(e); + return default; + } } - public ReadOnlySpan<byte> Write<TMessage, TReply>(uint sequenceId, TMessage message) where TMessage : IMessage<TListener, TReply> { - if (!typeToCodeMapping.TryGetValue(typeof(TMessage), out ushort code)) { - logger.Error("Unknown message type {Type}.", typeof(TMessage)); + public ReadOnlySpan<byte> Write<TMessage, TReply>(uint sequenceId, TMessage message) where TMessage : TMessageBase, ICanReply<TReply> { + if (!GetMessageCode<TMessage>(out var code)) { return default; } @@ -60,30 +82,49 @@ public sealed class MessageRegistry<TListener> { try { MessageSerializer.WriteCode(buffer, code); - - if (typeof(TReply) != typeof(NoReply)) { - MessageSerializer.WriteSequenceId(buffer, sequenceId); - } - + MessageSerializer.WriteSequenceId(buffer, sequenceId); MessageSerializer.Serialize(buffer, message); - if (buffer.WrittenCount > DefaultBufferSize && logger.IsEnabled(LogEventLevel.Verbose)) { - logger.Verbose("Serializing {Type} exceeded default buffer size: {WrittenSize} B > {DefaultBufferSize} B", typeof(TMessage).Name, buffer.WrittenCount, DefaultBufferSize); - } - + CheckWrittenBufferLength<TMessage>(buffer); return buffer.WrittenSpan; } catch (Exception e) { - logger.Error(e, "Failed to serialize message {Type}.", typeof(TMessage).Name); + LogWriteFailure<TMessage>(e); return default; } } - internal void Handle(ReadOnlyMemory<byte> data, MessageHandler<TListener> handler) { - ushort code; - try { - code = MessageSerializer.ReadCode(ref data); - } catch (Exception e) { - logger.Error(e, "Failed to deserialize message code."); + private bool GetMessageCode<TMessage>(out ushort code) where TMessage : TMessageBase { + if (typeToCodeMapping.TryGetValue(typeof(TMessage), out code)) { + return true; + } + else { + logger.Error("Unknown message type {Type}.", typeof(TMessage)); + return false; + } + } + + private void CheckWrittenBufferLength<TMessage>(ArrayBufferWriter<byte> buffer) where TMessage : TMessageBase { + if (buffer.WrittenCount > DefaultBufferSize && logger.IsEnabled(LogEventLevel.Verbose)) { + logger.Verbose("Serializing {Type} exceeded default buffer size: {WrittenSize} B > {DefaultBufferSize} B", typeof(TMessage).Name, buffer.WrittenCount, DefaultBufferSize); + } + } + + private void LogWriteFailure<TMessage>(Exception e) where TMessage : TMessageBase { + logger.Error(e, "Failed to serialize message {Type}.", typeof(TMessage).Name); + } + + internal bool Read<TMessage>(ReadOnlyMemory<byte> data, out TMessage message) where TMessage : TMessageBase { + if (ReadTypeCode(ref data, out ushort code) && codeToTypeMapping.TryGetValue(code, out var expectedType) && expectedType == typeof(TMessage) && ReadMessage(data, out message)) { + return true; + } + else { + message = default!; + return false; + } + } + + internal void Handle(ReadOnlyMemory<byte> data, MessageHandler<TMessageBase> handler) { + if (!ReadTypeCode(ref data, out var code)) { return; } @@ -95,31 +136,48 @@ public sealed class MessageRegistry<TListener> { handle(data, code, handler); } - private void DeserializationHandler<TMessage>(ReadOnlyMemory<byte> data, ushort code, MessageHandler<TListener> handler) where TMessage : IMessage<TListener, NoReply> { - DeserializeAndEnqueueMessage<TMessage, NoReply>(data, code, handler, 0); + private bool ReadTypeCode(ref ReadOnlyMemory<byte> data, out ushort code) { + try { + code = MessageSerializer.ReadCode(ref data); + return true; + } catch (Exception e) { + code = default; + logger.Error(e, "Failed to deserialize message code."); + return false; + } } - private void DeserializationHandler<TMessage, TReply>(ReadOnlyMemory<byte> data, ushort code, MessageHandler<TListener> handler) where TMessage : IMessage<TListener, TReply> { - uint sequenceId; + private bool ReadSequenceId<TMessage, TReply>(ref ReadOnlyMemory<byte> data, out uint sequenceId) where TMessage : TMessageBase, ICanReply<TReply> { try { sequenceId = MessageSerializer.ReadSequenceId(ref data); + return true; } catch (Exception e) { - logger.Error(e, "Failed to deserialize sequence ID of message with code {Code}.", code); - return; + sequenceId = default; + logger.Error(e, "Failed to deserialize sequence ID of message {Type}.", typeof(TMessage).Name); + return false; } - - DeserializeAndEnqueueMessage<TMessage, TReply>(data, code, handler, sequenceId); } - private void DeserializeAndEnqueueMessage<TMessage, TReply>(ReadOnlyMemory<byte> data, ushort code, MessageHandler<TListener> handler, uint sequenceId) where TMessage : IMessage<TListener, TReply> { - TMessage message; + private bool ReadMessage<TMessage>(ReadOnlyMemory<byte> data, out TMessage message) where TMessage : TMessageBase { try { message = MessageSerializer.Deserialize<TMessage>(data); + return true; } catch (Exception e) { - logger.Error(e, "Failed to deserialize message with code {Code}.", code); - return; + message = default!; + logger.Error(e, "Failed to deserialize message {Type}.", typeof(TMessage).Name); + return false; } + } - handler.Enqueue<TMessage, TReply>(sequenceId, message); + private void DeserializationHandler<TMessage>(ReadOnlyMemory<byte> data, ushort code, MessageHandler<TMessageBase> handler) where TMessage : TMessageBase { + if (ReadMessage<TMessage>(data, out var message)) { + handler.Tell(message); + } + } + + private void DeserializationHandler<TMessage, TReply>(ReadOnlyMemory<byte> data, ushort code, MessageHandler<TMessageBase> handler) where TMessage : TMessageBase, ICanReply<TReply> { + if (ReadSequenceId<TMessage, TReply>(ref data, out var sequenceId) && ReadMessage<TMessage>(data, out var message)) { + handler.TellAndReply<TMessage, TReply>(message, sequenceId); + } } } diff --git a/Utils/Phantom.Utils.Rpc/Message/MessageSerializer.cs b/Utils/Phantom.Utils.Rpc/Message/MessageSerializer.cs index 8a0701d..40bb28b 100644 --- a/Utils/Phantom.Utils.Rpc/Message/MessageSerializer.cs +++ b/Utils/Phantom.Utils.Rpc/Message/MessageSerializer.cs @@ -18,7 +18,7 @@ static class MessageSerializer { public static T Deserialize<T>(ReadOnlyMemory<byte> memory) { return MemoryPackSerializer.Deserialize<T>(memory.Span) ?? throw new NullReferenceException(); } - + public static void WriteCode(IBufferWriter<byte> destination, ushort value) { Span<byte> buffer = stackalloc byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(buffer, value); diff --git a/Utils/Phantom.Utils.Rpc/Message/NoReply.cs b/Utils/Phantom.Utils.Rpc/Message/NoReply.cs deleted file mode 100644 index 20354a7..0000000 --- a/Utils/Phantom.Utils.Rpc/Message/NoReply.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Phantom.Utils.Rpc.Message; - -public readonly struct NoReply { - public static NoReply Instance { get; } = new (); -} diff --git a/Utils/Phantom.Utils.Rpc/Message/ReplySender.cs b/Utils/Phantom.Utils.Rpc/Message/ReplySender.cs new file mode 100644 index 0000000..0c2377a --- /dev/null +++ b/Utils/Phantom.Utils.Rpc/Message/ReplySender.cs @@ -0,0 +1,17 @@ +using Phantom.Utils.Rpc.Runtime; + +namespace Phantom.Utils.Rpc.Message; + +sealed class ReplySender<TMessageBase, TReplyMessage> : IReplySender where TReplyMessage : TMessageBase { + private readonly RpcConnection<TMessageBase> connection; + private readonly IReplyMessageFactory<TReplyMessage> replyMessageFactory; + + public ReplySender(RpcConnection<TMessageBase> connection, IReplyMessageFactory<TReplyMessage> replyMessageFactory) { + this.connection = connection; + this.replyMessageFactory = replyMessageFactory; + } + + public Task SendReply(uint sequenceId, byte[] serializedReply) { + return connection.Send(replyMessageFactory.CreateReplyMessage(sequenceId, serializedReply)); + } +} diff --git a/Utils/Phantom.Utils.Rpc/Phantom.Utils.Rpc.csproj b/Utils/Phantom.Utils.Rpc/Phantom.Utils.Rpc.csproj index 5e70e64..e07d7ec 100644 --- a/Utils/Phantom.Utils.Rpc/Phantom.Utils.Rpc.csproj +++ b/Utils/Phantom.Utils.Rpc/Phantom.Utils.Rpc.csproj @@ -12,6 +12,7 @@ </ItemGroup> <ItemGroup> + <ProjectReference Include="..\Phantom.Utils.Actor\Phantom.Utils.Actor.csproj" /> <ProjectReference Include="..\Phantom.Utils\Phantom.Utils.csproj" /> <ProjectReference Include="..\Phantom.Utils.Logging\Phantom.Utils.Logging.csproj" /> </ItemGroup> diff --git a/Utils/Phantom.Utils.Rpc/RpcConfiguration.cs b/Utils/Phantom.Utils.Rpc/RpcConfiguration.cs index f730671..84c254f 100644 --- a/Utils/Phantom.Utils.Rpc/RpcConfiguration.cs +++ b/Utils/Phantom.Utils.Rpc/RpcConfiguration.cs @@ -2,6 +2,7 @@ namespace Phantom.Utils.Rpc; -public sealed record RpcConfiguration(string LoggerName, string Host, ushort Port, NetMQCertificate ServerCertificate) { - public string TcpUrl => "tcp://" + Host + ":" + Port; +public sealed record RpcConfiguration(string ServiceName, string Host, ushort Port, NetMQCertificate ServerCertificate) { + internal string LoggerName => "Rpc:" + ServiceName; + internal string TcpUrl => "tcp://" + Host + ":" + Port; } diff --git a/Utils/Phantom.Utils.Rpc/RpcQueue.cs b/Utils/Phantom.Utils.Rpc/RpcQueue.cs deleted file mode 100644 index d27c7e7..0000000 --- a/Utils/Phantom.Utils.Rpc/RpcQueue.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Threading.Channels; -using Phantom.Utils.Tasks; - -namespace Phantom.Utils.Rpc; - -sealed class RpcQueue { - private readonly Channel<Func<Task>> channel = Channel.CreateUnbounded<Func<Task>>(new UnboundedChannelOptions { - SingleReader = true, - SingleWriter = false, - AllowSynchronousContinuations = false - }); - - private readonly Task processingTask; - - public RpcQueue(TaskManager taskManager, string taskName) { - this.processingTask = taskManager.Run(taskName, Process); - } - - public Task Enqueue(Action action) { - return Enqueue(() => { - action(); - return Task.CompletedTask; - }); - } - - public Task Enqueue(Func<Task> task) { - var completionSource = AsyncTasks.CreateCompletionSource(); - - if (!channel.Writer.TryWrite(() => task().ContinueWith(t => completionSource.SetResultFrom(t)))) { - completionSource.SetCanceled(); - } - - return completionSource.Task; - } - - public Task<T> Enqueue<T>(Func<Task<T>> task) { - var completionSource = AsyncTasks.CreateCompletionSource<T>(); - - if (!channel.Writer.TryWrite(() => task().ContinueWith(t => completionSource.SetResultFrom(t)))) { - completionSource.SetCanceled(); - } - - return completionSource.Task; - } - - private async Task Process() { - try { - await foreach (var task in channel.Reader.ReadAllAsync()) { - await task(); - } - } catch (OperationCanceledException) { - // Ignore. - } - } - - public Task Stop() { - channel.Writer.Complete(); - return processingTask; - } -} diff --git a/Utils/Phantom.Utils.Rpc/Runtime/IRegistrationHandler.cs b/Utils/Phantom.Utils.Rpc/Runtime/IRegistrationHandler.cs new file mode 100644 index 0000000..41ebdeb --- /dev/null +++ b/Utils/Phantom.Utils.Rpc/Runtime/IRegistrationHandler.cs @@ -0,0 +1,7 @@ +using Phantom.Utils.Actor; + +namespace Phantom.Utils.Rpc.Runtime; + +public interface IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> where TRegistrationMessage : TServerMessage { + Task<Props<TServerMessage>?> TryRegister(RpcConnectionToClient<TClientMessage> connection, TRegistrationMessage message); +} diff --git a/Utils/Phantom.Utils.Rpc/Runtime/RpcClientRuntime.cs b/Utils/Phantom.Utils.Rpc/Runtime/RpcClientRuntime.cs index 3de2b11..3a1bb67 100644 --- a/Utils/Phantom.Utils.Rpc/Runtime/RpcClientRuntime.cs +++ b/Utils/Phantom.Utils.Rpc/Runtime/RpcClientRuntime.cs @@ -1,4 +1,5 @@ using NetMQ.Sockets; +using Phantom.Utils.Actor; using Phantom.Utils.Rpc.Message; using Phantom.Utils.Rpc.Sockets; using Serilog; @@ -6,18 +7,18 @@ using Serilog.Events; namespace Phantom.Utils.Rpc.Runtime; -public abstract class RpcClientRuntime<TClientListener, TServerListener, TReplyMessage> : RpcRuntime<ClientSocket> where TReplyMessage : IMessage<TClientListener, NoReply>, IMessage<TServerListener, NoReply> { - private readonly RpcConnectionToServer<TServerListener> connection; - private readonly IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions; - private readonly TClientListener messageListener; +public abstract class RpcClientRuntime<TClientMessage, TServerMessage, TReplyMessage> : RpcRuntime<ClientSocket> where TReplyMessage : TClientMessage, TServerMessage { + private readonly RpcConnectionToServer<TServerMessage> connection; + private readonly IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions; + private readonly ActorRef<TClientMessage> handlerActor; private readonly SemaphoreSlim disconnectSemaphore; private readonly CancellationToken receiveCancellationToken; - protected RpcClientRuntime(RpcClientSocket<TClientListener, TServerListener, TReplyMessage> socket, TClientListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket) { + protected RpcClientRuntime(RpcClientSocket<TClientMessage, TServerMessage, TReplyMessage> socket, ActorRef<TClientMessage> handlerActor, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket) { this.connection = socket.Connection; this.messageDefinitions = socket.MessageDefinitions; - this.messageListener = messageListener; + this.handlerActor = handlerActor; this.disconnectSemaphore = disconnectSemaphore; this.receiveCancellationToken = receiveCancellationToken; } @@ -26,8 +27,9 @@ public abstract class RpcClientRuntime<TClientListener, TServerListener, TReplyM return RunWithConnection(socket, connection); } - protected virtual async Task RunWithConnection(ClientSocket socket, RpcConnectionToServer<TServerListener> connection) { - var handler = new Handler(LoggerName, connection, messageDefinitions, messageListener); + protected virtual async Task RunWithConnection(ClientSocket socket, RpcConnectionToServer<TServerMessage> connection) { + var replySender = new ReplySender<TServerMessage, TReplyMessage>(connection, messageDefinitions); + var messageHandler = new MessageHandler<TClientMessage>(LoggerName, handlerActor, replySender); try { while (!receiveCancellationToken.IsCancellationRequested) { @@ -36,13 +38,13 @@ public abstract class RpcClientRuntime<TClientListener, TServerListener, TReplyM LogMessageType(RuntimeLogger, data); if (data.Length > 0) { - messageDefinitions.ToClient.Handle(data, handler); + messageDefinitions.ToClient.Handle(data, messageHandler); } } } catch (OperationCanceledException) { // Ignore. } finally { - await handler.StopReceiving(); + await handlerActor.Stop(); RuntimeLogger.Debug("ZeroMQ client stopped receiving messages."); await disconnectSemaphore.WaitAsync(CancellationToken.None); @@ -50,12 +52,6 @@ public abstract class RpcClientRuntime<TClientListener, TServerListener, TReplyM } private protected sealed override async Task Disconnect(ClientSocket socket) { - try { - await connection.StopSending().WaitAsync(TimeSpan.FromSeconds(10), CancellationToken.None); - } catch (TimeoutException) { - RuntimeLogger.Error("Timed out waiting for message sending queue."); - } - await SendDisconnectMessage(socket, RuntimeLogger); } @@ -73,18 +69,4 @@ public abstract class RpcClientRuntime<TClientListener, TServerListener, TReplyM logger.Verbose("Received {Bytes} B message.", data.Length); } } - - private sealed class Handler : MessageHandler<TClientListener> { - private readonly RpcConnectionToServer<TServerListener> connection; - private readonly IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions; - - public Handler(string loggerName, RpcConnectionToServer<TServerListener> connection, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, TClientListener listener) : base(loggerName, listener) { - this.connection = connection; - this.messageDefinitions = messageDefinitions; - } - - protected override Task SendReply(uint sequenceId, byte[] serializedReply) { - return connection.Send(messageDefinitions.CreateReplyMessage(sequenceId, serializedReply)); - } - } } diff --git a/Utils/Phantom.Utils.Rpc/Runtime/RpcConnection.cs b/Utils/Phantom.Utils.Rpc/Runtime/RpcConnection.cs index b4a0fd5..c4ac00a 100644 --- a/Utils/Phantom.Utils.Rpc/Runtime/RpcConnection.cs +++ b/Utils/Phantom.Utils.Rpc/Runtime/RpcConnection.cs @@ -1,36 +1,27 @@ -using Phantom.Utils.Rpc.Message; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Message; namespace Phantom.Utils.Rpc.Runtime; -public abstract class RpcConnection<TListener> { - private readonly MessageRegistry<TListener> messageRegistry; - private readonly MessageQueues sendingQueues; +public abstract class RpcConnection<TMessageBase> { + private readonly MessageRegistry<TMessageBase> messageRegistry; private readonly MessageReplyTracker replyTracker; - internal RpcConnection(string loggerName, MessageRegistry<TListener> messageRegistry, MessageReplyTracker replyTracker) { + internal RpcConnection(MessageRegistry<TMessageBase> messageRegistry, MessageReplyTracker replyTracker) { this.messageRegistry = messageRegistry; - this.sendingQueues = new MessageQueues(loggerName + ":Send"); this.replyTracker = replyTracker; } private protected abstract ValueTask Send(byte[] bytes); - public Task Send<TMessage>(TMessage message) where TMessage : IMessage<TListener, NoReply> { - return sendingQueues.Enqueue(message.QueueKey, () => SendTask(message)); - } - - public Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : IMessage<TListener, TReply> { - return sendingQueues.Enqueue(message.QueueKey, () => SendTask<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken)); - } - - private async Task SendTask<TMessage>(TMessage message) where TMessage : IMessage<TListener, NoReply> { + public async Task Send<TMessage>(TMessage message) where TMessage : TMessageBase { var bytes = messageRegistry.Write(message).ToArray(); if (bytes.Length > 0) { await Send(bytes); } } - private async Task<TReply> SendTask<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : IMessage<TListener, TReply> { + public async Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : TMessageBase, ICanReply<TReply> { var sequenceId = replyTracker.RegisterReply(); var bytes = messageRegistry.Write<TMessage, TReply>(sequenceId, message).ToArray(); @@ -46,8 +37,4 @@ public abstract class RpcConnection<TListener> { public void Receive(IReply message) { replyTracker.ReceiveReply(message.SequenceId, message.SerializedReply); } - - internal Task StopSending() { - return sendingQueues.Stop(); - } } diff --git a/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToClient.cs b/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToClient.cs index ffa6123..788c6a0 100644 --- a/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToClient.cs +++ b/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToClient.cs @@ -4,29 +4,19 @@ using Phantom.Utils.Rpc.Message; namespace Phantom.Utils.Rpc.Runtime; -public sealed class RpcConnectionToClient<TListener> : RpcConnection<TListener> { +public sealed class RpcConnectionToClient<TMessageBase> : RpcConnection<TMessageBase> { private readonly ServerSocket socket; private readonly uint routingId; - private readonly TaskCompletionSource<bool> authorizationCompletionSource = new (); - internal event EventHandler<RpcClientConnectionClosedEventArgs>? Closed; public bool IsClosed { get; private set; } - internal RpcConnectionToClient(string loggerName, ServerSocket socket, uint routingId, MessageRegistry<TListener> messageRegistry, MessageReplyTracker replyTracker) : base(loggerName, messageRegistry, replyTracker) { + internal RpcConnectionToClient(ServerSocket socket, uint routingId, MessageRegistry<TMessageBase> messageRegistry, MessageReplyTracker replyTracker) : base(messageRegistry, replyTracker) { this.socket = socket; this.routingId = routingId; } - internal Task<bool> GetAuthorization() { - return authorizationCompletionSource.Task; - } - - public void SetAuthorizationResult(bool isAuthorized) { - authorizationCompletionSource.SetResult(isAuthorized); - } - - public bool IsSame(RpcConnectionToClient<TListener> other) { + public bool IsSame(RpcConnectionToClient<TMessageBase> other) { return this.routingId == other.routingId && this.socket == other.socket; } diff --git a/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToServer.cs b/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToServer.cs index 638e0d6..bb3ba06 100644 --- a/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToServer.cs +++ b/Utils/Phantom.Utils.Rpc/Runtime/RpcConnectionToServer.cs @@ -5,13 +5,13 @@ using Phantom.Utils.Tasks; namespace Phantom.Utils.Rpc.Runtime; -public sealed class RpcConnectionToServer<TListener> : RpcConnection<TListener> { +public sealed class RpcConnectionToServer<TMessageBase> : RpcConnection<TMessageBase> { private readonly ClientSocket socket; private readonly TaskCompletionSource isReady = AsyncTasks.CreateCompletionSource(); public Task IsReady => isReady.Task; - internal RpcConnectionToServer(string loggerName, ClientSocket socket, MessageRegistry<TListener> messageRegistry, MessageReplyTracker replyTracker) : base(loggerName, messageRegistry, replyTracker) { + internal RpcConnectionToServer(ClientSocket socket, MessageRegistry<TMessageBase> messageRegistry, MessageReplyTracker replyTracker) : base(messageRegistry, replyTracker) { this.socket = socket; } diff --git a/Utils/Phantom.Utils.Rpc/Runtime/RpcReceiverActor.cs b/Utils/Phantom.Utils.Rpc/Runtime/RpcReceiverActor.cs new file mode 100644 index 0000000..9b5b4bb --- /dev/null +++ b/Utils/Phantom.Utils.Rpc/Runtime/RpcReceiverActor.cs @@ -0,0 +1,75 @@ +using Akka.Actor; +using Akka.Event; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Message; + +namespace Phantom.Utils.Rpc.Runtime; + +sealed class RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage> : ReceiveActor<RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>.ReceiveMessageCommand>, IWithStash where TRegistrationMessage : TServerMessage where TReplyMessage : TClientMessage, TServerMessage { + public readonly record struct Init( + string LoggerName, + IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> MessageDefinitions, + IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> RegistrationHandler, + RpcConnectionToClient<TClientMessage> Connection + ); + + public static Props<ReceiveMessageCommand> Factory(Init init) { + return Props<ReceiveMessageCommand>.Create(() => new RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>(init), new ActorConfiguration { + SupervisorStrategy = SupervisorStrategies.Resume, + StashCapacity = 100 + }); + } + + public IStash Stash { get; set; } = null!; + + private readonly string loggerName; + private readonly IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions; + private readonly IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> registrationHandler; + private readonly RpcConnectionToClient<TClientMessage> connection; + + private RpcReceiverActor(Init init) { + this.loggerName = init.LoggerName; + this.messageDefinitions = init.MessageDefinitions; + this.registrationHandler = init.RegistrationHandler; + this.connection = init.Connection; + + ReceiveAsync<ReceiveMessageCommand>(ReceiveMessageUnauthorized); + } + + public sealed record ReceiveMessageCommand(Type MessageType, ReadOnlyMemory<byte> Data); + + private async Task ReceiveMessageUnauthorized(ReceiveMessageCommand command) { + if (command.MessageType == typeof(TRegistrationMessage)) { + await HandleRegistrationMessage(command); + } + else if (Stash.IsFull) { + Context.GetLogger().Warning("Stash is full, dropping message: {MessageType}", command.MessageType); + } + else { + Stash.Stash(); + } + } + + private async Task HandleRegistrationMessage(ReceiveMessageCommand command) { + if (!messageDefinitions.ToServer.Read(command.Data, out TRegistrationMessage message)) { + return; + } + + var props = await registrationHandler.TryRegister(connection, message); + if (props == null) { + return; + } + + var handlerActor = Context.ActorOf(props, "Handler"); + var replySender = new ReplySender<TClientMessage, TReplyMessage>(connection, messageDefinitions); + BecomeAuthorized(new MessageHandler<TServerMessage>(loggerName, handlerActor, replySender)); + } + + private void BecomeAuthorized(MessageHandler<TServerMessage> handler) { + Stash.UnstashAll(); + + Become(() => { + Receive<ReceiveMessageCommand>(command => messageDefinitions.ToServer.Handle(command.Data, handler)); + }); + } +} diff --git a/Utils/Phantom.Utils.Rpc/Runtime/RpcServerRuntime.cs b/Utils/Phantom.Utils.Rpc/Runtime/RpcServerRuntime.cs index 002b58e..6bbbaec 100644 --- a/Utils/Phantom.Utils.Rpc/Runtime/RpcServerRuntime.cs +++ b/Utils/Phantom.Utils.Rpc/Runtime/RpcServerRuntime.cs @@ -1,34 +1,44 @@ using System.Collections.Concurrent; +using Akka.Actor; using NetMQ.Sockets; +using Phantom.Utils.Actor; using Phantom.Utils.Logging; using Phantom.Utils.Rpc.Message; using Phantom.Utils.Rpc.Sockets; -using Phantom.Utils.Tasks; +using Serilog; using Serilog.Events; namespace Phantom.Utils.Rpc.Runtime; public static class RpcServerRuntime { - public static Task Launch<TClientListener, TServerListener, TReplyMessage>(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) where TReplyMessage : IMessage<TClientListener, NoReply>, IMessage<TServerListener, NoReply> { - return RpcServerRuntime<TClientListener, TServerListener, TReplyMessage>.Launch(config, messageDefinitions, listenerFactory, cancellationToken); + public static Task Launch<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>( + RpcConfiguration config, + IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions, + IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> registrationHandler, + IActorRefFactory actorSystem, + CancellationToken cancellationToken + ) where TRegistrationMessage : TServerMessage where TReplyMessage : TClientMessage, TServerMessage { + return RpcServerRuntime<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>.Launch(config, messageDefinitions, registrationHandler, actorSystem, cancellationToken); } } -internal sealed class RpcServerRuntime<TClientListener, TServerListener, TReplyMessage> : RpcRuntime<ServerSocket> where TReplyMessage : IMessage<TClientListener, NoReply>, IMessage<TServerListener, NoReply> { - internal static Task Launch(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) { +internal sealed class RpcServerRuntime<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage> : RpcRuntime<ServerSocket> where TRegistrationMessage : TServerMessage where TReplyMessage : TClientMessage, TServerMessage { + internal static Task Launch(RpcConfiguration config, IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions, IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> registrationHandler, IActorRefFactory actorSystem, CancellationToken cancellationToken) { var socket = RpcServerSocket.Connect(config); - return new RpcServerRuntime<TClientListener, TServerListener, TReplyMessage>(socket, messageDefinitions, listenerFactory, cancellationToken).Launch(); + return new RpcServerRuntime<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>(socket, messageDefinitions, registrationHandler, actorSystem, cancellationToken).Launch(); } - private readonly IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions; - private readonly Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory; - private readonly TaskManager taskManager; + private readonly string serviceName; + private readonly IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions; + private readonly IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> registrationHandler; + private readonly IActorRefFactory actorSystem; private readonly CancellationToken cancellationToken; - private RpcServerRuntime(RpcServerSocket socket, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) : base(socket) { + private RpcServerRuntime(RpcServerSocket socket, IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions, IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> registrationHandler, IActorRefFactory actorSystem, CancellationToken cancellationToken) : base(socket) { + this.serviceName = socket.Config.ServiceName; this.messageDefinitions = messageDefinitions; - this.listenerFactory = listenerFactory; - this.taskManager = new TaskManager(PhantomLogger.Create<TaskManager>(socket.Config.LoggerName + ":Runtime")); + this.registrationHandler = registrationHandler; + this.actorSystem = actorSystem; this.cancellationToken = cancellationToken; } @@ -56,31 +66,30 @@ internal sealed class RpcServerRuntime<TClientListener, TServerListener, TReplyM } if (!clients.TryGetValue(routingId, out var client)) { - if (!messageDefinitions.IsRegistrationMessage(messageType)) { + if (messageType != typeof(TRegistrationMessage)) { RuntimeLogger.Warning("Received {MessageType} ({Bytes} B) from unregistered client {RoutingId}.", messageType.Name, data.Length, routingId); continue; } var clientLoggerName = LoggerName + ":" + routingId; - var processingQueue = new RpcQueue(taskManager, "Process messages from " + routingId); - var connection = new RpcConnectionToClient<TClientListener>(clientLoggerName, socket, routingId, messageDefinitions.ToClient, ReplyTracker); + var clientActorName = "Rpc-" + serviceName + "-" + routingId; + // TODO add pings and tear down connection after too much inactivity + var connection = new RpcConnectionToClient<TClientMessage>(socket, routingId, messageDefinitions.ToClient, ReplyTracker); connection.Closed += OnConnectionClosed; - client = new Client(clientLoggerName, connection, processingQueue, messageDefinitions, listenerFactory(connection), taskManager); + client = new Client(clientLoggerName, clientActorName, connection, actorSystem, messageDefinitions, registrationHandler); clients[routingId] = client; - client.EnqueueRegistrationMessage(messageType, data); - } - else { - client.Enqueue(messageType, data); } + + client.Enqueue(messageType, data); } foreach (var client in clients.Values) { client.Connection.Close(); } - - return taskManager.Stop(); + + return Task.CompletedTask; } private void LogUnknownMessage(uint routingId, ReadOnlyMemory<byte> data) { @@ -91,66 +100,38 @@ internal sealed class RpcServerRuntime<TClientListener, TServerListener, TReplyM return Task.CompletedTask; } - private sealed class Client : MessageHandler<TServerListener> { - public RpcConnectionToClient<TClientListener> Connection { get; } + private sealed class Client { + public RpcConnectionToClient<TClientMessage> Connection { get; } - private readonly RpcQueue processingQueue; - private readonly IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions; - private readonly TaskManager taskManager; - - public Client(string loggerName, RpcConnectionToClient<TClientListener> connection, RpcQueue processingQueue, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, TServerListener listener, TaskManager taskManager) : base(loggerName, listener) { + private readonly ILogger logger; + private readonly ActorRef<RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>.ReceiveMessageCommand> receiverActor; + + public Client(string loggerName, string actorName, RpcConnectionToClient<TClientMessage> connection, IActorRefFactory actorSystem, IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions, IRegistrationHandler<TClientMessage, TServerMessage, TRegistrationMessage> registrationHandler) { this.Connection = connection; this.Connection.Closed += OnConnectionClosed; - - this.processingQueue = processingQueue; - this.messageDefinitions = messageDefinitions; - this.taskManager = taskManager; - } - internal void EnqueueRegistrationMessage(Type messageType, ReadOnlyMemory<byte> data) { - LogMessageType(messageType, data); - processingQueue.Enqueue(() => Handle(data)); + this.logger = PhantomLogger.Create(loggerName); + + var receiverActorInit = new RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>.Init(loggerName, messageDefinitions, registrationHandler, Connection); + this.receiverActor = actorSystem.ActorOf(RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>.Factory(receiverActorInit), actorName + "-Receiver"); } internal void Enqueue(Type messageType, ReadOnlyMemory<byte> data) { LogMessageType(messageType, data); - processingQueue.Enqueue(() => WaitForAuthorizationAndHandle(data)); + receiverActor.Tell(new RpcReceiverActor<TClientMessage, TServerMessage, TRegistrationMessage, TReplyMessage>.ReceiveMessageCommand(messageType, data)); } private void LogMessageType(Type messageType, ReadOnlyMemory<byte> data) { - if (Logger.IsEnabled(LogEventLevel.Verbose)) { - Logger.Verbose("Received {MessageType} ({Bytes} B).", messageType.Name, data.Length); + if (logger.IsEnabled(LogEventLevel.Verbose)) { + logger.Verbose("Received {MessageType} ({Bytes} B).", messageType.Name, data.Length); } } - private void Handle(ReadOnlyMemory<byte> data) { - messageDefinitions.ToServer.Handle(data, this); - } - - private async Task WaitForAuthorizationAndHandle(ReadOnlyMemory<byte> data) { - if (await Connection.GetAuthorization()) { - Handle(data); - } - else { - Logger.Warning("Dropped message after failed registration."); - } - } - - protected override Task SendReply(uint sequenceId, byte[] serializedReply) { - return Connection.Send(messageDefinitions.CreateReplyMessage(sequenceId, serializedReply)); - } - private void OnConnectionClosed(object? sender, RpcClientConnectionClosedEventArgs e) { Connection.Closed -= OnConnectionClosed; - Logger.Debug("Closing connection..."); - - taskManager.Run("Closing connection to " + e.RoutingId, async () => { - await StopReceiving(); - await processingQueue.Stop(); - await Connection.StopSending(); - Logger.Debug("Connection closed."); - }); + logger.Debug("Closing connection..."); + receiverActor.Stop(); } } } diff --git a/Utils/Phantom.Utils.Rpc/Sockets/RpcClientSocket.cs b/Utils/Phantom.Utils.Rpc/Sockets/RpcClientSocket.cs index bf25889..7aa7f2b 100644 --- a/Utils/Phantom.Utils.Rpc/Sockets/RpcClientSocket.cs +++ b/Utils/Phantom.Utils.Rpc/Sockets/RpcClientSocket.cs @@ -7,13 +7,13 @@ using Phantom.Utils.Rpc.Runtime; namespace Phantom.Utils.Rpc.Sockets; public static class RpcClientSocket { - public static RpcClientSocket<TClientListener, TServerListener, TReplyMessage> Connect<TClientListener, TServerListener, TReplyMessage, THelloMessage>(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, THelloMessage helloMessage) where THelloMessage : IMessage<TServerListener, NoReply> where TReplyMessage : IMessage<TClientListener, NoReply>, IMessage<TServerListener, NoReply> { - return RpcClientSocket<TClientListener, TServerListener, TReplyMessage>.Connect(config, messageDefinitions, helloMessage); + public static RpcClientSocket<TClientMessage, TServerMessage, TReplyMessage> Connect<TClientMessage, TServerMessage, TReplyMessage, THelloMessage>(RpcConfiguration config, IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions, THelloMessage helloMessage) where THelloMessage : TServerMessage where TReplyMessage : TClientMessage, TServerMessage { + return RpcClientSocket<TClientMessage, TServerMessage, TReplyMessage>.Connect(config, messageDefinitions, helloMessage); } } -public sealed class RpcClientSocket<TClientListener, TServerListener, TReplyMessage> : RpcSocket<ClientSocket> where TReplyMessage : IMessage<TClientListener, NoReply>, IMessage<TServerListener, NoReply> { - internal static RpcClientSocket<TClientListener, TServerListener, TReplyMessage> Connect<THelloMessage>(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions, THelloMessage helloMessage) where THelloMessage : IMessage<TServerListener, NoReply> { +public sealed class RpcClientSocket<TClientMessage, TServerMessage, TReplyMessage> : RpcSocket<ClientSocket> where TReplyMessage : TClientMessage, TServerMessage { + internal static RpcClientSocket<TClientMessage, TServerMessage, TReplyMessage> Connect<THelloMessage>(RpcConfiguration config, IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions, THelloMessage helloMessage) where THelloMessage : TServerMessage { var socket = new ClientSocket(); var options = socket.Options; @@ -29,14 +29,14 @@ public sealed class RpcClientSocket<TClientListener, TServerListener, TReplyMess socket.Connect(url); logger.Information("ZeroMQ client ready."); - return new RpcClientSocket<TClientListener, TServerListener, TReplyMessage>(socket, config, messageDefinitions); + return new RpcClientSocket<TClientMessage, TServerMessage, TReplyMessage>(socket, config, messageDefinitions); } - public RpcConnectionToServer<TServerListener> Connection { get; } - internal IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> MessageDefinitions { get; } + public RpcConnectionToServer<TServerMessage> Connection { get; } + internal IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> MessageDefinitions { get; } - private RpcClientSocket(ClientSocket socket, RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener, TReplyMessage> messageDefinitions) : base(socket, config) { + private RpcClientSocket(ClientSocket socket, RpcConfiguration config, IMessageDefinitions<TClientMessage, TServerMessage, TReplyMessage> messageDefinitions) : base(socket, config) { MessageDefinitions = messageDefinitions; - Connection = new RpcConnectionToServer<TServerListener>(config.LoggerName, socket, messageDefinitions.ToServer, ReplyTracker); + Connection = new RpcConnectionToServer<TServerMessage>(socket, messageDefinitions.ToServer, ReplyTracker); } } diff --git a/Utils/Phantom.Utils.Rpc/Sockets/RpcServerSocket.cs b/Utils/Phantom.Utils.Rpc/Sockets/RpcServerSocket.cs index c4b9ced..70ebdea 100644 --- a/Utils/Phantom.Utils.Rpc/Sockets/RpcServerSocket.cs +++ b/Utils/Phantom.Utils.Rpc/Sockets/RpcServerSocket.cs @@ -3,7 +3,7 @@ using Phantom.Utils.Logging; namespace Phantom.Utils.Rpc.Sockets; -public sealed class RpcServerSocket : RpcSocket<ServerSocket> { +sealed class RpcServerSocket : RpcSocket<ServerSocket> { public static RpcServerSocket Connect(RpcConfiguration config) { var socket = new ServerSocket(); var options = socket.Options; diff --git a/Utils/Phantom.Utils/Tasks/AsyncTasks.cs b/Utils/Phantom.Utils/Tasks/AsyncTasks.cs index 98fb26a..0414c45 100644 --- a/Utils/Phantom.Utils/Tasks/AsyncTasks.cs +++ b/Utils/Phantom.Utils/Tasks/AsyncTasks.cs @@ -8,28 +8,4 @@ public static class AsyncTasks { public static TaskCompletionSource<T> CreateCompletionSource<T>() { return new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously); } - - public static void SetResultFrom(this TaskCompletionSource completionSource, Task task) { - if (task.IsFaulted) { - completionSource.SetException(task.Exception.InnerExceptions); - } - else if (task.IsCanceled) { - completionSource.SetCanceled(); - } - else { - completionSource.SetResult(); - } - } - - public static void SetResultFrom<T>(this TaskCompletionSource<T> completionSource, Task<T> task) { - if (task.IsFaulted) { - completionSource.SetException(task.Exception.InnerExceptions); - } - else if (task.IsCanceled) { - completionSource.SetCanceled(); - } - else { - completionSource.SetResult(task.Result); - } - } } diff --git a/Web/Phantom.Web.Services/PhantomWebServices.cs b/Web/Phantom.Web.Services/PhantomWebServices.cs index ae0ec84..374eed4 100644 --- a/Web/Phantom.Web.Services/PhantomWebServices.cs +++ b/Web/Phantom.Web.Services/PhantomWebServices.cs @@ -14,7 +14,7 @@ namespace Phantom.Web.Services; public static class PhantomWebServices { public static void AddPhantomServices(this IServiceCollection services) { services.AddSingleton<ControllerConnection>(); - services.AddSingleton<MessageListener>(); + services.AddSingleton<ControllerMessageHandlerFactory>(); services.AddSingleton<AgentManager>(); services.AddSingleton<InstanceManager>(); diff --git a/Web/Phantom.Web.Services/Rpc/ControllerConnection.cs b/Web/Phantom.Web.Services/Rpc/ControllerConnection.cs index 2cc950e..a03bd4b 100644 --- a/Web/Phantom.Web.Services/Rpc/ControllerConnection.cs +++ b/Web/Phantom.Web.Services/Rpc/ControllerConnection.cs @@ -1,12 +1,13 @@ using Phantom.Common.Messages.Web; +using Phantom.Utils.Actor; using Phantom.Utils.Rpc.Runtime; namespace Phantom.Web.Services.Rpc; public sealed class ControllerConnection { - private readonly RpcConnectionToServer<IMessageToControllerListener> connection; + private readonly RpcConnectionToServer<IMessageToController> connection; - public ControllerConnection(RpcConnectionToServer<IMessageToControllerListener> connection) { + public ControllerConnection(RpcConnectionToServer<IMessageToController> connection) { this.connection = connection; } @@ -14,11 +15,11 @@ public sealed class ControllerConnection { return connection.Send(message); } - public Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken = default) where TMessage : IMessageToController<TReply> { + public Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken = default) where TMessage : IMessageToController, ICanReply<TReply> { return connection.Send<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken); } - public Task<TReply> Send<TMessage, TReply>(TMessage message, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToController<TReply> { + public Task<TReply> Send<TMessage, TReply>(TMessage message, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToController, ICanReply<TReply> { return connection.Send<TMessage, TReply>(message, Timeout.InfiniteTimeSpan, waitForReplyCancellationToken); } } diff --git a/Web/Phantom.Web.Services/Rpc/ControllerMessageHandlerActor.cs b/Web/Phantom.Web.Services/Rpc/ControllerMessageHandlerActor.cs new file mode 100644 index 0000000..7e2024f --- /dev/null +++ b/Web/Phantom.Web.Services/Rpc/ControllerMessageHandlerActor.cs @@ -0,0 +1,57 @@ +using Phantom.Common.Messages.Web; +using Phantom.Common.Messages.Web.BiDirectional; +using Phantom.Common.Messages.Web.ToWeb; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Runtime; +using Phantom.Web.Services.Agents; +using Phantom.Web.Services.Instances; + +namespace Phantom.Web.Services.Rpc; + +sealed class ControllerMessageHandlerActor : ReceiveActor<IMessageToWeb> { + public readonly record struct Init(RpcConnectionToServer<IMessageToController> Connection, AgentManager AgentManager, InstanceManager InstanceManager, InstanceLogManager InstanceLogManager, TaskCompletionSource<bool> RegisterSuccessWaiter); + + public static Props<IMessageToWeb> Factory(Init init) { + return Props<IMessageToWeb>.Create(() => new ControllerMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); + } + + private readonly RpcConnectionToServer<IMessageToController> connection; + private readonly AgentManager agentManager; + private readonly InstanceManager instanceManager; + private readonly InstanceLogManager instanceLogManager; + private readonly TaskCompletionSource<bool> registerSuccessWaiter; + + private ControllerMessageHandlerActor(Init init) { + this.connection = init.Connection; + this.agentManager = init.AgentManager; + this.instanceManager = init.InstanceManager; + this.instanceLogManager = init.InstanceLogManager; + this.registerSuccessWaiter = init.RegisterSuccessWaiter; + + Receive<RegisterWebResultMessage>(HandleRegisterWebResult); + Receive<RefreshAgentsMessage>(HandleRefreshAgents); + Receive<RefreshInstancesMessage>(HandleRefreshInstances); + Receive<InstanceOutputMessage>(HandleInstanceOutput); + Receive<ReplyMessage>(HandleReply); + } + + private void HandleRegisterWebResult(RegisterWebResultMessage message) { + registerSuccessWaiter.TrySetResult(message.Success); + } + + private void HandleRefreshAgents(RefreshAgentsMessage message) { + agentManager.RefreshAgents(message.Agents); + } + + private void HandleRefreshInstances(RefreshInstancesMessage message) { + instanceManager.RefreshInstances(message.Instances); + } + + private void HandleInstanceOutput(InstanceOutputMessage message) { + instanceLogManager.AddLines(message.InstanceGuid, message.Lines); + } + + private void HandleReply(ReplyMessage message) { + connection.Receive(message); + } +} diff --git a/Web/Phantom.Web.Services/Rpc/ControllerMessageHandlerFactory.cs b/Web/Phantom.Web.Services/Rpc/ControllerMessageHandlerFactory.cs new file mode 100644 index 0000000..3438483 --- /dev/null +++ b/Web/Phantom.Web.Services/Rpc/ControllerMessageHandlerFactory.cs @@ -0,0 +1,34 @@ +using Akka.Actor; +using Phantom.Common.Messages.Web; +using Phantom.Utils.Actor; +using Phantom.Utils.Rpc.Runtime; +using Phantom.Utils.Tasks; +using Phantom.Web.Services.Agents; +using Phantom.Web.Services.Instances; + +namespace Phantom.Web.Services.Rpc; + +public sealed class ControllerMessageHandlerFactory { + private readonly RpcConnectionToServer<IMessageToController> connection; + private readonly AgentManager agentManager; + private readonly InstanceManager instanceManager; + private readonly InstanceLogManager instanceLogManager; + + private readonly TaskCompletionSource<bool> registerSuccessWaiter = AsyncTasks.CreateCompletionSource<bool>(); + + public Task<bool> RegisterSuccessWaiter => registerSuccessWaiter.Task; + + private int messageHandlerId = 0; + + public ControllerMessageHandlerFactory(RpcConnectionToServer<IMessageToController> connection, AgentManager agentManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager) { + this.connection = connection; + this.agentManager = agentManager; + this.instanceManager = instanceManager; + this.instanceLogManager = instanceLogManager; + } + + public ActorRef<IMessageToWeb> Create(IActorRefFactory actorSystem) { + int id = Interlocked.Increment(ref messageHandlerId); + return actorSystem.ActorOf(ControllerMessageHandlerActor.Factory(new ControllerMessageHandlerActor.Init(connection, agentManager, instanceManager, instanceLogManager, registerSuccessWaiter)), "ControllerMessageHandler-" + id); + } +} diff --git a/Web/Phantom.Web.Services/Rpc/MessageListener.cs b/Web/Phantom.Web.Services/Rpc/MessageListener.cs deleted file mode 100644 index b0e3fe8..0000000 --- a/Web/Phantom.Web.Services/Rpc/MessageListener.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Phantom.Common.Messages.Web; -using Phantom.Common.Messages.Web.BiDirectional; -using Phantom.Common.Messages.Web.ToWeb; -using Phantom.Utils.Rpc.Message; -using Phantom.Utils.Rpc.Runtime; -using Phantom.Utils.Tasks; -using Phantom.Web.Services.Agents; -using Phantom.Web.Services.Instances; - -namespace Phantom.Web.Services.Rpc; - -public sealed class MessageListener : IMessageToWebListener { - public TaskCompletionSource<bool> RegisterSuccessWaiter { get; } = AsyncTasks.CreateCompletionSource<bool>(); - - private readonly RpcConnectionToServer<IMessageToControllerListener> connection; - private readonly AgentManager agentManager; - private readonly InstanceManager instanceManager; - private readonly InstanceLogManager instanceLogManager; - - public MessageListener(RpcConnectionToServer<IMessageToControllerListener> connection, AgentManager agentManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager) { - this.connection = connection; - this.agentManager = agentManager; - this.instanceManager = instanceManager; - this.instanceLogManager = instanceLogManager; - } - - public Task<NoReply> HandleRegisterWebResult(RegisterWebResultMessage message) { - RegisterSuccessWaiter.TrySetResult(message.Success); - return Task.FromResult(NoReply.Instance); - } - - public Task<NoReply> HandleRefreshAgents(RefreshAgentsMessage message) { - agentManager.RefreshAgents(message.Agents); - return Task.FromResult(NoReply.Instance); - } - - public Task<NoReply> HandleRefreshInstances(RefreshInstancesMessage message) { - instanceManager.RefreshInstances(message.Instances); - return Task.FromResult(NoReply.Instance); - } - - public Task<NoReply> HandleInstanceOutput(InstanceOutputMessage message) { - instanceLogManager.AddLines(message.InstanceGuid, message.Lines); - return Task.FromResult(NoReply.Instance); - } - - public Task<NoReply> HandleReply(ReplyMessage message) { - connection.Receive(message); - return Task.FromResult(NoReply.Instance); - } -} diff --git a/Web/Phantom.Web.Services/Rpc/RpcClientRuntime.cs b/Web/Phantom.Web.Services/Rpc/RpcClientRuntime.cs index 87de6a6..2085174 100644 --- a/Web/Phantom.Web.Services/Rpc/RpcClientRuntime.cs +++ b/Web/Phantom.Web.Services/Rpc/RpcClientRuntime.cs @@ -3,18 +3,19 @@ using NetMQ.Sockets; using Phantom.Common.Messages.Web; using Phantom.Common.Messages.Web.BiDirectional; using Phantom.Common.Messages.Web.ToController; +using Phantom.Utils.Actor; using Phantom.Utils.Rpc.Runtime; using Phantom.Utils.Rpc.Sockets; using ILogger = Serilog.ILogger; namespace Phantom.Web.Services.Rpc; -public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToWebListener, IMessageToControllerListener, ReplyMessage> { - public static Task Launch(RpcClientSocket<IMessageToWebListener, IMessageToControllerListener, ReplyMessage> socket, IMessageToWebListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) { - return new RpcClientRuntime(socket, messageListener, disconnectSemaphore, receiveCancellationToken).Launch(); +public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToWeb, IMessageToController, ReplyMessage> { + public static Task Launch(RpcClientSocket<IMessageToWeb, IMessageToController, ReplyMessage> socket, ActorRef<IMessageToWeb> handlerActorRef, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) { + return new RpcClientRuntime(socket, handlerActorRef, disconnectSemaphore, receiveCancellationToken).Launch(); } - private RpcClientRuntime(RpcClientSocket<IMessageToWebListener, IMessageToControllerListener, ReplyMessage> socket, IMessageToWebListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket, messageListener, disconnectSemaphore, receiveCancellationToken) {} + private RpcClientRuntime(RpcClientSocket<IMessageToWeb, IMessageToController, ReplyMessage> socket, ActorRef<IMessageToWeb> handlerActor, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket, handlerActor, disconnectSemaphore, receiveCancellationToken) {} protected override async Task SendDisconnectMessage(ClientSocket socket, ILogger logger) { var unregisterMessageBytes = WebMessageRegistries.ToController.Write(new UnregisterWebMessage()).ToArray(); diff --git a/Web/Phantom.Web/Program.cs b/Web/Phantom.Web/Program.cs index 1ae9ab5..1ac8e44 100644 --- a/Web/Phantom.Web/Program.cs +++ b/Web/Phantom.Web/Program.cs @@ -2,6 +2,7 @@ using NetMQ; using Phantom.Common.Messages.Web; using Phantom.Common.Messages.Web.ToController; +using Phantom.Utils.Actor; using Phantom.Utils.Cryptography; using Phantom.Utils.IO; using Phantom.Utils.Logging; @@ -52,23 +53,25 @@ try { var administratorToken = TokenGenerator.Create(60); var applicationProperties = new ApplicationProperties(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken)); - var rpcConfiguration = new RpcConfiguration("Rpc", controllerHost, controllerPort, controllerCertificate); + var rpcConfiguration = new RpcConfiguration("Web", controllerHost, controllerPort, controllerCertificate); var rpcSocket = RpcClientSocket.Connect(rpcConfiguration, WebMessageRegistries.Definitions, new RegisterWebMessage(webToken)); var webConfiguration = new WebLauncher.Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken); var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Web")); var webApplication = WebLauncher.CreateApplication(webConfiguration, taskManager, applicationProperties, rpcSocket.Connection); - MessageListener messageListener; + using var actorSystem = ActorSystemFactory.Create("Web"); + + ControllerMessageHandlerFactory messageHandlerFactory; await using (var scope = webApplication.Services.CreateAsyncScope()) { - messageListener = scope.ServiceProvider.GetRequiredService<MessageListener>(); + messageHandlerFactory = scope.ServiceProvider.GetRequiredService<ControllerMessageHandlerFactory>(); } var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1); - var rpcTask = RpcClientRuntime.Launch(rpcSocket, messageListener, rpcDisconnectSemaphore, shutdownCancellationToken); + var rpcTask = RpcClientRuntime.Launch(rpcSocket, messageHandlerFactory.Create(actorSystem), rpcDisconnectSemaphore, shutdownCancellationToken); try { PhantomLogger.Root.Information("Registering with the controller..."); - if (await messageListener.RegisterSuccessWaiter.Task) { + if (await messageHandlerFactory.RegisterSuccessWaiter) { PhantomLogger.Root.Information("Successfully registered with the controller."); } else { diff --git a/Web/Phantom.Web/WebLauncher.cs b/Web/Phantom.Web/WebLauncher.cs index b4a534c..7266a5f 100644 --- a/Web/Phantom.Web/WebLauncher.cs +++ b/Web/Phantom.Web/WebLauncher.cs @@ -13,7 +13,7 @@ static class WebLauncher { public string HttpUrl => "http://" + Host + ":" + Port; } - internal static WebApplication CreateApplication(Configuration config, TaskManager taskManager, ApplicationProperties applicationProperties, RpcConnectionToServer<IMessageToControllerListener> controllerConnection) { + internal static WebApplication CreateApplication(Configuration config, TaskManager taskManager, ApplicationProperties applicationProperties, RpcConnectionToServer<IMessageToController> controllerConnection) { var assembly = typeof(WebLauncher).Assembly; var builder = WebApplication.CreateBuilder(new WebApplicationOptions { ApplicationName = assembly.GetName().Name,