| File: PoolLoader.cs | Web Access |
| Project: src\src\Microsoft.Extensions.ML\Microsoft.Extensions.ML.csproj (Microsoft.Extensions.ML) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.ML; namespace Microsoft.Extensions.ML { /// <summary> /// Encapsulates the data and logic required for loading and reloading PredictionEngine object pools. /// </summary> internal class PoolLoader<TData, TPrediction> : IDisposable where TData : class where TPrediction : class, new() { private static readonly ObjectPoolProvider _poolProvider = new DefaultObjectPoolProvider(); private ObjectPool<PredictionEngine<TData, TPrediction>> _pool; private ITransformer _model; private readonly IDisposable _changeTokenRegistration; private readonly ConditionalWeakTable<PredictionEngine<TData, TPrediction>, ObjectPool<PredictionEngine<TData, TPrediction>>> _rentedEngines; private int _disposed; public PoolLoader(IServiceProvider sp, PredictionEnginePoolOptions<TData, TPrediction> poolOptions) { var contextOptions = sp.GetRequiredService<IOptions<MLOptions>>(); Context = contextOptions.Value.MLContext ?? throw new ArgumentNullException(nameof(contextOptions)); Loader = poolOptions.ModelLoader ?? throw new ArgumentNullException(nameof(poolOptions)); _rentedEngines = new ConditionalWeakTable<PredictionEngine<TData, TPrediction>, ObjectPool<PredictionEngine<TData, TPrediction>>>(); LoadPool(); _changeTokenRegistration = ChangeToken.OnChange( () => Loader.GetReloadToken(), () => LoadPool()); } 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)); } if (_rentedEngines.TryGetValue(engine, out var origin)) { _rentedEngines.Remove(engine); origin.Return(engine); } else { engine.Dispose(); } } private void LoadPool() { if (Volatile.Read(ref _disposed) != 0) { return; } var model = Loader.GetModel(); var policy = new PredictionEnginePoolPolicy<TData, TPrediction>(Context, model); var newPool = _poolProvider.Create(policy); var oldPool = Interlocked.Exchange(ref _pool, newPool); var oldModel = Interlocked.Exchange(ref _model, model); DisposeGeneration(oldPool, oldModel, model); if (Volatile.Read(ref _disposed) != 0 && Interlocked.CompareExchange(ref _pool, null, newPool) == newPool) { DisposeGeneration(newPool, model, null); } } private static void DisposeGeneration( ObjectPool<PredictionEngine<TData, TPrediction>> pool, ITransformer model, ITransformer survivingModel) { (pool as IDisposable)?.Dispose(); if (model != null && !ReferenceEquals(model, survivingModel)) { (model as IDisposable)?.Dispose(); } } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) != 0) { return; } _changeTokenRegistration?.Dispose(); var pool = Interlocked.Exchange(ref _pool, null); var model = Interlocked.Exchange(ref _model, null); DisposeGeneration(pool, model, null); (Loader as IDisposable)?.Dispose(); } } }