[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/kidoz/dotpython/main/src/DotPython.Hosting/DotPythonHost.cs [Back]  [Original]

using System.Diagnostics.CodeAnalysis;
using DotPython.Contracts;
using DotPython.Runtime.Managed;

namespace DotPython.Hosting;

/// 
/// Owns a managed DotPython runtime and typed module clients for small applications.
/// 
public sealed class DotPythonHost : IAsyncDisposable
{
    private readonly object _gate = new();
    private readonly DotPythonModuleProvider _provider;
    private readonly IDotPythonModuleRuntime _runtime;
    private readonly List _sessions = [];
    private Task? _disposeTask;
    private bool _disposed;

    internal DotPythonHost(IDotPythonModuleRuntime runtime)
    {
        _runtime = runtime;
        _provider = new DotPythonModuleProvider(runtime);
    }

    /// Creates a host that owns a managed DotPython runtime.
    [SuppressMessage(
        "Reliability",
        "CA2000:Dispose objects before losing scope",
        Justification = "The returned host takes ownership of the managed runtime."
    )]
    public static DotPythonHost CreateManaged() => new(new ManagedPythonModuleRuntime());

    /// 
    /// Creates a host that takes ownership of a backend-independent module runtime.
    /// 
    public static DotPythonHost Create(IDotPythonModuleRuntime runtime)
    {
        ArgumentNullException.ThrowIfNull(runtime);
        return new DotPythonHost(runtime);
    }

    /// Creates a typed client for a per-runtime module.
    public TService GetModule(PythonModuleRegistration registration)
        where TService : class => GetModule(registration, static _ => { });

    /// Creates a configured typed client for a per-runtime module.
    public TService GetModule(
        PythonModuleRegistration registration,
        Action configure
    )
        where TService : class
    {
        ArgumentNullException.ThrowIfNull(registration);
        ArgumentNullException.ThrowIfNull(configure);
        if (registration.StatePolicy != PythonModuleStatePolicy.PerRuntime)
        {
            throw new InvalidOperationException(
                "Per-session modules must be resolved from a DotPythonModuleSession."
            );
        }

        lock (_gate)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
            ConfigureProvider(_provider, registration.Definition, configure);
            return registration.CreateClient(_provider);
        }
    }

    /// Loads and validates a per-runtime module before its first invocation.
    public ValueTask WarmUpAsync(
        PythonModuleRegistration registration,
        CancellationToken cancellationToken = default
    )
        where TService : class => WarmUpAsync(registration, static _ => { }, cancellationToken);

    /// Loads and validates a configured per-runtime module.
    public ValueTask WarmUpAsync(
        PythonModuleRegistration registration,
        Action configure,
        CancellationToken cancellationToken = default
    )
        where TService : class
    {
        ArgumentNullException.ThrowIfNull(registration);
        ArgumentNullException.ThrowIfNull(configure);
        if (registration.StatePolicy != PythonModuleStatePolicy.PerRuntime)
        {
            throw new InvalidOperationException(
                "Per-session modules must be warmed within a DotPythonModuleSession."
            );
        }

        lock (_gate)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
            ConfigureProvider(_provider, registration.Definition, configure);
            return _provider.WarmUpAsync(registration.Definition, cancellationToken);
        }
    }

    /// 
    /// Creates a logical state scope for per-session modules. This is not a security boundary.
    /// 
    public DotPythonModuleSession CreateSession()
    {
        lock (_gate)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
            var session = new DotPythonModuleSession(_runtime, RemoveSession);
            _sessions.Add(session);
            return session;
        }
    }

    /// 
    public ValueTask DisposeAsync()
    {
        lock (_gate)
        {
            if (_disposeTask is not null)
            {
                return new ValueTask(_disposeTask);
            }

            _disposed = true;
            _disposeTask = DisposeCoreAsync(_sessions.AsEnumerable().Reverse().ToArray());
            return new ValueTask(_disposeTask);
        }
    }

    private async Task DisposeCoreAsync(IReadOnlyList sessions)
    {
        try
        {
            foreach (var session in sessions)
            {
                await session.DisposeFromHostAsync().ConfigureAwait(false);
            }
        }
        finally
        {
            try
            {
                await _provider.DisposeAsync().ConfigureAwait(false);
            }
            finally
            {
                await _runtime.DisposeAsync().ConfigureAwait(false);
            }
        }
    }

    private void RemoveSession(DotPythonModuleSession session)
    {
        lock (_gate)
        {
            _sessions.Remove(session);
        }
    }

    internal static void ConfigureProvider(
        DotPythonModuleProvider provider,
        PythonModuleDefinition definition,
        Action configure
    )
    {
        var options = new DotPythonModuleHostingOptions();
        configure(options);
        options.Validate();
        provider.ConfigureInitialization(definition, options.MaximumInitializationAttempts);
    }
}

Web Proxy Viewer  |  New URL  |  Original Page