dotnet/machinelearning · error · ObjectDisposedException

PoolLoader`1

Error message

PoolLoader`1

What it means

PoolLoader.Get() rents a PredictionEngine from its internal pool. If the loader was disposed (hot-swap or pool shutdown set _disposed) or the pool object is gone, it throws ObjectDisposedException named after PoolLoader<TData,TPrediction>.

Solutions

  1. Resolve PredictionEnginePool through DI fresh each time instead of caching it across hot-swaps
  2. Catch ObjectDisposedException and re-resolve the pool
  3. Ensure the host/pool outlives all code that rents engines

Example fix

// before: cached field
private readonly PredictionEnginePool<MData,Pred> _pool; // disposed after hot-swap
// after: resolve per use
var engine = _serviceProvider.GetRequiredService<PredictionEnginePool<MData,Pred>>().GetPredictionEngine();
Defensive patterns

Strategy: try-catch

Validate before calling

// check pool liveness via DI resolution each use, not a cached field

Type guard

null

Try / catch

try { var engine = pool.Get(); } catch (ObjectDisposedException) { pool = serviceProvider.GetRequiredService<PredictionEnginePool<TData,TPred>>(); var engine = pool.Get(); }

Prevention

When it happens

Trigger: Calling Get() after the PredictionEnginePool was disposed (e.g. after a model hot-swap replaced the pool, or after host shutdown); holding a rented engine across a model reload then calling Get() again.

Common situations: App long-lived code caching a reference to PredictionEnginePool across a model file update; DI container disposed while background code still calls Get.

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/449630977504bff1. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.Extensions.ML/PoolLoader.cs:65

        public ModelLoader Loader { get; }
        private MLContext Context { get; }

        /// <summary>
        /// The active pool generation. Exposed for compatibility; prefer <see cref="Get"/> and
        /// <see cref="Return"/>, which route an engine back to the generation that created it.
        /// </summary>
        public ObjectPool<PredictionEngine<TData, TPrediction>> PredictionEnginePool { get { return Volatile.Read(ref _pool); } }

        /// <summary>
        /// Rents an engine from the current pool generation, recording its origin so it can be
        /// returned to the correct generation later.
        /// </summary>
        public PredictionEngine<TData, TPrediction> Get()
        {
            var pool = Volatile.Read(ref _pool);
            if (Volatile.Read(ref _disposed) != 0 || pool == null)
            {
                throw new ObjectDisposedException(nameof(PoolLoader<TData, TPrediction>));
            }

            var engine = pool.Get();

            _rentedEngines.Remove(engine);
            _rentedEngines.Add(engine, pool);
            return engine;
        }

        /// <summary>
        /// Returns an engine to the generation it was rented from. If that generation has already
        /// been disposed by a hot-swap, the pool disposes the engine instead of retaining it.
        /// </summary>
        public void Return(PredictionEngine<TData, TPrediction> engine)
        {
            if (engine == null)
            {
                throw new ArgumentNullException(nameof(engine));

View on GitHub (pinned to 7b76e69cf9)