Concurrency Groups

Overview

FASTBuild automatically utilizes available CPU cores and parallelizes work where possible given the constraints of dependencies between tasks. While global concurrency can be controlled with the -j option, in some cases more fine-grained control is useful or desirable.

For example:

  • Linkers - Some linkers with certain codebases or options can use very large amounts of memory such that system memory may be exhausted when multiple link operations occur in parallel.
  • Tests - Running tests may rely on exclusive access to resources, such as network sockets, development hardware or file system structures. Running such tests concurrently may cause failures.
  • Compilers - Some compilers with certain codebases or options may use very large amounts of memory, exhausting system memory if run concurrently. Special options such as Static Analysis can often significantly increase memory usage.
  • Old Toolchains - Some toolchains may not be possible to run concurrently due to assumptions about being run in a single threaded environment.
  • External Processes - Invocation of external processes or scripts may not suppport being run concurrently.

In the above cases, limiting concurrency between related tasks can be useful while still allowing other tasks to be scheduled in an unconstrained manner. Concurrency Groups allow such tasks to be constrained.

NOTE: Concurrency Groups by their very nature limit build parallelism. Additionally, each Concurrency Group adds a small overhead to all job scheduling. As such, Concurrency Groups should be used sparingly and as few groups as possible should be used.

Defining Concurrency Groups

Concurrency Groups are named objects, specified globally via the Settings function. Each group is later referenced in tasks that should be in said group via .ConcurrencyGroupName.

Concurrency Limit

A Concurrency Group can define a .ConcurrencyLimit in order to cap the number of tasks in the group that can be run in parallel. For example, to limit concurrency to a single task:

Settings { .Group = [ .ConcurrencyGroupName = 'TestExecution' .ConcurrencyLimit = 1 ] .ConcurrencyGroups = { .Group } }
Concurrency Job Memory Limit

A Concurrency Group can define a .ConcurrencyPerJobMiB value in order to cap the number of tasks in the group based on the physical memory of the host. For example to limit concurrency assuming 2GiB per-task is required:

Settings { .Group = [ .ConcurrencyGroupName = 'Linker' .ConcurrencyPerJobMiB = 2048 ] .ConcurrencyGroups = { .Group } }

This constraint can be useful when dealing with tasks that need to run on a variety of systems which may have differening capabilities such that a fixed limit would unnecssarily pessimize for some users.

Multiple Constraints

Concurrency constraints can be combined, limiting by both absolute count as well as memory requirements as in the following example:

Settings { .Group = [ .ConcurrencyGroupName = 'Example' .ConcurrencyLimit = 4 .ConcurrencyPerJobMiB = 2048 ] .ConcurrencyGroups = { .Group } }

The lesser of the two constraints at any given time will be utilized to limit concurrency appropriately.

Using Concurrency Groups

Tasks which support Concurrency Groups expose a .ConcurrencyGroupName property which can be set to reference a previously defined Concurrency Group. For example:

Test( 'Test-Run-$Config$' ) { .ConcurrencyGroupName = 'TestExecution'

Tasks withing a given group will respect the concurrency constraits of that group.