Skip to main content

Parallelization Disabling

As mentioned, modules will try to run in parallel by default, waiting on any dependencies if they need to.

However, sometimes modules don't have any dependencies, but also it isn't a good idea trying to run them in parallel.

As example of this could be installing applications. Windows for instance, doesn't like you trying to install multiple applications at the same time.

So if you want any modules to be run without parallelisation, there is the NotInParallel attribute.

These modules will attempt to run first, before all other modules that can be run in parallel. If these have any dependencies, they will be triggered too.

Example

[NotInParallel]
public class MyModule : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

Not NotInParallel attribute can also take a ConstraintKey parameter. If this is set, then a module will not run in parallel with other modules containing the same constraint key. If another module has a different constraint key, these will still run in parallel.

Example

[NotInParallel(ConstraintKey = "Install")]
public class InstallModule1 : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

[NotInParallel(ConstraintKey = "Install")]
public class InstallModule2 : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

[NotInParallel(ConstraintKey = "Build")]
public class BuildProjectModule : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

In the above example, InstallModule1 and InstallModule2 will not run at the same time. However, either of them could run at the same time as BuildProjectModule.

Parallel Limiter

ModularPipelines allows the user to control the parallel limit on specific modules.

To do this, we add a [ParallelLimiter<>] attribute.

You'll notice this has a generic type argument - You must give it a type that implements IParallelLimit and has a public empty constructor. That interface requires you to define what the limit is for those modules.

If a module doesn't have a parallel limit defined, it'll try and eagerly run when the .NET thread pool allows it to do so.

If it does have a parallel limit defined, be aware that that parallel limit is shared for any modules with that same Type of parallel limit.

In the example below, MyParallelLimit has a limit of 2. Now any module that has this parallel limit attribute applied to it, will only be processed 2 at a time.

Other modules without this attribute may run alongside them still.

And other modules with a different Type of parallel limit may also run alongside them still, but limited amongst themselves by their shared Type and limit.

So be aware that limits are only shared among modules with that same IParallelLimit Type.

Example

using TUnit.Core;

namespace MyTestProject;

[ParallelLimiter<MyParallelLimit>]
public class InstallModule1 : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

[ParallelLimiter<MyParallelLimit>]
public class InstallModule2 : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

[ParallelLimiter<MyParallelLimit>]
public class BuildProjectModule : Module
{
protected override Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// Do something
}
}

public record MyParallelLimit : IParallelLimit
{
public int Limit => 2;
}

Caveats

If a test uses [DependsOn(nameof(OtherTest))] and the other test has its own different parallel limit, this isn't guaranteed to be honoured.