Fix reminding for livetradingbot
This commit is contained in:
@@ -287,6 +287,7 @@ builder.WebHost.SetupDiscordBot();
|
|||||||
|
|
||||||
// App
|
// App
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseSerilogRequestLogging();
|
app.UseSerilogRequestLogging();
|
||||||
app.UseOpenApi();
|
app.UseOpenApi();
|
||||||
app.UseSwaggerUI(c =>
|
app.UseSwaggerUI(c =>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Orleans;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grain responsible for initializing bot reminders on application startup.
|
||||||
|
/// This grain ensures that only one instance runs the initialization process
|
||||||
|
/// even in multi-silo environments.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBotReminderInitializerGrain : IGrainWithIntegerKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes bot reminders by fetching all running bots and pinging them
|
||||||
|
/// to ensure their reminders are properly registered.
|
||||||
|
/// </summary>
|
||||||
|
Task InitializeBotRemindersAsync();
|
||||||
|
}
|
||||||
@@ -39,4 +39,11 @@ public interface ILiveTradingBotGrain : IGrainWithGuidKey
|
|||||||
/// Deletes the bot and cleans up all associated resources
|
/// Deletes the bot and cleans up all associated resources
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task DeleteAsync();
|
Task DeleteAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pings the bot to reactivate it and ensure reminders are registered
|
||||||
|
/// Used during startup to reactivate bots that may have lost their reminders
|
||||||
|
/// Returns true if the ping was successful, false otherwise
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> PingAsync();
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace Managing.Application.Bots;
|
|
||||||
|
|
||||||
public class BotReminderInitializer
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using Managing.Application.Abstractions;
|
||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
|
using Managing.Core;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
namespace Managing.Application.Bots.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grain that initializes bot reminders on application startup and periodically checks for running bots.
|
||||||
|
/// Fetches all running bots and pings them to ensure their reminders are properly registered.
|
||||||
|
/// This grain ensures that only one instance runs the initialization process
|
||||||
|
/// even in multi-silo environments.
|
||||||
|
/// </summary>
|
||||||
|
public class BotReminderInitializerGrain : Grain, IBotReminderInitializerGrain, IRemindable
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private readonly ILogger<BotReminderInitializerGrain> _logger;
|
||||||
|
|
||||||
|
private const string CheckRunningBotsReminderName = "CheckRunningBotsReminder";
|
||||||
|
|
||||||
|
public BotReminderInitializerGrain(
|
||||||
|
IServiceScopeFactory scopeFactory,
|
||||||
|
ILogger<BotReminderInitializerGrain> logger)
|
||||||
|
{
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes bot reminders by fetching all running bots and pinging them
|
||||||
|
/// to ensure their reminders are properly registered.
|
||||||
|
/// </summary>
|
||||||
|
public async Task InitializeBotRemindersAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("BotReminderInitializerGrain starting - fetching running bots to reactivate reminders");
|
||||||
|
|
||||||
|
// Get all running bots from the database
|
||||||
|
var runningBots = await GetRunningBotsAsync();
|
||||||
|
|
||||||
|
if (!runningBots.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("No running bots found to reactivate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Found {Count} running bots to reactivate", runningBots.Count());
|
||||||
|
|
||||||
|
// Ping each running bot to reactivate it and ensure reminders are registered
|
||||||
|
var tasks = runningBots.Select(async bot =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Pinging bot {BotId} ({BotName}) to reactivate", bot.Identifier, bot.Name);
|
||||||
|
|
||||||
|
var grain = GrainFactory.GetGrain<ILiveTradingBotGrain>(bot.Identifier);
|
||||||
|
var success = await grain.PingAsync();
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Successfully pinged bot {BotId} ({BotName})", bot.Identifier, bot.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Ping failed for bot {BotId} ({BotName})", bot.Identifier, bot.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to ping bot {BotId} ({BotName})", bot.Identifier, bot.Name);
|
||||||
|
SentrySdk.CaptureException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for all pings to complete
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
|
// Register a reminder to check running bots every hour
|
||||||
|
// Start immediately and repeat every hour
|
||||||
|
await this.RegisterOrUpdateReminder(
|
||||||
|
CheckRunningBotsReminderName,
|
||||||
|
TimeSpan.FromHours(1), // Start in 1 hour
|
||||||
|
TimeSpan.FromHours(1)); // Repeat every hour
|
||||||
|
|
||||||
|
_logger.LogInformation("BotReminderInitializerGrain completed - processed {Count} running bots", runningBots.Count());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during BotReminderInitializerGrain initialization");
|
||||||
|
SentrySdk.CaptureException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles reminder callbacks for periodic bot checking
|
||||||
|
/// </summary>
|
||||||
|
public Task ReceiveReminder(string reminderName, TickStatus status)
|
||||||
|
{
|
||||||
|
if (reminderName == CheckRunningBotsReminderName)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Received hourly reminder to check running bots");
|
||||||
|
return InitializeBotRemindersAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches all bots with Running status from the database
|
||||||
|
/// </summary>
|
||||||
|
private async Task<IEnumerable<Bot>> GetRunningBotsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await ServiceScopeHelpers.WithScopedService<IBotService, IEnumerable<Bot>>(
|
||||||
|
_scopeFactory,
|
||||||
|
async botService => await botService.GetBotsByStatusAsync(BotStatus.Running));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to fetch running bots from database");
|
||||||
|
return Enumerable.Empty<Bot>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,8 +43,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("LiveTradingBotGrain {GrainId} activated", this.GetPrimaryKey());
|
_logger.LogInformation("LiveTradingBotGrain {GrainId} activated", this.GetPrimaryKey());
|
||||||
await base.OnActivateAsync(cancellationToken);
|
|
||||||
await ResumeBotIfRequiredAsync();
|
await ResumeBotIfRequiredAsync();
|
||||||
|
await base.OnActivateAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken)
|
public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken)
|
||||||
@@ -795,4 +795,27 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
_logger.LogError(ex, "Failed to send swap notification for bot {BotId}", _tradingBot?.Identifier);
|
_logger.LogError(ex, "Failed to send swap notification for bot {BotId}", _tradingBot?.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pings the bot to reactivate it and ensure reminders are registered
|
||||||
|
/// Used during startup to reactivate bots that may have lost their reminders
|
||||||
|
/// The grain activation will automatically handle reminder registration
|
||||||
|
/// </summary>
|
||||||
|
public Task<bool> PingAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Ping received for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
|
||||||
|
// The grain activation (OnActivateAsync) will automatically call ResumeBotIfRequiredAsync()
|
||||||
|
// which handles checking the registry status and re-registering reminders if needed
|
||||||
|
// So we just need to return true to indicate the ping was received
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during ping for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,17 +4,35 @@ using static Managing.Common.Enums;
|
|||||||
|
|
||||||
namespace Managing.Application.Grains;
|
namespace Managing.Application.Grains;
|
||||||
|
|
||||||
public class PriceFetcherInitializer : IHostedService
|
public class GrainInitializer : IHostedService
|
||||||
{
|
{
|
||||||
private readonly IGrainFactory _grainFactory;
|
private readonly IGrainFactory _grainFactory;
|
||||||
|
|
||||||
public PriceFetcherInitializer(IClusterClient grainFactory)
|
public GrainInitializer(IClusterClient grainFactory)
|
||||||
{
|
{
|
||||||
_grainFactory = grainFactory;
|
_grainFactory = grainFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
await InitializePriceFetcherAsync();
|
||||||
|
await InitializeBotRemindersAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeBotRemindersAsync()
|
||||||
|
{
|
||||||
|
if (Environment.GetEnvironmentVariable("TASK_SLOT") != "1")
|
||||||
|
return;
|
||||||
|
|
||||||
|
var grain = _grainFactory.GetGrain<IBotReminderInitializerGrain>(0);
|
||||||
|
await grain.InitializeBotRemindersAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializePriceFetcherAsync()
|
||||||
|
{
|
||||||
|
if (Environment.GetEnvironmentVariable("TASK_SLOT") != "1")
|
||||||
|
return;
|
||||||
|
|
||||||
// Initialize grains for different timeframes
|
// Initialize grains for different timeframes
|
||||||
var timeframes = new[]
|
var timeframes = new[]
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ public static class ApiBootstrap
|
|||||||
|
|
||||||
public static IServiceCollection AddHostedServices(this IServiceCollection services)
|
public static IServiceCollection AddHostedServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddHostedService<PriceFetcherInitializer>();
|
services.AddHostedService<GrainInitializer>();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +342,6 @@ public static class ApiBootstrap
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static IServiceCollection AddApplication(this IServiceCollection services)
|
private static IServiceCollection AddApplication(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<ITradingService, TradingService>();
|
services.AddScoped<ITradingService, TradingService>();
|
||||||
|
|||||||
Reference in New Issue
Block a user