Skip to content
This repository has been archived by the owner on Mar 28, 2020. It is now read-only.

Commit

Permalink
Parallel tasks support;
Browse files Browse the repository at this point in the history
  • Loading branch information
KonH committed Sep 12, 2017
1 parent ef0421e commit f6704c9
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 39 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,35 @@ You can include one build into another build using this syntax in task list:
}
}
```

All tasks in sub build will be inserted in position of "_build" task.

It allow you to re-use tasks and avoid code redundancy.

If build args and sub-build args is the same, you can skip it and provide specific args only (which is not included in build args).

### Task parallelism

You can mark several tasks (one by one) as "parallel". This tasks run simultaneously and next execution is continued when all this tasks is done.

```
{
"task_a": {
...,
"parallel": "true"
},
"task_b": {
...,
"parallel": "true"
},
"task_c": {
...
}
}
In example above "task_a" and "task_b" start at same time, "task_c" started after "task_a" and "task_b" is done.
## Start
You need [dotnet](https://www.microsoft.com/net/download/core) runtime installed. Next steps is osX based, but Windows is also supported:
Expand Down
8 changes: 7 additions & 1 deletion Server/BuildConfig/BuildNode.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
using System.Collections.Generic;
using System.Collections.Generic;

namespace Server.BuildConfig {
public class BuildNode {
public string Name { get; }
public string Command { get; }
public Dictionary<string, string> Args { get; }

public bool IsParallel {
get {
return Args.GetBoolean("parallel", false);
}
}

public BuildNode(string name, string command, Dictionary<string, string> args) {
Name = name;
Command = command;
Expand Down
30 changes: 18 additions & 12 deletions Server/Runtime/BuildProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,22 @@ public bool IsSuccess {
get { return IsDone && !IsAborted && Tasks.All(task => task.IsSuccess); }
}

public BuildTask CurrentTask { get; private set; }
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public TimeSpan WorkTime { get; private set; }
public bool IsAborted { get; private set; }
public bool Silent { get; private set; }

public List<BuildTask> CurrentTasks { get; private set; }
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public TimeSpan WorkTime { get; private set; }
public bool IsAborted { get; private set; }
public bool Silent { get; private set; }

ILogger _logger;

bool _isDone = false;

public BuildProcess(LoggerFactory loggerFactory, Build build) {
_logger = loggerFactory.CreateLogger<BuildProcess>();
Name = build.Name;
Tasks = build.Nodes.Select(node => new BuildTask(loggerFactory, node)).ToList();
CurrentTasks = new List<BuildTask>();
}

BuildTask FindTask(BuildNode node) {
Expand All @@ -59,23 +61,23 @@ public void StartTask(BuildNode node) {
var task = FindTask(node);
if (task != null) {
_logger.LogDebug($"BuildProcess.StartTask: Task: {task.GetHashCode()}");
CurrentTask = task;
CurrentTasks.Add(task);
task.Start();
TaskStarted?.Invoke(task);
}
}

public void DoneTask(DateTime time, CommandResult result) {
public void DoneTask(BuildNode node, DateTime time, CommandResult result) {
_logger.LogDebug($"BuildProcess.DoneTask: {time}");
var task = CurrentTask;
var task = FindTask(node);
if (task == null) {
return;
}
_logger.LogDebug($"BuildProcess.DoneTask: Task: {task.GetHashCode()}");
task.Done(result.IsSuccess, result.Message, result.Result);
Silent = result.Silent;
TaskDone?.Invoke(task);
CurrentTask = null;
CurrentTasks.Remove(task);
if (IsDone || IsAborted) {
DoneBuild(time);
}
Expand All @@ -84,12 +86,16 @@ public void DoneTask(DateTime time, CommandResult result) {
public void Abort(DateTime time) {
_logger.LogDebug($"BuildProcess.Abort: {time}");
IsAborted = true;
if (CurrentTask == null) {
if (CurrentTasks.Count == 0) {
DoneBuild(time);
}
}

void DoneBuild(DateTime time) {
if ( _isDone ) {
return;
}
_isDone = true;
_logger.LogDebug($"BuildProcess.DoneBuild: {time}, isAborted: {IsAborted}");
EndTime = time;
WorkTime = EndTime - StartTime;
Expand Down
79 changes: 64 additions & 15 deletions Server/Runtime/BuildServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Server.Commands;
using Server.Services;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace Server.Runtime {
public class BuildServer {
Expand All @@ -34,11 +35,11 @@ public string ServiceName {
}
}

string[] _buildArgs;
Build _build;
Thread _thread;
BuildProcess _process;
ICommand _curCommand;
string[] _buildArgs;
Build _build;
Thread _thread;
BuildProcess _process;
List<ICommand> _curCommands = new List<ICommand>();

Dictionary<string, string> _taskStates = new Dictionary<string, string>();

Expand Down Expand Up @@ -221,9 +222,12 @@ void ProcessBuild() {
_logger.LogError("ProcessBuild: No build nodes!");
_process.Abort(_curTime);
}
foreach (var node in nodes) {
_logger.LogDebug($"ProcessBuild: node: \"{node.Name}\" (\"{node.Command}\")");
var result = ProcessCommand(_build, _buildArgs, node);
var tasks = InitTasks(nodes);
foreach (var task in tasks) {
_logger.LogDebug($"ProcessBuild: task: {tasks.IndexOf(task)}/{tasks.Count}");
task.Start();
task.Wait();
var result = task.Result;
if (!result) {
_logger.LogDebug($"ProcessBuild: failed command!");
_process.Abort(_curTime);
Expand All @@ -242,6 +246,50 @@ void ProcessBuild() {
_logger.LogDebug("ProcessBuild: cleared");
}

List<Task<bool>> InitTasks(List<BuildNode> nodes) {
var tasks = new List<Task<bool>>();
List<Task<bool>> parallelAccum = null;
foreach ( var node in nodes ) {
_logger.LogDebug($"InitTasks: node: \"{node.Name}\" (\"{node.Command}\")");
var task = new Task<bool>(() => ProcessCommand(_build, _buildArgs, node));
if ( node.IsParallel ) {
if ( parallelAccum == null ) {
parallelAccum = new List<Task<bool>>();
}
parallelAccum.Add(task);
} else {
ProcessParallelTask(ref parallelAccum, tasks);
tasks.Add(task);
}
}
ProcessParallelTask(ref parallelAccum, tasks);
return tasks;
}

void ProcessParallelTask(ref List<Task<bool>> accum, List<Task<bool>> tasks) {
if ( accum != null ) {
tasks.Add(CreateParallelTask(accum));
accum = null;
}
}

Task<bool> CreateParallelTask(List<Task<bool>> tasks) {
return new Task<bool>(() => ParallelProcess(tasks));
}

bool ParallelProcess(List<Task<bool>> tasks) {
foreach ( var task in tasks ) {
_logger.LogDebug($"Start parallel task: {tasks.IndexOf(task)}/{tasks.Count}");
task.Start();
}
Task.WaitAll(tasks.ToArray());
bool result = true;
foreach ( var task in tasks ) {
result = result && task.Result;
}
return result;
}

string TryReplace(string message, string key, string value) {
var keyFormat = string.Format("{{{0}}}", key);
if (message.Contains(keyFormat)) {
Expand Down Expand Up @@ -298,15 +346,15 @@ bool ProcessCommand(Build build, string[] buildArgs, BuildNode node) {
_logger.LogDebug($"ProcessCommand: \"{node.Name}\" (\"{node.Command}\")");
_process.StartTask(node);
var command = _commandFactory.Create(node);
_curCommand = command;
_curCommands.Add(command);
_logger.LogDebug($"ProcessCommand: command is \"{command.GetType().Name}\"");
var runtimeArgs = CreateRuntimeArgs(Project, build, buildArgs, node);
_logger.LogDebug($"ProcessCommand: runtimeArgs is {runtimeArgs.Count}");
var result = command.Execute(_loggerFactory, runtimeArgs);
_curCommand = null;
_curCommands.Remove(command);
_logger.LogDebug(
$"ProcessCommand: result is [{result.IsSuccess}, \"{result.Message}\", \"{result.Result}\"]");
_process.DoneTask(_curTime, result);
_process.DoneTask(node, _curTime, result);
AddTaskState(node.Name, result);
return result.IsSuccess;
}
Expand Down Expand Up @@ -339,10 +387,11 @@ public void AbortBuild() {
_logger.LogDebug("AbortBuild: Abort running process");
proc.Abort(_curTime);
}
var abortableCommand = _curCommand as IAbortableCommand;
if (abortableCommand != null) {
_logger.LogDebug("AbortBuild: Abort running command");
abortableCommand.Abort();
foreach ( var command in _curCommands ) {
if ( command is IAbortableCommand abortableCommand ) {
_logger.LogDebug("AbortBuild: Abort running command");
abortableCommand.Abort();
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Server/Server.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<Version>0.16.29.5</Version>
<Version>0.17.30.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" />
Expand Down
18 changes: 11 additions & 7 deletions Server/Views/BaseServerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,21 @@ protected string GetHelpMessage() {

protected abstract void OnHelpRequest();

protected void AppendTaskInfo(BuildTask task, StringBuilder sb) {
var allTasks = Process.Tasks;
var curTaskName = task.Node.Name;
var taskIndex = allTasks.IndexOf(task);
var totalTasks = allTasks.Count;
sb.Append($"Task: {curTaskName} ({taskIndex}/{totalTasks}) started: {task.StartTime}, duration: {Utils.FormatTimeSpan(DateTime.Now - task.StartTime)}\n");
}

protected string GetStatusMessage() {
var sb = new StringBuilder();
sb.Append($"{Server.Name} ({Server.ServiceName})\n");
sb.Append($"Is busy: {Process != null}\n");
var curTask = Process?.CurrentTask;
if (curTask != null) {
var allTasks = Process.Tasks;
var curTaskName = curTask.Node.Name;
var taskIndex = allTasks.IndexOf(curTask);
var totalTasks = allTasks.Count;
sb.Append($"Task: {curTaskName} ({taskIndex}/{totalTasks}) started: {curTask.StartTime}, duration: {DateTime.Now - curTask.StartTime}\n");
var curTasks = Process?.CurrentTasks;
if ((curTasks != null) && (curTasks.Count > 0)) {
curTasks.ForEach(t => AppendTaskInfo(t, sb));
}
sb.Append("Services:\n");
foreach (var service in Server.Services) {
Expand Down
10 changes: 7 additions & 3 deletions Server/Views/ConsoleServerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
namespace Server.Views {
public class ConsoleServerView:BaseServerView {

public string LogFile { get; set; }
public string LogFile { get; private set; }

ILogger _logger;

object logLock = new object();

public ConsoleServerView(LoggerFactory loggerFactory, BuildServer server) : base(loggerFactory, server) {
_logger = loggerFactory.CreateLogger<ConsoleServerView>();
}
Expand All @@ -19,7 +21,9 @@ void WriteLine(string message = "") {
Console.WriteLine(message);
try {
if (!string.IsNullOrEmpty(LogFile)) {
File.AppendAllText(LogFile, message + '\n');
lock ( logLock ) {
File.AppendAllText(LogFile, message + '\n');
}
}
}
catch (Exception e) {
Expand Down Expand Up @@ -66,7 +70,7 @@ protected override void OnBuildProcessDone() {
}
WriteLine();
WriteLine($"Build done: {Process.Name} {GetBuildArgsMessage()}");
WriteLine($"(success: {Process.IsSuccess}) for {Process.WorkTime}");
WriteLine($"(success: {Process.IsSuccess}) for {Utils.FormatTimeSpan(Process.WorkTime)}");
var lastTask = Process.Tasks.Last();
if (lastTask.IsSuccess) {
WriteLine("Last task message:");
Expand Down

0 comments on commit f6704c9

Please sign in to comment.