#771 ThreadSafety issues i.e. in TaskBuilderCollection.get_Item

open
nobody
Core (183)
5
2011-12-05
2011-12-05
Anonymous
No

When I execute the custom made execution task which can execute multiple targets in parallel this works fine locally. On teamcity the taskpatcher tries to patch tasks which is not thread safe. This produces a collection modifed exception. See below

Exception on parallel worker: Collection was modified; enumeration operation may not execute. at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext() at NAnt.Core.TaskBuilderCollection.get_Item(String taskName) at JetBrains.BuildServer.NAntLoggers.TasksPatcher.PatchTask(String name, Type t) in c:\Agent\work\c283f2bd6aafd2d9\src\NAntLoggers\src\TasksPatcher.cs:line 52 at JetBrains.BuildServer.NAntLoggers.TasksPatcher.PatchTasks() in c:\Agent\work\c283f2bd6aafd2d9\src\NAntLoggers\src\TasksPatcher.cs:line 31 at JetBrains.BuildServer.NAntLoggers.NAntListenerImpl.TargetStarted(BuildEventArgs e) in c:\Agent\work\c283f2bd6aafd2d9\src\NAntLoggers\src\NAntListenerImpl.cs:line 82 at JetBrains.BuildServer.NAntLoggers.NAntListener2.TargetStarted(Object sender, BuildEventArgs e) in c:\Agent\work\c283f2bd6aafd2d9\src\NAntLoggers\src\NAntListener2.cs:line 62 at JetBrains.BuildServer.NAntLoggers.NAntListener.TargetStarted(Object sender, BuildEventArgs e) in c:\Agent\work\c283f2bd6aafd2d9\src\NAntLoggers\src\NAntListener.cs:line 33 at NAnt.Core.BuildEventHandler.Invoke(Object sender, BuildEventArgs e) at NAnt.Core.Project.OnTargetStarted(Object sender, BuildEventArgs e) at NAnt.Core.Target.Execute() at NAnt.Core.Project.Execute(String targetName, Boolean forceDependencies) at NAnt.Core.Tasks.CallTask.ExecuteTask() at NAnt.Core.Task.Execute() at bbv.NantTasks.AsyncCallTask.Worker(ConcurrentQueue`1 exceptions, ParallelLoopState state, CallTask task)

Here the task definition

code
[TaskName("asynccall")]
public class AsyncCallTask : Task
{
private readonly ConcurrentBag<CallTask> tasksToBeExecuted;

public AsyncCallTask()
{
this.FailFast = true;
this.MaxDegreeOfParallelism = -1;

this.tasksToBeExecuted = new ConcurrentBag<CallTask>();
}

protected override void ExecuteTask()
{
var exceptions = new ConcurrentQueue<Exception>();
var result = Parallel.ForEach(this.tasksToBeExecuted, new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, (task, state) => this.Worker(exceptions, state, task));

if (this.FailOnError && exceptions.Any())
{
throw new BuildException("Parallel worker failed!", new AggregateException(exceptions));
}
}

[TaskAttribute("FailFast")]
public bool FailFast
{
get;
set;
}

[TaskAttribute("MaxDegreeOfParallelism")]
public int MaxDegreeOfParallelism
{
get;
set;
}

[BuildElement("call", Required = true)]
public void AddExec(CallTask task)
{
task.Parent = this;

this.tasksToBeExecuted.Add(task);
}

private void Worker(ConcurrentQueue<Exception> exceptions, ParallelLoopState state, CallTask task)
{
try
{
task.Execute();
}
catch (Exception e)
{
Log(this.FailOnError ? Level.Error : Level.Debug, "Exception on parallel worker: {0}\r\n{3}\r\nFailOnError: {1} / FailFast: {2}", e.Message, this.FailOnError, this.FailFast, e.StackTrace);

exceptions.Enqueue(e);

if (this.FailFast)
{
state.Break();
}
}
}
}
code

and here the usage

code
<target name="tests" description="run tests and specs in parallel">
<asynccall FailFast="true" MaxDegreeOfParallelism="4">
<call target="unit-tests" />
<call target="specifications" />
</asynccall>
</target>

I first thought it is related to TeamCity's task patching but the problem is that TaskBuilderCollection.Item[] is not thread safe.

Discussion