mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-05-31 16:34:04 +02:00
Refactor RPC to use a single long running task
This commit is contained in:
parent
69f3fbcbf4
commit
bcb53528b9
Agent/Phantom.Agent.Rpc
Server/Phantom.Server.Rpc
Utils/Phantom.Utils.Rpc
@ -5,6 +5,7 @@ using Phantom.Common.Messages;
|
|||||||
using Phantom.Common.Messages.ToServer;
|
using Phantom.Common.Messages.ToServer;
|
||||||
using Phantom.Utils.Rpc;
|
using Phantom.Utils.Rpc;
|
||||||
using Phantom.Utils.Runtime;
|
using Phantom.Utils.Runtime;
|
||||||
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Phantom.Agent.Rpc;
|
namespace Phantom.Agent.Rpc;
|
||||||
@ -24,7 +25,7 @@ public sealed class RpcLauncher : RpcRuntime<ClientSocket> {
|
|||||||
private readonly RpcConfiguration config;
|
private readonly RpcConfiguration config;
|
||||||
private readonly Guid agentGuid;
|
private readonly Guid agentGuid;
|
||||||
private readonly Func<ClientSocket, IMessageToAgentListener> messageListenerFactory;
|
private readonly Func<ClientSocket, IMessageToAgentListener> messageListenerFactory;
|
||||||
|
|
||||||
private readonly SemaphoreSlim disconnectSemaphore;
|
private readonly SemaphoreSlim disconnectSemaphore;
|
||||||
private readonly CancellationToken receiveCancellationToken;
|
private readonly CancellationToken receiveCancellationToken;
|
||||||
|
|
||||||
@ -45,39 +46,46 @@ public sealed class RpcLauncher : RpcRuntime<ClientSocket> {
|
|||||||
logger.Information("ZeroMQ client ready.");
|
logger.Information("ZeroMQ client ready.");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task Run(ClientSocket socket, TaskManager taskManager) {
|
protected override void Run(ClientSocket socket, TaskManager taskManager) {
|
||||||
var logger = config.Logger;
|
var logger = config.Logger;
|
||||||
var listener = messageListenerFactory(socket);
|
var listener = messageListenerFactory(socket);
|
||||||
|
|
||||||
ServerMessaging.SetCurrentSocket(socket);
|
ServerMessaging.SetCurrentSocket(socket);
|
||||||
var keepAliveLoop = new KeepAliveLoop(socket, taskManager);
|
var keepAliveLoop = new KeepAliveLoop(socket, taskManager);
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO optimize msg
|
|
||||||
await foreach (var bytes in socket.ReceiveBytesAsyncEnumerable(receiveCancellationToken)) {
|
|
||||||
if (logger.IsEnabled(LogEventLevel.Verbose)) {
|
|
||||||
if (bytes.Length > 0 && MessageRegistries.ToAgent.TryGetType(bytes, out var type)) {
|
|
||||||
logger.Verbose("Received {MessageType} ({Bytes} B) from server.", type.Name, bytes.Length);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.Verbose("Received {Bytes} B message from server.", bytes.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes.Length > 0) {
|
try {
|
||||||
MessageRegistries.ToAgent.Handle(bytes, listener, taskManager, receiveCancellationToken);
|
while (!receiveCancellationToken.IsCancellationRequested) {
|
||||||
|
var data = socket.Receive(receiveCancellationToken);
|
||||||
|
|
||||||
|
LogMessageType(logger, data);
|
||||||
|
|
||||||
|
if (data.Length > 0) {
|
||||||
|
MessageRegistries.ToAgent.Handle(data, listener, taskManager, receiveCancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (OperationCanceledException) {
|
} catch (OperationCanceledException) {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
} finally {
|
} finally {
|
||||||
logger.Verbose("ZeroMQ client stopped receiving messages.");
|
logger.Verbose("ZeroMQ client stopped receiving messages.");
|
||||||
|
|
||||||
await disconnectSemaphore.WaitAsync(CancellationToken.None);
|
disconnectSemaphore.Wait(CancellationToken.None);
|
||||||
keepAliveLoop.Cancel();
|
keepAliveLoop.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void LogMessageType(ILogger logger, ReadOnlyMemory<byte> data) {
|
||||||
|
if (!logger.IsEnabled(LogEventLevel.Verbose)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.Length > 0 && MessageRegistries.ToAgent.TryGetType(data, out var type)) {
|
||||||
|
logger.Verbose("Received {MessageType} ({Bytes} B) from server.", type.Name, data.Length);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.Verbose("Received {Bytes} B message from server.", data.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task Disconnect(ClientSocket socket) {
|
protected override async Task Disconnect(ClientSocket socket) {
|
||||||
var unregisterTimeoutTask = Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None);
|
var unregisterTimeoutTask = Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None);
|
||||||
var finishedTask = await Task.WhenAny(socket.SendMessage(new UnregisterAgentMessage(agentGuid)), unregisterTimeoutTask);
|
var finishedTask = await Task.WhenAny(socket.SendMessage(new UnregisterAgentMessage(agentGuid)), unregisterTimeoutTask);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using NetMQ;
|
using NetMQ.Sockets;
|
||||||
using NetMQ.Sockets;
|
|
||||||
using Phantom.Common.Messages;
|
using Phantom.Common.Messages;
|
||||||
using Phantom.Common.Messages.ToServer;
|
using Phantom.Common.Messages.ToServer;
|
||||||
using Phantom.Utils.Rpc;
|
using Phantom.Utils.Rpc;
|
||||||
@ -39,7 +38,7 @@ public sealed class RpcLauncher : RpcRuntime<ServerSocket> {
|
|||||||
logger.Information("ZeroMQ server initialized, listening for agent connections on port {Port}.", config.Port);
|
logger.Information("ZeroMQ server initialized, listening for agent connections on port {Port}.", config.Port);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task Run(ServerSocket socket, TaskManager taskManager) {
|
protected override void Run(ServerSocket socket, TaskManager taskManager) {
|
||||||
var logger = config.Logger;
|
var logger = config.Logger;
|
||||||
var clients = new Dictionary<ulong, Client>();
|
var clients = new Dictionary<ulong, Client>();
|
||||||
|
|
||||||
@ -48,15 +47,16 @@ public sealed class RpcLauncher : RpcRuntime<ServerSocket> {
|
|||||||
logger.Verbose("Closed connection to {RoutingId}.", e.RoutingId);
|
logger.Verbose("Closed connection to {RoutingId}.", e.RoutingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO optimize msg
|
while (!cancellationToken.IsCancellationRequested) {
|
||||||
await foreach (var (routingId, bytes) in socket.ReceiveBytesAsyncEnumerable(cancellationToken)) {
|
var (routingId, data) = socket.Receive(cancellationToken);
|
||||||
if (bytes.Length == 0) {
|
|
||||||
LogMessageType(logger, routingId, bytes);
|
if (data.Length == 0) {
|
||||||
|
LogMessageType(logger, routingId, data);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clients.TryGetValue(routingId, out var client)) {
|
if (!clients.TryGetValue(routingId, out var client)) {
|
||||||
if (!CheckIsAgentRegistrationMessage(bytes, logger, routingId)) {
|
if (!CheckIsAgentRegistrationMessage(data, logger, routingId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +67,8 @@ public sealed class RpcLauncher : RpcRuntime<ServerSocket> {
|
|||||||
clients[routingId] = client;
|
clients[routingId] = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogMessageType(logger, routingId, bytes);
|
LogMessageType(logger, routingId, data);
|
||||||
MessageRegistries.ToServer.Handle(bytes, client.Listener, taskManager, cancellationToken);
|
MessageRegistries.ToServer.Handle(data, client.Listener, taskManager, cancellationToken);
|
||||||
|
|
||||||
if (client.Listener.IsDisposed) {
|
if (client.Listener.IsDisposed) {
|
||||||
client.Connection.Close();
|
client.Connection.Close();
|
||||||
@ -80,21 +80,21 @@ public sealed class RpcLauncher : RpcRuntime<ServerSocket> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogMessageType(ILogger logger, uint routingId, byte[] bytes) {
|
private static void LogMessageType(ILogger logger, uint routingId, ReadOnlyMemory<byte> data) {
|
||||||
if (!logger.IsEnabled(LogEventLevel.Verbose)) {
|
if (!logger.IsEnabled(LogEventLevel.Verbose)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes.Length > 0 && MessageRegistries.ToServer.TryGetType(bytes, out var type)) {
|
if (data.Length > 0 && MessageRegistries.ToServer.TryGetType(data, out var type)) {
|
||||||
logger.Verbose("Received {MessageType} ({Bytes} B) from {RoutingId}.", type.Name, bytes.Length, routingId);
|
logger.Verbose("Received {MessageType} ({Bytes} B) from {RoutingId}.", type.Name, data.Length, routingId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.Verbose("Received {Bytes} B message from {RoutingId}.", bytes.Length, routingId);
|
logger.Verbose("Received {Bytes} B message from {RoutingId}.", data.Length, routingId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CheckIsAgentRegistrationMessage(byte[] bytes, ILogger logger, uint routingId) {
|
private static bool CheckIsAgentRegistrationMessage(ReadOnlyMemory<byte> data, ILogger logger, uint routingId) {
|
||||||
if (MessageRegistries.ToServer.TryGetType(bytes, out var type) && type == typeof(RegisterAgentMessage)) {
|
if (MessageRegistries.ToServer.TryGetType(data, out var type) && type == typeof(RegisterAgentMessage)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +24,9 @@ public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase
|
|||||||
codeToDeserializerMapping.Add(code, MessageSerializer.Deserialize<TMessage, TMessageBase, TListener>());
|
codeToDeserializerMapping.Add(code, MessageSerializer.Deserialize<TMessage, TMessageBase, TListener>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetType(byte[] bytes, [NotNullWhen(true)] out Type? type) {
|
public bool TryGetType(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out Type? type) {
|
||||||
var memory = new ReadOnlyMemory<byte>(bytes);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var code = MessageSerializer.ReadCode(ref memory);
|
var code = MessageSerializer.ReadCode(ref data);
|
||||||
return codeToTypeMapping.TryGetValue(code, out type);
|
return codeToTypeMapping.TryGetValue(code, out type);
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
type = null;
|
type = null;
|
||||||
@ -59,12 +57,10 @@ public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(byte[] bytes, TListener listener, TaskManager taskManager, CancellationToken cancellationToken) {
|
public void Handle(ReadOnlyMemory<byte> data, TListener listener, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||||
var memory = new ReadOnlyMemory<byte>(bytes);
|
|
||||||
|
|
||||||
ushort code;
|
ushort code;
|
||||||
try {
|
try {
|
||||||
code = MessageSerializer.ReadCode(ref memory);
|
code = MessageSerializer.ReadCode(ref data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.Error(e, "Failed to deserialize message code.");
|
logger.Error(e, "Failed to deserialize message code.");
|
||||||
return;
|
return;
|
||||||
@ -77,7 +73,7 @@ public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase
|
|||||||
|
|
||||||
TMessageBase message;
|
TMessageBase message;
|
||||||
try {
|
try {
|
||||||
message = deserialize(memory);
|
message = deserialize(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.Error(e, "Failed to deserialize message with code {Code}.", code);
|
logger.Error(e, "Failed to deserialize message with code {Code}.", code);
|
||||||
return;
|
return;
|
||||||
|
32
Utils/Phantom.Utils.Rpc/RpcExtensions.cs
Normal file
32
Utils/Phantom.Utils.Rpc/RpcExtensions.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using NetMQ;
|
||||||
|
using NetMQ.Sockets;
|
||||||
|
|
||||||
|
namespace Phantom.Utils.Rpc;
|
||||||
|
|
||||||
|
public static class RpcExtensions {
|
||||||
|
public static ReadOnlyMemory<byte> Receive(this ClientSocket socket, CancellationToken cancellationToken) {
|
||||||
|
var msg = new Msg();
|
||||||
|
msg.InitEmpty();
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.Receive(ref msg, cancellationToken);
|
||||||
|
return msg.SliceAsMemory();
|
||||||
|
} finally {
|
||||||
|
// Only releases references, so the returned ReadOnlyMemory is safe.
|
||||||
|
msg.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (uint, ReadOnlyMemory<byte>) Receive(this ServerSocket socket, CancellationToken cancellationToken) {
|
||||||
|
var msg = new Msg();
|
||||||
|
msg.InitEmpty();
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.Receive(ref msg, cancellationToken);
|
||||||
|
return (msg.RoutingId, msg.SliceAsMemory());
|
||||||
|
} finally {
|
||||||
|
// Only releases references, so the returned ReadOnlyMemory is safe.
|
||||||
|
msg.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,9 +38,13 @@ public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket, new(
|
|||||||
|
|
||||||
protected async Task Launch() {
|
protected async Task Launch() {
|
||||||
Connect(socket);
|
Connect(socket);
|
||||||
|
|
||||||
|
void RunTask() {
|
||||||
|
Run(socket, taskManager);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Run(socket, taskManager);
|
await Task.Factory.StartNew(RunTask, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
} catch (OperationCanceledException) {
|
} catch (OperationCanceledException) {
|
||||||
// ignore
|
// ignore
|
||||||
} finally {
|
} finally {
|
||||||
@ -55,7 +59,7 @@ public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket, new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void Connect(TSocket socket);
|
protected abstract void Connect(TSocket socket);
|
||||||
protected abstract Task Run(TSocket socket, TaskManager taskManager);
|
protected abstract void Run(TSocket socket, TaskManager taskManager);
|
||||||
|
|
||||||
protected virtual Task Disconnect(TSocket socket) {
|
protected virtual Task Disconnect(TSocket socket) {
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
Loading…
Reference in New Issue
Block a user