namespace QuantEngine.Core.Scheduling; using System.Diagnostics; /// /// 스케줄러 작업 기본 클래스 /// 패턴화/표준화 원칙 적용 /// public abstract class SchedulerJobBase { public string JobName { get; } public string JobId { get; } = Guid.NewGuid().ToString("N")[..12]; protected SchedulerJobBase(string jobName) { JobName = jobName ?? throw new ArgumentNullException(nameof(jobName)); } public async Task ExecuteAsync() { var stopwatch = Stopwatch.StartNew(); var startTime = DateTime.UtcNow; try { await OnStartingAsync(); var jobResult = await RunAsync(); stopwatch.Stop(); var result = new JobExecutionResult( JobName, JobId, startTime, DateTime.UtcNow, stopwatch.ElapsedMilliseconds, true, jobResult.Message, jobResult.Data, null); await OnCompletedAsync(result); return result; } catch (OperationCanceledException ex) { stopwatch.Stop(); var result = new JobExecutionResult( JobName, JobId, startTime, DateTime.UtcNow, stopwatch.ElapsedMilliseconds, false, $"Task cancelled: {ex.Message}", null, ex); await OnFailedAsync(result); return result; } catch (Exception ex) { stopwatch.Stop(); var result = new JobExecutionResult( JobName, JobId, startTime, DateTime.UtcNow, stopwatch.ElapsedMilliseconds, false, $"Task failed: {ex.Message}", null, ex); await OnFailedAsync(result); return result; } } protected abstract Task RunAsync(); protected virtual Task OnStartingAsync() => Task.CompletedTask; protected virtual Task OnCompletedAsync(JobExecutionResult result) => Task.CompletedTask; protected virtual Task OnFailedAsync(JobExecutionResult result) => Task.CompletedTask; protected async Task RetryAsync( Func> operation, int maxRetries = 3, int initialDelayMs = 1000) { for (int attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (Exception) when (attempt < maxRetries) { await Task.Delay(initialDelayMs * (int)Math.Pow(2, attempt - 1)); } } return await operation(); } } public class JobExecutionResult { public JobExecutionResult(string jobName, string jobId, DateTime startedAt, DateTime completedAt, long elapsedMs, bool succeeded, string message, object? data, Exception? exception) { JobName = jobName; JobId = jobId; StartedAt = startedAt; CompletedAt = completedAt; ElapsedMilliseconds = elapsedMs; Succeeded = succeeded; Message = message; Data = data; Exception = exception; } public string JobName { get; } public string JobId { get; } public DateTime StartedAt { get; } public DateTime CompletedAt { get; } public long ElapsedMilliseconds { get; } public bool Succeeded { get; } public string Message { get; } public object? Data { get; } public Exception? Exception { get; } } public record JobRunResult { public bool Succeeded { get; init; } public string Message { get; init; } = string.Empty; public object? Data { get; init; } public static JobRunResult Success(string message, object? data = null) => new() { Succeeded = true, Message = message, Data = data }; public static JobRunResult Failure(string message, object? data = null) => new() { Succeeded = false, Message = message, Data = data }; }