public virtual async Task <TaskDescriptor> GetOrAddTaskAsync <T>(Action <TaskDescriptor> createAction) where T : ITask { Guard.NotNull(createAction, nameof(createAction)); var type = typeof(T); if (type.IsAbstract || type.IsInterface || type.IsNotPublic) { throw new InvalidOperationException("Only concrete public task types can be registered."); } var task = await this.GetTaskByTypeAsync <T>(); if (task == null) { task = new TaskDescriptor { Type = type.GetAttribute <TaskNameAttribute>(false)?.Name ?? type.Name }; createAction(task); _db.TaskDescriptors.Add(task); await _db.SaveChangesAsync(); } return(task); }
public async Task <ITaskDescriptor> GetOrAddTaskAsync <T>(Action <ITaskDescriptor> createAction) where T : ITask { Guard.NotNull(createAction, nameof(createAction)); var type = typeof(T); if (type.IsAbstract || type.IsInterface || type.IsNotPublic) { throw new InvalidOperationException("Only concrete public task types can be registered."); } var task = await this.GetTaskByTypeAsync <T>(); if (task == null) { task = new TaskDescriptor { Type = type.AssemblyQualifiedNameWithoutVersion() }; createAction(task); _db.TaskDescriptors.Add((TaskDescriptor)task); await _db.SaveChangesAsync(); } return(task); }
public virtual Task DeleteTaskAsync(TaskDescriptor task) { Guard.NotNull(task, nameof(task)); _db.TaskDescriptors.Remove(task); return(_db.SaveChangesAsync()); }
public virtual Task InsertTaskAsync(TaskDescriptor task) { Guard.NotNull(task, nameof(task)); _db.TaskDescriptors.Add(task); return(_db.SaveChangesAsync()); }
/// <summary> /// Loads - if not already loaded - the last <see cref="TaskExecutionInfo"/> object instance for a task from the store /// and assigns data to <see cref="TaskDescriptor.LastExecution"/>. /// </summary> /// <param name="task">The task to load data for.</param> /// <param name="force"><c>true</c> always enforces a reload, even if data is loaded already.</param> public static async Task LoadLastExecutionInfoAsync(this ITaskStore store, TaskDescriptor task, bool force = false) { Guard.NotNull(task, nameof(task)); if (task.LastExecution == null || force) { task.LastExecution = await store.GetLastExecutionInfoByTaskAsync(task); } }
private static void FixTypeName(TaskDescriptor task) { // TODO: (core) Map old task types to new types. // In versions prior V3 a double space could exist in ScheduleTask type name. if (task.Type.IndexOf(", ") > 0) { task.Type = task.Type.Replace(", ", ", "); } }
public virtual TaskExecutionInfo CreateExecutionInfo(TaskDescriptor task) { Guard.NotNull(task, nameof(task)); return(new TaskExecutionInfo { TaskDescriptorId = task.Id, IsRunning = true, MachineName = _appContext.MachineName.EmptyNull(), StartedOnUtc = DateTime.UtcNow }); }
public Type GetTaskClrType(TaskDescriptor task) { try { return(Type.GetType(task.Type)); } catch { // TODO: (core) Map old task types to new types. return(null); } }
public virtual Task UpdateTaskAsync(TaskDescriptor task) { Guard.NotNull(task, nameof(task)); try { _db.TryUpdate(task); return(_db.SaveChangesAsync()); } catch (Exception ex) { Logger.Error(ex); throw; } }
public virtual DateTime?GetNextSchedule(TaskDescriptor task) { if (task.Enabled) { try { var localTimeZone = _dtHelper.DefaultStoreTimeZone; var baseTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, localTimeZone); var next = CronExpression.GetNextSchedule(task.CronExpression, baseTime); var utcTime = _dtHelper.ConvertToUtcTime(next, localTimeZone); return(utcTime); } catch (Exception ex) { Logger.Error(ex, "Could not calculate next schedule time for task '{0}'", task.Name); } } return(null); }
public virtual async Task ExecuteAsync( TaskDescriptor task, HttpContext httpContext, bool throwOnError = false, CancellationToken cancelToken = default) { Guard.NotNull(task, nameof(task)); if (_asyncRunner.AppShutdownCancellationToken.IsCancellationRequested) { return; } await _taskStore.LoadLastExecutionInfoAsync(task); if (task.LastExecution?.IsRunning == true) { return; } ITask job = null; Type taskType = null; Exception exception = null; bool faulted = false, canceled = false; string lastError = null, stateName = null; var executionInfo = _taskStore.CreateExecutionInfo(task); try { taskType = _taskStore.GetTaskClrType(task); if (taskType == null) { Logger.Debug($"Invalid scheduled task type: {task.Type.NaIfEmpty()}"); return; } if (!_appContext.ModuleCatalog.IsActiveModuleAssembly(taskType.Assembly)) { return; } await _taskStore.InsertExecutionInfoAsync(executionInfo); } catch { // Get out on any initialization error. return; } try { // Task history entry has been successfully added, now we execute the task. // Create task instance. job = _taskResolver(taskType); stateName = task.Id.ToString(); // Create & set a composite CancellationTokenSource which also contains the global app shoutdown token. var cts = CancellationTokenSource.CreateLinkedTokenSource(_asyncRunner.AppShutdownCancellationToken, cancelToken); await _asyncState.CreateAsync(task, stateName, false, cts); // Run the task Logger.Debug("Executing scheduled task: {0}", task.Type); var ctx = new TaskExecutionContext(_taskStore, httpContext, _componentContext, executionInfo); //// TODO: (core) Uncomment job.Run and remove Task.Delay() //await job.Run(ctx, cts.Token); await Task.Delay(50, cts.Token); } catch (Exception ex) { exception = ex; faulted = true; canceled = ex is OperationCanceledException; lastError = ex.ToAllMessages(true); if (canceled) { Logger.Warn(ex, $"The scheduled task '{task.Name}' has been canceled."); } else { Logger.Error(ex, string.Concat($"Error while running scheduled task '{task.Name}'", ": ", ex.Message)); } } finally { var now = DateTime.UtcNow; var updateTask = false; executionInfo.IsRunning = false; executionInfo.ProgressPercent = null; executionInfo.ProgressMessage = null; executionInfo.Error = lastError; executionInfo.FinishedOnUtc = now; if (faulted) { if ((!canceled && task.StopOnError) || task == null) { task.Enabled = false; updateTask = true; } } else { executionInfo.SucceededOnUtc = now; } try { Logger.Debug("Executed scheduled task: {0}. Elapsed: {1} ms.", task.Type, (now - executionInfo.StartedOnUtc).TotalMilliseconds); // Remove from AsyncState. if (stateName.HasValue()) { // We don't just remove the cancellation token, but the whole state (along with the token) // for the case that a state was registered during task execution. await _asyncState.RemoveAsync <TaskDescriptor>(stateName); } } catch (Exception ex) { Logger.Error(ex); } if (task.Enabled) { task.NextRunUtc = _taskStore.GetNextSchedule(task); updateTask = true; } await _taskStore.UpdateExecutionInfoAsync(executionInfo); if (updateTask) { await _taskStore.UpdateTaskAsync(task); } if (!canceled) { await Throttle.CheckAsync( "Delete old schedule task history entries", TimeSpan.FromHours(4), async() => await _taskStore.TrimExecutionInfosAsync(cancelToken) > 0); } } if (throwOnError && exception != null) { throw exception; } }
public virtual async Task <TaskExecutionInfo> GetLastExecutionInfoByTaskAsync(TaskDescriptor task, bool?runningOnly = null) { Guard.NotNull(task, nameof(task)); var query = _db.IsCollectionLoaded(task, x => x.ExecutionHistory) ? task.ExecutionHistory.AsQueryable() : GetExecutionInfoQuery().Include(x => x.Task).ApplyTaskFilter(task.Id); if (runningOnly.HasValue) { query = query.Where(x => x.IsRunning == runningOnly.Value); } query = query.ApplyCurrentMachineNameFilter(); return(await ExecuteWithRetry(() => query.FirstOrDefaultAsync())); }
public virtual Task ReloadTaskAsync(TaskDescriptor task) { return(_db.ReloadEntityAsync(task)); }
public Task <TaskExecutionInfo> GetLastExecutionInfoByTaskAsync(TaskDescriptor task, bool?runningOnly = null) { throw new NotImplementedException(); }