fix(store-and-forward): wire up parked-message handler and start S&F service on sites
The Parked Messages page returned "Parked message handler not available"
because no actor was ever registered for ParkedMessages, and Retry/Discard
requests had no Receive at all (would have hit deadletters). On top of
that, StoreAndForwardService.StartAsync() was never called anywhere, so
the sf_messages SQLite table was never created and the retry timer never
ran — silently breaking all of S&F.
- New ParkedMessageHandlerActor bridges StoreAndForwardService.{Get,Retry,Discard}
using the Sender→Task→PipeTo pattern already used in DeploymentManagerActor.
- SiteCommunicationActor now routes ParkedMessageRetryRequest and
ParkedMessageDiscardRequest the same way as the existing Query handler.
- AkkaHostedService.RegisterSiteActors() resolves StoreAndForwardService,
calls StartAsync() to create the schema and start the timer, then
creates and registers the handler actor.
This commit is contained in:
119
src/ScadaLink.StoreAndForward/ParkedMessageHandlerActor.cs
Normal file
119
src/ScadaLink.StoreAndForward/ParkedMessageHandlerActor.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Text.Json;
|
||||
using Akka.Actor;
|
||||
using Akka.Event;
|
||||
using ScadaLink.Commons.Messages.RemoteQuery;
|
||||
|
||||
namespace ScadaLink.StoreAndForward;
|
||||
|
||||
/// <summary>
|
||||
/// Akka actor bridge for <see cref="StoreAndForwardService"/> parked-message operations.
|
||||
/// Receives Query/Retry/Discard requests from the SiteCommunicationActor and replies
|
||||
/// with the matching response records.
|
||||
/// </summary>
|
||||
public class ParkedMessageHandlerActor : ReceiveActor
|
||||
{
|
||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||
private readonly StoreAndForwardService _service;
|
||||
private readonly string _siteId;
|
||||
|
||||
public ParkedMessageHandlerActor(StoreAndForwardService service, string siteId)
|
||||
{
|
||||
_service = service;
|
||||
_siteId = siteId;
|
||||
|
||||
Receive<ParkedMessageQueryRequest>(HandleQuery);
|
||||
Receive<ParkedMessageRetryRequest>(HandleRetry);
|
||||
Receive<ParkedMessageDiscardRequest>(HandleDiscard);
|
||||
}
|
||||
|
||||
private void HandleQuery(ParkedMessageQueryRequest msg)
|
||||
{
|
||||
var sender = Sender;
|
||||
var siteId = _siteId;
|
||||
|
||||
_service.GetParkedMessagesAsync(category: null, msg.PageNumber, msg.PageSize)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
{
|
||||
var entries = t.Result.Messages
|
||||
.Select(m => new ParkedMessageEntry(
|
||||
MessageId: m.Id,
|
||||
TargetSystem: m.Target,
|
||||
MethodName: ExtractMethodName(m.PayloadJson, m.Category),
|
||||
ErrorMessage: m.LastError ?? string.Empty,
|
||||
AttemptCount: m.RetryCount,
|
||||
OriginalTimestamp: m.CreatedAt,
|
||||
LastAttemptTimestamp: m.LastAttemptAt ?? m.CreatedAt))
|
||||
.ToList();
|
||||
|
||||
return new ParkedMessageQueryResponse(
|
||||
msg.CorrelationId, siteId, entries, t.Result.TotalCount,
|
||||
msg.PageNumber, msg.PageSize, true, null, DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
return new ParkedMessageQueryResponse(
|
||||
msg.CorrelationId, siteId, [], 0, msg.PageNumber, msg.PageSize,
|
||||
false, t.Exception?.GetBaseException().Message, DateTimeOffset.UtcNow);
|
||||
}).PipeTo(sender);
|
||||
}
|
||||
|
||||
private void HandleRetry(ParkedMessageRetryRequest msg)
|
||||
{
|
||||
var sender = Sender;
|
||||
|
||||
_service.RetryParkedMessageAsync(msg.MessageId)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
{
|
||||
return new ParkedMessageRetryResponse(
|
||||
msg.CorrelationId, t.Result,
|
||||
t.Result ? null : "Message not found or no longer parked.");
|
||||
}
|
||||
|
||||
return new ParkedMessageRetryResponse(
|
||||
msg.CorrelationId, false, t.Exception?.GetBaseException().Message);
|
||||
}).PipeTo(sender);
|
||||
}
|
||||
|
||||
private void HandleDiscard(ParkedMessageDiscardRequest msg)
|
||||
{
|
||||
var sender = Sender;
|
||||
|
||||
_service.DiscardParkedMessageAsync(msg.MessageId)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
{
|
||||
return new ParkedMessageDiscardResponse(
|
||||
msg.CorrelationId, t.Result,
|
||||
t.Result ? null : "Message not found or no longer parked.");
|
||||
}
|
||||
|
||||
return new ParkedMessageDiscardResponse(
|
||||
msg.CorrelationId, false, t.Exception?.GetBaseException().Message);
|
||||
}).PipeTo(sender);
|
||||
}
|
||||
|
||||
private static string ExtractMethodName(string payloadJson, Commons.Types.Enums.StoreAndForwardCategory category)
|
||||
{
|
||||
if (string.IsNullOrEmpty(payloadJson))
|
||||
return category.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(payloadJson);
|
||||
var root = doc.RootElement;
|
||||
if (root.TryGetProperty("MethodName", out var method) && method.ValueKind == JsonValueKind.String)
|
||||
return method.GetString() ?? category.ToString();
|
||||
if (root.TryGetProperty("Subject", out var subject) && subject.ValueKind == JsonValueKind.String)
|
||||
return subject.GetString() ?? category.ToString();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
return category.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user