CommunityToolkit / CommunityToolkit/dotnet
ParallelHelper.For: integer overflow in range length causes single-threaded fallback and OverflowException on valid inputs
- 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
Assessment
This issue has not been assessed yet.