// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.TaskServices { /// /// A singleton class to manager the current long running operations /// public class SqlTaskManager : IDisposable { private static SqlTaskManager instance = new SqlTaskManager(); private static readonly object lockObject = new object(); private bool isDisposed; private readonly ConcurrentDictionary tasks = new ConcurrentDictionary(); public event EventHandler> TaskAdded; public event EventHandler> TaskRemoved; /// /// Constructor to create an instance for test purposes use only /// internal SqlTaskManager() { } /// /// Singleton instance /// public static SqlTaskManager Instance { get { return instance; } } /// /// Task connections /// internal ReadOnlyCollection Tasks { get { lock (lockObject) { return new ReadOnlyCollection(tasks.Values.ToList()); } } } /// /// Clear completed tasks /// internal void ClearCompletedTasks() { ValidateNotDisposed(); lock (lockObject) { var tasksToRemove = (from task in tasks.Values where task.IsCompleted select task).ToList(); foreach (var task in tasksToRemove) { RemoveCompletedTask(task); } } } /// /// Creates a new task /// /// Task Metadata /// The function to run the operation /// The function to cancel the operation /// The new sql task public SqlTask CreateTask(TaskMetadata taskMetadata, Func> taskToRun, Func> taskToCancel) { return CreateTask(taskMetadata, taskToRun, taskToCancel); } /// /// Creates a new task /// /// Task Metadata /// The new sql task public SqlTask CreateTask(TaskMetadata taskMetadata) where T : SqlTask, new() { Validate.IsNotNull(nameof(taskMetadata), taskMetadata); return CreateTask(taskMetadata, TaskOperationHelper.ExecuteTaskAsync, TaskOperationHelper.CancelTaskAsync); } /// /// Creates a new task /// /// Task Metadata /// The function to run the operation /// The function to cancel the operation /// The new sql task public SqlTask CreateTask(TaskMetadata taskMetadata, Func> taskToRun, Func> taskToCancel) where T : SqlTask, new() { ValidateNotDisposed(); var newTask = new T(); newTask.Init(taskMetadata, taskToRun, taskToCancel); if (taskMetadata != null && taskMetadata.TaskOperation != null) { taskMetadata.TaskOperation.SqlTask = newTask; } lock (lockObject) { tasks.AddOrUpdate(newTask.TaskId, newTask, (key, oldValue) => newTask); } OnTaskAdded(new TaskEventArgs(newTask)); return newTask; } /// /// Creates a new task /// /// Task Metadata /// The function to run the operation /// The new sql task public SqlTask CreateTask(TaskMetadata taskMetadata, Func> taskToRun) { return CreateTask(taskMetadata, taskToRun); } /// /// Creates a new task /// /// Task Metadata /// The function to run the operation /// public SqlTask CreateTask(TaskMetadata taskMetadata, Func> taskToRun) where T : SqlTask, new() { return CreateTask(taskMetadata, taskToRun, null); } /// /// Creates a new task and starts the task /// /// Task Metadata /// The function to run the operation /// The function to cancel the operation /// public SqlTask CreateAndRun(TaskMetadata taskMetadata, Func> taskToRun, Func> taskToCancel) { return CreateAndRun(taskMetadata, taskToRun, taskToCancel); } public SqlTask CreateAndRun(TaskMetadata taskMetadata) where T : SqlTask, new() { var sqlTask = CreateTask(taskMetadata); sqlTask.Run(); return sqlTask; } /// /// Creates a new task and starts the task /// /// Task Metadata /// The function to run the operation /// The function to cancel the operation /// public SqlTask CreateAndRun(TaskMetadata taskMetadata, Func> taskToRun, Func> taskToCancel) where T : SqlTask, new() { var sqlTask = CreateTask(taskMetadata, taskToRun, taskToCancel); sqlTask.Run(); return sqlTask; } public void Dispose() { Dispose(true); } void Dispose(bool disposing) { if (isDisposed) { return; } if (disposing) { lock (lockObject) { foreach (var task in tasks.Values) { task.Dispose(); } tasks.Clear(); } } isDisposed = true; } /// /// Returns true if there's any completed task /// /// internal bool HasCompletedTasks() { lock (lockObject) { return tasks.Values.Any(task => task.IsCompleted); } } private void OnTaskAdded(TaskEventArgs e) { var handler = TaskAdded; if (handler != null) { handler(this, e); } } private void OnTaskRemoved(TaskEventArgs e) { var handler = TaskRemoved; if (handler != null) { handler(this, e); } } /// /// Cancel a task /// /// public void CancelTask(Guid taskId) { SqlTask taskToCancel; lock (lockObject) { tasks.TryGetValue(taskId, out taskToCancel); } if (taskToCancel != null) { taskToCancel.Cancel(); } } /// /// Internal for test purposes only. /// Removes all tasks regardless of status from the model. /// This is used as a test aid since Monitor is a singleton class. /// internal void Reset() { foreach (var task in tasks.Values) { RemoveTask(task); } } internal void RemoveCompletedTask(SqlTask task) { if (task.IsCompleted) { RemoveTask(task); } } private void RemoveTask(SqlTask task) { SqlTask removedTask; tasks.TryRemove(task.TaskId, out removedTask); } void ValidateNotDisposed() { if (isDisposed) { throw new ObjectDisposedException(typeof(SqlTaskManager).FullName); } } } }