dotnet/machinelearning · error · ObjectDisposedException

PredictionEnginePool`2

Error message

PredictionEnginePool`2

What it means

PredictionEnginePool.GetPredictionEngine(modelName) checks _disposed before returning a pooled engine; after Dispose() (host shutdown or hot-swap) it throws ObjectDisposedException named PredictionEnginePool<TData,TPrediction>.

Solutions

  1. Re-resolve the pool from the service provider for each prediction batch
  2. Catch ObjectDisposedException and rebuild/resolve a new pool
  3. Ensure prediction work is cancelled during host shutdown

Example fix

// before
_pool.GetPredictionEngine("modelA"); // stale reference
// after
using var scope = _scopeFactory.CreateScope();
var pool = scope.ServiceProvider.GetRequiredService<PredictionEnginePool<MData,Pred>>();
pool.GetPredictionEngine("modelA");
Defensive patterns

Strategy: try-catch

Validate before calling

// resolve pool via IServiceProvider right before use instead of holding a reference

Type guard

null

Try / catch

try { var e = pool.GetPredictionEngine("m"); } catch (ObjectDisposedException) { pool = sp.GetRequiredService<PredictionEnginePool<TData,TPred>>(); var e = pool.GetPredictionEngine("m"); }

Prevention

When it happens

Trigger: Calling GetPredictionEngine after the pool instance was disposed — e.g. holding a stale reference across a model update or using the pool after the DI scope/host was torn down.

Common situations: Singleton-captured pool used by a background thread after host shutdown; long-running prediction loops surviving a model hot-swap that disposes old pools.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/machinelearning@7b76e69cf9 (2026-09-11). Data as JSON: /api/errors/9f014ce77f73ee87. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.Extensions.ML/PredictionEnginePool.cs:92

        /// </summary>
        public PredictionEngine<TData, TPrediction> GetPredictionEngine()
        {
            return GetPredictionEngine(string.Empty);
        }

        /// <summary>
        /// Gets a PredictionEngine for a named model.
        /// </summary>
        /// <param name="modelName">
        /// The name of the model which allows for uniquely identifying the model when
        /// multiple models have the same <typeparamref name="TData"/> and
        /// <typeparamref name="TPrediction"/> types.
        /// </param>
        public PredictionEngine<TData, TPrediction> GetPredictionEngine(string modelName)
        {
            if (Volatile.Read(ref _disposed) != 0)
            {
                throw new ObjectDisposedException(nameof(PredictionEnginePool<TData, TPrediction>));
            }

            if (_namedPools.TryGetValue(modelName, out var existingPool))
            {
                return existingPool.Get();
            }

            //This is the case where someone has used string.Empty to get the default model.
            //We can throw all the time, but it seems reasonable that we would just do what
            //they are expecting if they know that an empty string means default.
            if (string.IsNullOrEmpty(modelName))
            {
                if (_defaultEnginePool == null)
                {
                    throw new ArgumentException("You need to configure a default, not named, model before you use this method.");
                }

                return _defaultEnginePool.Get();

View on GitHub (pinned to 7b76e69cf9)