From 1f7d914625f23cef4c2e5be4ca2d3916dd060d7c Mon Sep 17 00:00:00 2001 From: cryptooda Date: Thu, 13 Nov 2025 18:05:55 +0700 Subject: [PATCH] Add cancellation token support to backtest execution and update progress handling --- .../Backtests/BacktestExecutor.cs | 7 ++++++- .../Backtests/BacktestExecutorAdapter.cs | 6 ++++-- src/Managing.Application/GeneticService.cs | 4 +++- .../Workers/BacktestComputeWorker.cs | 14 +++----------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Managing.Application/Backtests/BacktestExecutor.cs b/src/Managing.Application/Backtests/BacktestExecutor.cs index f1cd0b03..bbc73885 100644 --- a/src/Managing.Application/Backtests/BacktestExecutor.cs +++ b/src/Managing.Application/Backtests/BacktestExecutor.cs @@ -125,6 +125,7 @@ public class BacktestExecutor /// Optional bundle request ID to update with backtest result /// Additional metadata /// Optional callback for progress updates (0-100) + /// Cancellation token to stop execution /// The lightweight backtest result public async Task ExecuteAsync( TradingBotConfig config, @@ -135,7 +136,8 @@ public class BacktestExecutor string requestId = null, Guid? bundleRequestId = null, object metadata = null, - Func progressCallback = null) + Func progressCallback = null, + CancellationToken cancellationToken = default) { if (candles == null || candles.Count == 0) { @@ -247,6 +249,9 @@ public class BacktestExecutor // Process all candles with optimized rolling window approach foreach (var candle in orderedCandles) { + // Check for cancellation (timeout or shutdown) + cancellationToken.ThrowIfCancellationRequested(); + // Add to HashSet for reuse fixedCandles.Add(candle); tradingBot.LastCandle = candle; diff --git a/src/Managing.Application/Backtests/BacktestExecutorAdapter.cs b/src/Managing.Application/Backtests/BacktestExecutorAdapter.cs index 880a7c6b..96820b75 100644 --- a/src/Managing.Application/Backtests/BacktestExecutorAdapter.cs +++ b/src/Managing.Application/Backtests/BacktestExecutorAdapter.cs @@ -65,7 +65,8 @@ public class BacktestExecutorAdapter : IBacktester requestId, bundleRequestId: null, metadata, - progressCallback: null); + progressCallback: null, + cancellationToken: default); return result; } @@ -88,7 +89,8 @@ public class BacktestExecutorAdapter : IBacktester requestId, bundleRequestId: null, metadata, - progressCallback: null); + progressCallback: null, + cancellationToken: default); return result; } diff --git a/src/Managing.Application/GeneticService.cs b/src/Managing.Application/GeneticService.cs index 47fa02d9..461db445 100644 --- a/src/Managing.Application/GeneticService.cs +++ b/src/Managing.Application/GeneticService.cs @@ -1057,7 +1057,9 @@ public class TradingBotFitness : IFitness requestId: _request.RequestId, bundleRequestId: null, // Genetic algorithm doesn't use bundle requests metadata: new GeneticBacktestMetadata(_geneticAlgorithm?.GenerationsNumber ?? 0, - _request.RequestId) + _request.RequestId), + progressCallback: null, + cancellationToken: default ) ).GetAwaiter().GetResult(); } diff --git a/src/Managing.Application/Workers/BacktestComputeWorker.cs b/src/Managing.Application/Workers/BacktestComputeWorker.cs index e3f899d0..37cd4af5 100644 --- a/src/Managing.Application/Workers/BacktestComputeWorker.cs +++ b/src/Managing.Application/Workers/BacktestComputeWorker.cs @@ -267,18 +267,9 @@ public class BacktestComputeWorker : BackgroundService _jobProgressTrackers.TryAdd(job.Id, progressTracker); // Progress callback that only updates in-memory progress (non-blocking) + // Timeout is now enforced via CancellationToken, not by throwing in callback Func progressCallback = (percentage) => { - // Check if job has been running too long - var elapsed = DateTime.UtcNow - jobStartTime; - if (elapsed.TotalMinutes > _options.JobTimeoutMinutes) - { - _logger.LogWarning( - "Job {JobId} has been running for {ElapsedMinutes} minutes, exceeding timeout of {TimeoutMinutes} minutes", - job.Id, elapsed.TotalMinutes, _options.JobTimeoutMinutes); - throw new TimeoutException($"Job exceeded timeout of {_options.JobTimeoutMinutes} minutes"); - } - // Update progress in memory only - persistence happens in background progressTracker.UpdateProgress(percentage); @@ -301,7 +292,8 @@ public class BacktestComputeWorker : BackgroundService requestId: job.RequestId, bundleRequestId: job.BundleRequestId, metadata: null, - progressCallback: progressCallback); + progressCallback: progressCallback, + cancellationToken: linkedCts.Token); } catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested && !cancellationToken.IsCancellationRequested) {