| File: Language\IntegrationHostProcessLauncher.cs | Web Access |
| Project: src\src\Aspire.Hosting.RemoteHost\Aspire.Hosting.RemoteHost.csproj (Aspire.Hosting.RemoteHost) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using Aspire.Hosting.RemoteHost.Diagnostics; using Aspire.Shared; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.RemoteHost.Language; /// <summary> /// Resolves and starts contained integration processes without managing recovery. /// </summary> internal sealed class IntegrationHostProcessLauncher( LanguageSupportResolver languageResolver, IntegrationHostConfiguration configuration, RemoteHostProfilingTelemetry profilingTelemetry, ILogger<IntegrationHostLauncher> logger, IChildProcessFactory processFactory, TimeProvider timeProvider) { public async Task<ProcessScope> LaunchAsync(IntegrationHostDescriptor descriptor, string registrationId, CancellationToken cancellationToken) { using var activity = profilingTelemetry.StartIntegrationHostPhase("launch", descriptor.PackageName, descriptor.Language); try { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(configuration.SocketPath)) { throw new InvalidOperationException("REMOTE_APP_HOST_SOCKET_PATH is not set on the server; cannot spawn integration hosts."); } var languageSupport = languageResolver.GetLanguageSupport(descriptor.Language); var hostSpec = languageSupport is null ? null : LanguageService.GetIntegrationHostSpec(languageSupport); if (hostSpec is null) { throw new InvalidOperationException( $"Language '{descriptor.Language}' does not provide an integration host for '{descriptor.PackageName}'."); } // Restore is a CLI operation, never a side effect of process recovery. if (!CommandPathResolver.TryResolveCommand(hostSpec.Execute.Command, out var command, out var error)) { throw new InvalidOperationException($"Cannot launch integration host '{descriptor.PackageName}' [{descriptor.Language}]: {error}"); } var startInfo = CreateProcessStartInfo(command!, hostSpec.Execute.Args, descriptor.HostEntryPoint, OperatingSystem.IsWindows()); startInfo.Environment["REMOTE_APP_HOST_SOCKET_PATH"] = configuration.SocketPath; startInfo.Environment[IntegrationHostLauncher.RegistrationIdVariable] = registrationId; if (!string.IsNullOrEmpty(configuration.Token)) { startInfo.Environment[KnownConfigNames.RemoteAppHostToken] = configuration.Token; } logger.LogInformation( "Launching integration host '{Name}' [{Language}]: {Command} (entry: {EntryPoint}, cwd: {Directory}).", descriptor.PackageName, descriptor.Language, command, descriptor.HostEntryPoint, startInfo.WorkingDirectory); var execution = processFactory.Create(startInfo, logger, new ChildProcessOptions { Lifetime = ChildProcessLifetime.OwnedTree, TimeProvider = timeProvider, TerminationTimeout = configuration.ShutdownTimeout, OutputDrainIdleTimeout = configuration.OutputDrainIdleTimeout, StandardOutputCallback = line => logger.LogInformation("IntegrationHost[{Name}]: {Line}", descriptor.PackageName, line), StandardErrorCallback = line => logger.LogWarning("IntegrationHost[{Name}]: {Line}", descriptor.PackageName, line) }, OperatingSystem.IsWindows()); try { if (!await execution.StartAsync(cancellationToken).ConfigureAwait(false)) { throw new InvalidOperationException($"Could not start integration host '{descriptor.PackageName}'."); } var process = new ProcessScope(execution, logger, descriptor.PackageName); activity.SetIntegrationHostProcessId(process.ProcessId); logger.LogInformation("Started integration host supervisor '{Name}' (PID {Pid}).", descriptor.PackageName, process.ProcessId); return process; } catch (Exception launchError) { try { await execution.DisposeAsync().ConfigureAwait(false); } catch (Exception cleanupError) { throw new AggregateException("Integration host launch and cleanup both failed.", launchError, cleanupError); } throw; } } catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested) { activity.SetError(ex); logger.LogDebug("Launch canceled for integration host '{Name}' [{Language}].", descriptor.PackageName, descriptor.Language); throw; } catch (Exception ex) { activity.SetError(ex); logger.LogError(ex, "Failed to launch integration host '{Name}' [{Language}].", descriptor.PackageName, descriptor.Language); throw; } } internal static ProcessStartInfo CreateProcessStartInfo(string command, IReadOnlyList<string> argumentTemplates, string entryPoint, bool isWindows) { var startInfo = new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName(entryPoint)!, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }; ProcessStartInfoHelper.SetCommand( startInfo, command, argumentTemplates.Select(argument => argument.Replace("{entryPoint}", entryPoint)), isWindows); return startInfo; } }