chore(unity): 固定Unity CLI Loop本地包并移除UnitySkills依赖
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
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<IExecuteDynamicCodeUseCase> GetExecuteDynamicCodeUseCaseAsync()
|
||||
{
|
||||
IDynamicCodeExecutionRuntime runtimeFacade = await GetRuntimeFacadeAsync();
|
||||
return new ExecuteDynamicCodeUseCase(runtimeFacade);
|
||||
}
|
||||
|
||||
public static async Task<IPrewarmDynamicCodeUseCase> 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<IDynamicCodeExecutionRuntime> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user