stride3d/stride · error · ArgumentOutOfRangeException

maximumConcurrencyLevel

Error message

maximumConcurrencyLevel

What it means

EffectPriorityScheduler schedules shader-compile tasks on dedicated threads with a bounded concurrency level. Zero is invalid (would mean no worker threads), so the constructor throws ArgumentOutOfRangeException for maximumConcurrencyLevel == 0. Note negative values are not rejected by this guard.

Solutions

  1. Pass at least 1 for maximumConcurrencyLevel, or use Environment.ProcessorCount.
  2. Clamp the configured value before constructing: Math.Max(1, configured).
  3. Fix config/container settings that resolve to 0.

Example fix

// before
var scheduler = new EffectPriorityScheduler(ThreadPriority.Normal, config.MaxConcurrency);
// after
var scheduler = new EffectPriorityScheduler(ThreadPriority.Normal, Math.Max(1, config.MaxConcurrency));
Defensive patterns

Strategy: validation

Validate before calling

if (maximumConcurrencyLevel < 1) throw new InvalidOperationException($"maximumConcurrencyLevel must be >= 1, got {maximumConcurrencyLevel}");

Type guard

null

Try / catch

try { var scheduler = new EffectPriorityScheduler(priority, level); } catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "maximumConcurrencyLevel") { level = Math.Max(1, level); }

Prevention

When it happens

Trigger: new EffectPriorityScheduler(threadPriority, 0) — passing a zero concurrency level, e.g. from a config value of 0 or Environment.ProcessorCount miscomputation.

Common situations: Config files with maxConcurrency=0 meant as 'unlimited'; CPU-count detection returning 0 in restricted containers.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/bad25f32e21ad9b1. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Effects/Compiler/EffectPriorityScheduler.cs:31

    /// A <see cref="TaskScheduler"/> with control over concurrency and priority, useful with <see cref="EffectCompilerCache"/>.
    /// </summary>
    public class EffectPriorityScheduler : TaskScheduler, IDisposable
    {
        private static object lockObject = new object();
        private ThreadPriority threadPriority;
        private Thread[] threads;
        private readonly int maximumConcurrencyLevel;
        private readonly PriorityQueue<QueuedTask> taskPriorityQueue;
        private readonly System.Collections.Generic.SortedList<int, PriorityGroupScheduler> priorityGroups = new System.Collections.Generic.SortedList<int, PriorityGroupScheduler>();
        private readonly AutoResetEvent notifyQueuedTaskEvent = new AutoResetEvent(false);
        private readonly AutoResetEvent notifyHighPriorityQueuedTaskEvent = new AutoResetEvent(false);
        private readonly ManualResetEvent notifyThreadExitEvent = new ManualResetEvent(false);
        private bool threadShouldExit;

        public EffectPriorityScheduler(ThreadPriority threadPriority, int maximumConcurrencyLevel)
        {
            if (maximumConcurrencyLevel == 0)
                throw new ArgumentOutOfRangeException("maximumConcurrencyLevel");

            this.taskPriorityQueue = new PriorityQueue<QueuedTask>(new TaskPriorityComparer());

            this.threadPriority = threadPriority;
            this.maximumConcurrencyLevel = maximumConcurrencyLevel;
        }

        public int QueuedTaskCount { get { return taskPriorityQueue.Count; } }

        /// <inheritdoc/>
        public override int MaximumConcurrencyLevel
        {
            get { return maximumConcurrencyLevel; }
        }

        /// <inheritdoc/>
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {

View on GitHub (pinned to 96fad776d2)