Skip to content

Commit

Permalink
API фоновых заданий и тесты. closes #764, closes #489
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrey Ovsiankin committed Mar 17, 2021
1 parent 34cbc05 commit fefcfe3
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 39 deletions.
33 changes: 16 additions & 17 deletions src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the
----------------------------------------------------------*/

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ScriptEngine.Machine;
using ScriptEngine.Machine.Contexts;
Expand All @@ -22,7 +23,6 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm
{
Target = target;
MethodName = methodName;
Status = TaskStatusEnum.NotRunned;
if(parameters != default)
Parameters = new ArrayImpl(parameters);

Expand All @@ -34,20 +34,20 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm

public Task WorkerTask { get; set; }

[ContextProperty("Идентификатор","Identifier")]
[ContextProperty("УникальныйИдентификатор","UUID")]
public GuidWrapper Identifier { get; private set; }

[ContextProperty("ИмяМетода","MethodName")]
public string MethodName { get; private set; }

[ContextProperty("Объект","Object")]
public IRuntimeContextInstance Target { get; private set; }
[ContextProperty("Состояние", "Status")]
public TaskStatusEnum Status { get; private set; }

[ContextProperty("Состояние", "State")]
public TaskStateEnum State { get; private set; }

[ContextProperty("Параметры", "Parameters")]
public ArrayImpl Parameters { get; private set; }
public IValue Parameters { get; private set; } = ValueFactory.Create();

[ContextProperty("Результат", "Result")]
public IValue Result { get; private set; } = ValueFactory.Create();
Expand All @@ -63,24 +63,23 @@ public BackgroundTask(IRuntimeContextInstance target, string methodName, ArrayIm
[ContextMethod("ОжидатьЗавершения", "Wait")]
public bool Wait(int timeout = 0)
{
if(timeout < 0)
throw RuntimeException.InvalidArgumentValue();
if (timeout == 0)
timeout = -1;
timeout = BackgroundTasksManager.ConvertTimeout(timeout);

return WorkerTask.Wait(timeout);
}

public void ExecuteOnCurrentThread()
{
if (Status != TaskStatusEnum.NotRunned)
if (State != TaskStateEnum.NotRunned)
throw new RuntimeException(Locale.NStr("ru = 'Неверное состояние задачи';en = 'Incorrect task status'"));

var parameters = Parameters?.ToArray() ?? new IValue[0];

var parameters = Parameters is ArrayImpl array ?
array.ToArray() :
new IValue[0];

try
{
Status = TaskStatusEnum.Running;
State = TaskStateEnum.Running;
if (_method.IsFunction)
{
Target.CallAsFunction(_methIndex, parameters, out var result);
Expand All @@ -91,11 +90,11 @@ public void ExecuteOnCurrentThread()
Target.CallAsProcedure(_methIndex, parameters);
}

Status = TaskStatusEnum.Completed;
State = TaskStateEnum.Completed;
}
catch (RuntimeException exception)
{
Status = TaskStatusEnum.CompletedWithErrors;
State = TaskStateEnum.CompletedWithErrors;
ExceptionInfo = new ExceptionInfoContext(exception);
}
}
Expand Down
143 changes: 129 additions & 14 deletions src/ScriptEngine.HostedScript/Library/Tasks/BackgroundTasksManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This Source Code Form is subject to the terms of the
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using ScriptEngine.Machine;
Expand All @@ -33,8 +34,8 @@ public BackgroundTasksManager(ScriptingEngine engine)
/// <param name="methodName">Имя экспортного метода в объекте</param>
/// <param name="parameters">Массив параметров метода</param>
/// <returns>ФоновоеЗадание</returns>
[ContextMethod("Создать", "Create")]
public BackgroundTask Create(IRuntimeContextInstance target, string methodName, ArrayImpl parameters = null)
[ContextMethod("Выполнить", "Execute")]
public BackgroundTask Execute(IRuntimeContextInstance target, string methodName, ArrayImpl parameters = null)
{
var task = new BackgroundTask(target, methodName, parameters);
_tasks.Add(task);
Expand All @@ -61,30 +62,144 @@ public void Clear()
}

/// <summary>
/// Ждать завершения всех незавершенных заданий
/// Ожидает завершения всех переданных заданий
/// </summary>
[ContextMethod("ЖдатьВсе", "WaitAll")]
public void WaitAll()
/// <param name="tasks">Массив заданий</param>
/// <param name="timeout">Таймаут ожидания. 0 = ожидать бесконечно</param>
/// <returns>Истина - дождались все задания, Ложь - истек таймаут</returns>
[ContextMethod("ОжидатьВсе", "WaitAll")]
public bool WaitAll(ArrayImpl tasks, int timeout = 0)
{
var tasks = _tasks
.Select(x => x.WorkerTask)
.ToArray();

var workers = GetWorkerTasks(tasks);
timeout = ConvertTimeout(timeout);

// Фоновые задания перехватывают исключения внутри себя
// и выставляют свойство ИнформацияОбОшибке
// если WaitAll выбросит исключение, значит действительно что-то пошло не так на уровне самого Task
Task.WaitAll(tasks);
return Task.WaitAll(workers, timeout);
}

/// <summary>
/// Ожидать хотя бы одно из переданных заданий.
/// </summary>
/// <param name="tasks">Массив заданий</param>
/// <param name="timeout">Таймаут ожидания. 0 = ожидать бесконечно</param>
/// <returns>Число. Индекс в массиве заданий, указывающий на элемент-задание, которое завершилось. -1 = сработал таймаут</returns>
[ContextMethod("ОжидатьЛюбое", "WaitAny")]
public int WaitAny(ArrayImpl tasks, int timeout = 0)
{
var workers = GetWorkerTasks(tasks);
timeout = ConvertTimeout(timeout);

// Фоновые задания перехватывают исключения внутри себя
// и выставляют свойство ИнформацияОбОшибке
// если WaitAny выбросит исключение, значит действительно что-то пошло не так на уровне самого Task
return Task.WaitAny(workers, timeout);
}

/// <summary>
/// Блокирует поток до завершения всех заданий.
/// Выбрасывает исключение, если какие-то задания завершились аварийно.
/// Выброшенное исключение в свойстве Параметры содержит массив аварийных заданий.
/// </summary>
[ContextMethod("ОжидатьЗавершенияЗадач", "WaitCompletionOfTasks")]
public void WaitCompletionOfTasks()
{
lock (_tasks)
{
var workers = GetWorkerTasks();
Task.WaitAll(workers);

var failedTasks = _tasks.Where(x => x.State == TaskStateEnum.CompletedWithErrors)
.ToList();

if (failedTasks.Any())
{
throw new ParametrizedRuntimeException(
Locale.NStr("ru = 'Задания завершились с ошибками';en = 'Tasks are completed with errors'"),
new ArrayImpl(failedTasks));
}

_tasks.Clear();
}
}

[ContextMethod("ПолучитьФоновыеЗадания", "GetBackgroundJobs")]
public ArrayImpl GetBackgroundJobs(StructureImpl filter = default)
{
if(filter == default)
return new ArrayImpl(_tasks.ToArray());

var arr = new ArrayImpl();
foreach (var task in _tasks)
{
var result = true;
foreach (var filterItem in filter)
{
switch (filterItem.Key.AsString().ToLower())
{
case "состояние":
case "state":
var enumval = filterItem.Value as CLREnumValueWrapper<TaskStateEnum>;
if(enumval == default)
continue;

result = result && task.State == enumval.UnderlyingValue;
break;

case "имяметода":
case "methodname":
result = result && task.MethodName.ToLower() == filterItem.Value.AsString();
break;

case "объект":
case "object":
result = result && task.Target.Equals(filterItem.Value);
break;

case "уникальныйидентификатор":
case "uuid":
result = result && task.Identifier.Equals(filterItem.Value);
break;
}
}

if(result)
arr.Add(task);
}

return arr;
}

internal static int ConvertTimeout(int timeout)
{
if(timeout < 0)
throw RuntimeException.InvalidArgumentValue();

return timeout == 0 ? Timeout.Infinite : timeout;
}

private static Task[] GetWorkerTasks(ArrayImpl tasks)
{
return tasks
.Cast<BackgroundTask>()
.Select(x => x.WorkerTask)
.ToArray();
}

private static Task[] GetWorkerTasks(IEnumerable<BackgroundTask> tasks)
{
return tasks.Select(x => x.WorkerTask).ToArray();
}

[ContextMethod("Получить", "Get")]
public ArrayImpl Get()
private Task[] GetWorkerTasks()
{
return new ArrayImpl(_tasks.ToArray());
return GetWorkerTasks(_tasks);
}

public void Dispose()
{
WaitAll();
Task.WaitAll(GetWorkerTasks());
_tasks.Clear();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ This Source Code Form is subject to the terms of the

namespace ScriptEngine.HostedScript.Library.Tasks
{
[EnumerationType("СостояниеЗадания", "TaskStatus")]
public enum TaskStatusEnum
[EnumerationType("СостояниеФоновогоЗадания", "BackgroundJobState")]
public enum TaskStateEnum
{
[EnumItem("НеВыполнялось", "NotRunned")]
NotRunned,
[EnumItem("Выполняется", "Running")]
[EnumItem("Активно", "Active")]
Running,
[EnumItem("Завершено", "Completed")]
Completed,
[EnumItem("ЗавершеноСОшибками", "CompletedWithErrors")]
[EnumItem("ЗавершеноАварийно", "Failed")]
CompletedWithErrors
}
}
4 changes: 2 additions & 2 deletions src/ScriptEngine/Machine/Contexts/CriticalSectionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ public void Dispose()
}

[ScriptConstructor]
public CriticalSectionContext Create()
public static CriticalSectionContext Create()
{
return new CriticalSectionContext();
}

[ScriptConstructor(Name = "По объекту")]
public CriticalSectionContext Create(IValue instance)
public static CriticalSectionContext Create(IValue instance)
{
return new CriticalSectionContext(instance.AsObject());
}
Expand Down
3 changes: 1 addition & 2 deletions src/ScriptEngine/Machine/MachineInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1360,9 +1360,8 @@ private void RaiseException(int arg)
else
{
var exceptionValue = _operationStack.Pop().GetRawValue();
if (exceptionValue is ExceptionTemplate)
if (exceptionValue is ExceptionTemplate excInfo)
{
var excInfo = exceptionValue as ExceptionTemplate;
throw new ParametrizedRuntimeException(excInfo.Message, excInfo.Parameter);
}
else
Expand Down
Loading

0 comments on commit fefcfe3

Please sign in to comment.