CommunityToolkit / CommunityToolkit/dotnet

ParallelHelper.For: integer overflow in range length causes single-threaded fallback and OverflowException on valid inputs

Open
#1,188 0 comments 0 reactions 0 assignees View on GitHub
bug :bug:
Dominant language
C#
Stars
3.8k
Forks
400
PR merge metrics
No merged PRs in 30d

Description

### Describe the bug

`ParallelHelper.For(int start, int end, in TAction action, int minimumActionsPerThread)` silently degrades to a single-threaded execution for very large ranges, and can also throw `OverflowException` for some valid inputs.

The root cause is this computation inside the method:

https://github.com/CommunityToolkit/dotnet/blob/b135626dd54d33b8f05f2ff31591592c004aa848/src/CommunityToolkit.HighPerformance/Helpers/ParallelHelper.For.IAction.cs#L165-L166

1. `start - end` is computed in `int` and can overflow. For the full range `[int.MinValue, int.MaxValue)` the subtraction wraps around to `1`, so `count == 1`, `maxBatches == 1`, `numBatches == 1`, and the method executes the whole loop on the calling thread instead of scheduling work via `Parallel.For` as the caller explicitly requested.
2. For ranges like `[int.MinValue, 0)` the subtraction produces exactly `int.MinValue`, and `Math.Abs(int.MinValue)` throws `OverflowException` — the caller gets an exception for an otherwise valid input.

The input is already validated with `start > end` → throw, so `end - start` is guaranteed to be non-negative as a mathematical value, and it simply needs to be computed as `long` to avoid overflow.

### Regression

Not a regression as far as I can tell; the code has looked like this for a long time.

### Steps to reproduce

Tested on .NET 11, `CommunityToolkit.HighPerformance` 8.4.2.

```csharp
using CommunityToolkit.HighPerformance.Helpers;

internal readonly struct ThreadRecordingAction(int[] threadIds) : IAction
{
public void Invoke(int i)
{
// Only sample a few iterations to keep the repro fast
if ((i & 0xFFFFFF) != 0) return;

var slot = i >>> 24;
Volatile.Write(ref threadIds[slot], Environment.CurrentManagedThreadId);
}
}

internal static class Program
{
private static void Main()
{
// Bug #1: silently becomes single-threaded
var ids = new int[256];
ParallelHelper.For(int.MinValue, int.MaxValue, new ThreadRecordingAction(ids), 1);

Array.Sort(ids);
var unique = ids.Where((t, i) => i == 0 || t != ids[i - 1]).Count();

Console.WriteLine($"Distinct thread ids observed: {unique}"); // prints 1 on any multicore machine

// Bug #2: throws OverflowException
try
{
ParallelHelper.For(int.MinValue, 0, new ThreadRecordingAction(new int[1]), 1);
}
catch (OverflowException ex)
{
Console.WriteLine($"Unexpected: {ex}");
}
}
}
```

### Expected behavior

1. `ParallelHelper.For(int.MinValue, int.MaxValue, ..., 1)` should distribute work across all available cores, just like it does for any smaller range.
2. `ParallelHelper.For(int.MinValue, 0, ..., 1)` should execute successfully and not throw `OverflowException`.

More generally: for any valid pair `(start, end)` with `start <= end`, the method must compute the true element count without relying on `int` arithmetic that overflows.

### IDE and version

Rider

### IDE version

JetBrains Rider 2026.1 EAP 9 D

### Nuget packages

- [ ] CommunityToolkit.Common
- [ ] CommunityToolkit.Diagnostics
- [x] CommunityToolkit.HighPerformance
- [ ] CommunityToolkit.Mvvm (aka MVVM Toolkit)

### Nuget package version(s)

8.4.2

### Suggested fix

Compute the range as `long`:

```csharp
long count = (long)end - start; // always >= 0 after the start > end check
long maxBatches = 1 + ((count - 1) / minimumActionsPerThread);
int numBatches = (int)Math.Min(maxBatches, (long)Environment.ProcessorCount);
...
int batchSize = (int)(1 + ((count - 1) / (long)numBatches));
```

(`batchSize` fits into `int` because it is divided by `numBatches <= ProcessorCount`, but for full correctness `ActionInvoker` iteration bounds may need to be reviewed too.)
The bug is especially insidious because it does not throw or log anything — the workload simply runs on the calling thread, which looks like the parallel helper is "slow" rather than broken.

### Help us help you

Yes, but only if others can assist

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.