using System; using System.Threading; using System.Threading.Tasks; using io.github.hatayama.uLoopMCP.Factory; namespace io.github.hatayama.uLoopMCP { internal static class DynamicCodeServices { private static readonly object ServerScopedServicesLock = new(); private static TimeSpan s_serverScopedDrainTimeout = TimeSpan.FromSeconds(5); private static Task _serverScopedDrainTask = Task.CompletedTask; private static CancellationTokenSource _serverScopedLifetimeCancellationTokenSource; private static IDynamicCodeExecutorPool _executorPool; private static IDynamicCodeExecutionRuntime _runtimeFacade; private static IPrewarmDynamicCodeUseCase _prewarmDynamicCodeUseCase; public static DynamicCodeSourcePreparationService SourcePreparationService { get; } = new DynamicCodeSourcePreparationService(); public static ExternalCompilerPathResolutionService ExternalCompilerPathResolver { get; } = new ExternalCompilerPathResolutionService(); public static DynamicReferenceSetBuilderService ReferenceSetBuilder { get; } = new DynamicReferenceSetBuilderService(); public static IDynamicCompilationPlanner CompilationPlanner { get; } = new DynamicCompilationPlanner(SourcePreparationService); public static ICompiledAssemblyLoader AssemblyLoadService { get; } = new CompiledAssemblyLoadService(); public static DynamicCompilationBackend CompilationBackend { get; } = new DynamicCompilationBackend(); public static ICompiledAssemblyBuilder AssemblyBuilder { get; } = new CompiledAssemblyBuilder( ExternalCompilerPathResolver, ReferenceSetBuilder, CompilationBackend); public static CompiledCommandEntryPointResolver CommandEntryPointResolver { get; } = new CompiledCommandEntryPointResolver(); public static RegistryDynamicCodeExecutorFactory ExecutorFactory { get; } = new RegistryDynamicCodeExecutorFactory( SourcePreparationService, CommandEntryPointResolver); public static async Task GetExecuteDynamicCodeUseCaseAsync() { IDynamicCodeExecutionRuntime runtimeFacade = await GetRuntimeFacadeAsync(); return new ExecuteDynamicCodeUseCase(runtimeFacade); } public static async Task GetPrewarmDynamicCodeUseCaseAsync( string serverStartingLockToken = null) { await EnsureServerScopedServicesInitializedAsync(serverStartingLockToken); lock (ServerScopedServicesLock) { return _prewarmDynamicCodeUseCase; } } public static void ResetServerScopedServices() { CancellationTokenSource lifetimeCancellationTokenSource; IDynamicCodeExecutionRuntime runtimeFacade; Task shutdownTask; lock (ServerScopedServicesLock) { lifetimeCancellationTokenSource = _serverScopedLifetimeCancellationTokenSource; runtimeFacade = _runtimeFacade; _serverScopedLifetimeCancellationTokenSource = null; _executorPool = null; _runtimeFacade = null; _prewarmDynamicCodeUseCase = null; shutdownTask = CreateShutdownTask(lifetimeCancellationTokenSource, runtimeFacade); _serverScopedDrainTask = ChainDrainTask(_serverScopedDrainTask, shutdownTask); } } public static void ResetServerScopedServicesBeforeDomainReload() { CancellationTokenSource lifetimeCancellationTokenSource; IDynamicCodeExecutionRuntime runtimeFacade; lock (ServerScopedServicesLock) { lifetimeCancellationTokenSource = _serverScopedLifetimeCancellationTokenSource; runtimeFacade = _runtimeFacade; _serverScopedLifetimeCancellationTokenSource = null; _executorPool = null; _runtimeFacade = null; _prewarmDynamicCodeUseCase = null; _serverScopedDrainTask = Task.CompletedTask; } lifetimeCancellationTokenSource?.Cancel(); SignalRuntimeShutdownBeforeDomainReload(runtimeFacade); SharedRoslynCompilerWorkerHost.ShutdownForServerReset(); } private static void SignalRuntimeShutdownBeforeDomainReload( IDynamicCodeExecutionRuntime runtimeFacade) { if (runtimeFacade is IShutdownAwareDynamicCodeExecutionRuntime shutdownAwareRuntime) { Task shutdownTask = shutdownAwareRuntime.ShutdownAsync(); _ = CreateObservedDrainTask( shutdownTask, "server_scoped_shutdown_before_domain_reload_failed"); return; } (runtimeFacade as IDisposable)?.Dispose(); } private static async Task GetRuntimeFacadeAsync() { await EnsureServerScopedServicesInitializedAsync(); lock (ServerScopedServicesLock) { return _runtimeFacade; } } private static async Task EnsureServerScopedServicesInitializedAsync( string serverStartingLockToken = null) { DynamicCodeCompilationServiceRegistration.EnsureRegistered(); Task drainTask; lock (ServerScopedServicesLock) { if (_runtimeFacade != null) { AttachServerStartingLockTokenIfNeeded(serverStartingLockToken); return; } drainTask = _serverScopedDrainTask; } await AwaitDrainTaskAsync(drainTask); lock (ServerScopedServicesLock) { if (_runtimeFacade != null) { AttachServerStartingLockTokenIfNeeded(serverStartingLockToken); return; } _serverScopedLifetimeCancellationTokenSource = new CancellationTokenSource(); _executorPool = new DynamicCodeExecutorPool(ExecutorFactory); _runtimeFacade = new DynamicCodeExecutionFacade( AssemblyBuilder, _executorPool); _prewarmDynamicCodeUseCase = new PrewarmDynamicCodeUseCase( _runtimeFacade, _serverScopedLifetimeCancellationTokenSource.Token, null, serverStartingLockToken); } } private static void AttachServerStartingLockTokenIfNeeded(string serverStartingLockToken) { if (string.IsNullOrEmpty(serverStartingLockToken)) { return; } if (_prewarmDynamicCodeUseCase is PrewarmDynamicCodeUseCase prewarmDynamicCodeUseCase) { prewarmDynamicCodeUseCase.AttachServerStartingLockToken(serverStartingLockToken); } } private static Task CreateShutdownTask( CancellationTokenSource lifetimeCancellationTokenSource, IDynamicCodeExecutionRuntime runtimeFacade) { if (lifetimeCancellationTokenSource == null && runtimeFacade == null) { return Task.CompletedTask; } return ShutdownServerScopedServicesAsync(lifetimeCancellationTokenSource, runtimeFacade); } private static Task ChainDrainTask(Task currentDrainTask, Task nextDrainTask) { Task observedCurrentDrainTask = CreateObservedDrainTask( currentDrainTask, "server_scoped_shutdown_previous_failed"); Task observedNextDrainTask = CreateObservedDrainTask( nextDrainTask, "server_scoped_shutdown_failed"); if (observedCurrentDrainTask.IsCompletedSuccessfully) { return observedNextDrainTask; } return ContinueAfterDrainAsync(observedCurrentDrainTask, observedNextDrainTask); } private static async Task ContinueAfterDrainAsync(Task currentDrainTask, Task nextDrainTask) { await currentDrainTask; await nextDrainTask; } private static Task CreateObservedDrainTask(Task drainTask, string operation) { if (drainTask == null || drainTask.IsCompletedSuccessfully) { return Task.CompletedTask; } return drainTask.ContinueWith( task => LogDrainFailure(operation, task), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } private static void LogDrainFailure(string operation, Task drainTask) { if (drainTask.IsFaulted) { Exception exception = drainTask.Exception?.InnerException ?? drainTask.Exception; VibeLogger.LogWarning( operation, "Server-scoped dynamic code shutdown failed; continuing with a fresh runtime", new { exception_type = exception?.GetType().Name, exception_message = exception?.Message }); return; } if (drainTask.IsCanceled) { VibeLogger.LogInfo( operation, "Server-scoped dynamic code shutdown was cancelled; continuing with a fresh runtime"); } } internal static async Task AwaitDrainTaskAsync(Task drainTask) { if (drainTask == null) { return; } if (drainTask.IsCompleted) { await drainTask; return; } TimeSpan timeout = s_serverScopedDrainTimeout; UnityEngine.Debug.Assert(timeout > TimeSpan.Zero, "server-scoped drain timeout must be positive"); Task completedTask = await Task.WhenAny(drainTask, Task.Delay(timeout)); if (completedTask == drainTask) { await drainTask; return; } VibeLogger.LogWarning( "server_scoped_shutdown_timeout", "Server-scoped dynamic code shutdown exceeded the drain timeout; continuing with a fresh runtime", new { timeout_ms = (int)timeout.TotalMilliseconds }); } private static async Task ShutdownServerScopedServicesAsync( CancellationTokenSource lifetimeCancellationTokenSource, IDynamicCodeExecutionRuntime runtimeFacade) { lifetimeCancellationTokenSource?.Cancel(); SharedRoslynCompilerWorkerHost.ShutdownForServerReset(); if (runtimeFacade is IShutdownAwareDynamicCodeExecutionRuntime shutdownAwareRuntime) { await shutdownAwareRuntime.ShutdownAsync(); } else { (runtimeFacade as IDisposable)?.Dispose(); } lifetimeCancellationTokenSource?.Dispose(); } internal static TimeSpan SwapDrainTimeoutForTests(TimeSpan timeout) { UnityEngine.Debug.Assert(timeout > TimeSpan.Zero, "timeout must be positive"); TimeSpan previous = s_serverScopedDrainTimeout; s_serverScopedDrainTimeout = timeout; return previous; } internal static void SetServerScopedServicesForTests( CancellationTokenSource lifetimeCancellationTokenSource, IDynamicCodeExecutorPool executorPool, IDynamicCodeExecutionRuntime runtimeFacade, IPrewarmDynamicCodeUseCase prewarmDynamicCodeUseCase) { lock (ServerScopedServicesLock) { _serverScopedLifetimeCancellationTokenSource = lifetimeCancellationTokenSource; _executorPool = executorPool; _runtimeFacade = runtimeFacade; _prewarmDynamicCodeUseCase = prewarmDynamicCodeUseCase; } } internal static Task GetServerScopedDrainTaskForTests() { lock (ServerScopedServicesLock) { return _serverScopedDrainTask; } } internal static void ResetStateForTests() { lock (ServerScopedServicesLock) { _serverScopedLifetimeCancellationTokenSource = null; _executorPool = null; _runtimeFacade = null; _prewarmDynamicCodeUseCase = null; _serverScopedDrainTask = Task.CompletedTask; } } } }