using Nop.Core; using Nop.Core.Caching; using Nop.Core.Domain.ScheduleTasks; using Nop.Core.Infrastructure; using Nop.Services.Localization; using Nop.Services.Logging; namespace Nop.Services.ScheduleTasks; /// /// Schedule task runner /// public partial class ScheduleTaskRunner : IScheduleTaskRunner { #region Fields protected readonly ILocalizationService _localizationService; protected readonly ILocker _locker; protected readonly ILogger _logger; protected readonly IScheduleTaskService _scheduleTaskService; protected readonly IStoreContext _storeContext; #endregion #region Ctor public ScheduleTaskRunner(ILocalizationService localizationService, ILocker locker, ILogger logger, IScheduleTaskService scheduleTaskService, IStoreContext storeContext) { _localizationService = localizationService; _locker = locker; _logger = logger; _scheduleTaskService = scheduleTaskService; _storeContext = storeContext; } #endregion #region Utilities /// /// Initialize and execute task /// protected virtual async Task PerformTaskAsync(ScheduleTask scheduleTask) { var type = (Type.GetType(scheduleTask.Type) ?? //ensure that it works fine when only the type name is specified (do not require fully qualified names) AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(scheduleTask.Type)) .FirstOrDefault(t => t != null)) ?? throw new Exception($"Schedule task ({scheduleTask.Type}) cannot by instantiated"); object instance = null; try { instance = EngineContext.Current.Resolve(type); } catch { // ignored } instance ??= EngineContext.Current.ResolveUnregistered(type); if (instance is not IScheduleTask task) return; scheduleTask.LastStartUtc = DateTime.UtcNow; //update appropriate datetime properties await _scheduleTaskService.UpdateTaskAsync(scheduleTask); await task.ExecuteAsync(); scheduleTask.LastEndUtc = scheduleTask.LastSuccessUtc = DateTime.UtcNow; //update appropriate datetime properties await _scheduleTaskService.UpdateTaskAsync(scheduleTask); } /// /// Is task already running? /// /// Schedule task /// Result protected virtual bool IsTaskAlreadyRunning(ScheduleTask scheduleTask) { //task run for the first time if (!scheduleTask.LastStartUtc.HasValue && !scheduleTask.LastEndUtc.HasValue) return false; var lastStartUtc = scheduleTask.LastStartUtc ?? DateTime.UtcNow; //task already finished if (scheduleTask.LastEndUtc.HasValue && lastStartUtc < scheduleTask.LastEndUtc) return false; //task wasn't finished last time if (lastStartUtc.AddSeconds(scheduleTask.Seconds) <= DateTime.UtcNow) return false; return true; } #endregion #region Methods /// /// Executes the task /// /// Schedule task /// Force run /// A value indicating whether exception should be thrown if some error happens /// A value indicating whether we should ensure this task is run once per run period public async Task ExecuteAsync(ScheduleTask scheduleTask, bool forceRun = false, bool throwException = false, bool ensureRunOncePerPeriod = true) { var enabled = forceRun || (scheduleTask?.Enabled ?? false); if (scheduleTask == null || !enabled) return; if (ensureRunOncePerPeriod) { //task already running if (IsTaskAlreadyRunning(scheduleTask)) return; //validation (so nobody else can invoke this method when he wants) if (scheduleTask.LastStartUtc.HasValue && (DateTime.UtcNow - scheduleTask.LastStartUtc).Value.TotalSeconds < scheduleTask.Seconds) //too early return; } try { //get expiration time var expirationInSeconds = Math.Min(scheduleTask.Seconds, 300) - 1; var expiration = TimeSpan.FromSeconds(expirationInSeconds); //execute task with lock await _locker.PerformActionWithLockAsync(scheduleTask.Type, expiration, () => PerformTaskAsync(scheduleTask)); } catch (Exception exc) { var store = await _storeContext.GetCurrentStoreAsync(); var scheduleTaskUrl = $"{store.Url}{NopTaskDefaults.ScheduleTaskPath}"; scheduleTask.Enabled = scheduleTask.Enabled && !scheduleTask.StopOnError; scheduleTask.LastEndUtc = DateTime.UtcNow; await _scheduleTaskService.UpdateTaskAsync(scheduleTask); var message = string.Format(await _localizationService.GetResourceAsync("ScheduleTasks.Error"), scheduleTask.Name, exc.Message, scheduleTask.Type, store.Name, scheduleTaskUrl); //log error await _logger.ErrorAsync(message, exc); if (throwException) throw; } } #endregion }