From 10c5e35615ab75321d46db65517f1a85ab78dc24 Mon Sep 17 00:00:00 2001 From: Aishwarya Bhandari Date: Tue, 17 Sep 2024 15:58:45 -0700 Subject: [PATCH 1/3] initial changes for custom host --- .../AppLoader/AppLoader.cs | 107 +++++++++++++++ .../FunctionsCustomHost/AppLoader/HostFxr.cs | 46 +++++++ .../FunctionsCustomHost/AppLoader/NetHost.cs | 41 ++++++ .../WorkerLoadStatusSignalManager.cs | 19 +++ .../Configuration/Configuration.cs | 38 ++++++ .../Environment/EnvironmentUtils.cs | 37 +++++ .../Environment/EnvironmentVariables.cs | 32 +++++ .../FunctionsCustomHost.csproj | 40 ++++++ host/src/FunctionsCustomHost/Logger.cs | 27 ++++ .../Prelaunch/Prelauncher.cs | 81 +++++++++++ host/src/FunctionsCustomHost/Program.cs | 129 ++++++++++++++++++ .../Properties/launchSettings.json | 8 ++ host/src/FunctionsCustomHost/exports.def | 4 + host/src/FunctionsCustomHost/global.json | 6 + 14 files changed, 615 insertions(+) create mode 100644 host/src/FunctionsCustomHost/AppLoader/AppLoader.cs create mode 100644 host/src/FunctionsCustomHost/AppLoader/HostFxr.cs create mode 100644 host/src/FunctionsCustomHost/AppLoader/NetHost.cs create mode 100644 host/src/FunctionsCustomHost/AppLoader/WorkerLoadStatusSignalManager.cs create mode 100644 host/src/FunctionsCustomHost/Configuration/Configuration.cs create mode 100644 host/src/FunctionsCustomHost/Environment/EnvironmentUtils.cs create mode 100644 host/src/FunctionsCustomHost/Environment/EnvironmentVariables.cs create mode 100644 host/src/FunctionsCustomHost/FunctionsCustomHost.csproj create mode 100644 host/src/FunctionsCustomHost/Logger.cs create mode 100644 host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs create mode 100644 host/src/FunctionsCustomHost/Program.cs create mode 100644 host/src/FunctionsCustomHost/Properties/launchSettings.json create mode 100644 host/src/FunctionsCustomHost/exports.def create mode 100644 host/src/FunctionsCustomHost/global.json diff --git a/host/src/FunctionsCustomHost/AppLoader/AppLoader.cs b/host/src/FunctionsCustomHost/AppLoader/AppLoader.cs new file mode 100644 index 000000000..d673079eb --- /dev/null +++ b/host/src/FunctionsCustomHost/AppLoader/AppLoader.cs @@ -0,0 +1,107 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace FunctionsNetHost +{ + // If having problems with the managed host, enable the following: + // Environment.SetEnvironmentVariable("COREHOST_TRACE", "1"); + // In Unix environment, you need to run the below command in the terminal to set the environment variable. + // export COREHOST_TRACE=1 + + /// + /// Manages loading hostfxr & worker assembly. + /// + internal sealed class AppLoader : IDisposable + { + private IntPtr _hostfxrHandle = IntPtr.Zero; + private IntPtr _hostContextHandle = IntPtr.Zero; + private bool _disposed; + + internal AppLoader() + { + } + + internal int RunApplication(string? assemblyPath) + { + ArgumentNullException.ThrowIfNull(assemblyPath, nameof(assemblyPath)); + + unsafe + { + var parameters = new NetHost.get_hostfxr_parameters + { + size = sizeof(NetHost.get_hostfxr_parameters), + assembly_path = GetCharArrayPointer(assemblyPath) + }; + + var hostfxrFullPath = NetHost.GetHostFxrPath(¶meters); + Logger.LogTrace($"hostfxr path:{hostfxrFullPath}"); + + _hostfxrHandle = NativeLibrary.Load(hostfxrFullPath); + + if (_hostfxrHandle == IntPtr.Zero) + { + Logger.Log($"Failed to load hostfxr. hostfxrFullPath:{hostfxrFullPath}"); + return -1; + } + + Logger.LogTrace($"hostfxr loaded."); + + if (_hostContextHandle == IntPtr.Zero) + { + Logger.Log($"Failed to initialize the .NET Core runtime. Assembly path:{assemblyPath}"); + return -1; + } + + Logger.LogTrace($"hostfxr initialized with {assemblyPath}"); + HostFxr.SetAppContextData(_hostContextHandle, "AZURE_FUNCTIONS_NATIVE_HOST", "1"); + + return HostFxr.Run(_hostContextHandle); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (!disposing) + { + return; + } + + if (_hostfxrHandle != IntPtr.Zero) + { + NativeLibrary.Free(_hostfxrHandle); + Logger.LogTrace($"Freed hostfxr library handle"); + _hostfxrHandle = IntPtr.Zero; + } + + if (_hostContextHandle != IntPtr.Zero) + { + HostFxr.Close(_hostContextHandle); + Logger.LogTrace($"Closed hostcontext handle"); + _hostContextHandle = IntPtr.Zero; + } + + _disposed = true; + } + } + + private static unsafe char* GetCharArrayPointer(string assemblyPath) + { +#if OS_LINUX + return (char*)Marshal.StringToHGlobalAnsi(assemblyPath).ToPointer(); +#else + return (char*)Marshal.StringToHGlobalUni(assemblyPath).ToPointer(); +#endif + } + } +} diff --git a/host/src/FunctionsCustomHost/AppLoader/HostFxr.cs b/host/src/FunctionsCustomHost/AppLoader/HostFxr.cs new file mode 100644 index 000000000..7e0e7709b --- /dev/null +++ b/host/src/FunctionsCustomHost/AppLoader/HostFxr.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Runtime.InteropServices; + +namespace FunctionsNetHost +{ + static partial class HostFxr + { + public unsafe struct hostfxr_initialize_parameters + { + public nint size; + public char* host_path; + public char* dotnet_root; + }; + + [LibraryImport("hostfxr", EntryPoint = "hostfxr_initialize_for_dotnet_command_line", +#if OS_LINUX + StringMarshalling = StringMarshalling.Utf8 +#else + StringMarshalling = StringMarshalling.Utf16 +#endif + )] + public unsafe static partial int Initialize( + int argc, + string[] argv, + IntPtr parameters, + out IntPtr host_context_handle + ); + + [LibraryImport("hostfxr", EntryPoint = "hostfxr_run_app")] + public static partial int Run(IntPtr host_context_handle); + + [LibraryImport("hostfxr", EntryPoint = "hostfxr_set_runtime_property_value", +#if OS_LINUX + StringMarshalling = StringMarshalling.Utf8 +#else + StringMarshalling = StringMarshalling.Utf16 +#endif + )] + public static partial int SetAppContextData(IntPtr host_context_handle, string name, string value); + + [LibraryImport("hostfxr", EntryPoint = "hostfxr_close")] + public static partial int Close(IntPtr host_context_handle); + } +} diff --git a/host/src/FunctionsCustomHost/AppLoader/NetHost.cs b/host/src/FunctionsCustomHost/AppLoader/NetHost.cs new file mode 100644 index 000000000..3a7d70207 --- /dev/null +++ b/host/src/FunctionsCustomHost/AppLoader/NetHost.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Runtime.InteropServices; + +namespace FunctionsNetHost +{ + internal class NetHost + { + public unsafe struct get_hostfxr_parameters + { + public nint size; + + // Optional.Path to the application assembly, + // If specified, hostfxr is located from this directory if present (For self-contained deployments) + public char* assembly_path; + public char* dotnet_root; + } + + [DllImport("nethost", CharSet = CharSet.Auto)] + private unsafe static extern int get_hostfxr_path( + [Out] char[] buffer, + [In] ref int buffer_size, + get_hostfxr_parameters* parameters); + + internal unsafe static string GetHostFxrPath(get_hostfxr_parameters* parameters) + { + char[] buffer = new char[200]; + int bufferSize = buffer.Length; + + int rc = get_hostfxr_path(buffer, ref bufferSize, parameters); + + if (rc != 0) + { + throw new InvalidOperationException("Failed to get the hostfxr path."); + } + + return new string(buffer, 0, bufferSize - 1); + } + } +} diff --git a/host/src/FunctionsCustomHost/AppLoader/WorkerLoadStatusSignalManager.cs b/host/src/FunctionsCustomHost/AppLoader/WorkerLoadStatusSignalManager.cs new file mode 100644 index 000000000..6bd3f9e51 --- /dev/null +++ b/host/src/FunctionsCustomHost/AppLoader/WorkerLoadStatusSignalManager.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace FunctionsNetHost; + +/// +/// Provides a signaling mechanism to wait and get notified about successful load of worker assembly. +/// +public class WorkerLoadStatusSignalManager +{ + private WorkerLoadStatusSignalManager() + { + Signal = new ManualResetEvent(false); + } + + public static WorkerLoadStatusSignalManager Instance { get; } = new(); + + public readonly ManualResetEvent Signal; +} diff --git a/host/src/FunctionsCustomHost/Configuration/Configuration.cs b/host/src/FunctionsCustomHost/Configuration/Configuration.cs new file mode 100644 index 000000000..dd0368c5d --- /dev/null +++ b/host/src/FunctionsCustomHost/Configuration/Configuration.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace FunctionsNetHost +{ + /// + /// Represents the application configuration. + /// + internal static class Configuration + { + private const string DefaultLogPrefix = "LanguageWorkerConsoleLog"; + + static Configuration() + { + Reload(); + } + + /// + /// Force the configuration values to be reloaded. + /// + internal static void Reload() + { + IsTraceLogEnabled = string.Equals(EnvironmentUtils.GetValue(EnvironmentVariables.EnableTraceLogs), "1"); + var disableLogPrefix = string.Equals(EnvironmentUtils.GetValue(EnvironmentVariables.DisableLogPrefix), "1"); + LogPrefix = disableLogPrefix ? string.Empty : DefaultLogPrefix; + } + + /// + /// Gets the log prefix for the log messages. + /// + internal static string? LogPrefix { get; private set; } + + /// + /// Gets a value indicating whether trace level logging is enabled. + /// + internal static bool IsTraceLogEnabled { get; private set; } + } +} diff --git a/host/src/FunctionsCustomHost/Environment/EnvironmentUtils.cs b/host/src/FunctionsCustomHost/Environment/EnvironmentUtils.cs new file mode 100644 index 000000000..024a99315 --- /dev/null +++ b/host/src/FunctionsCustomHost/Environment/EnvironmentUtils.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace FunctionsNetHost +{ + internal static class EnvironmentUtils + { +#if OS_LINUX + [System.Runtime.InteropServices.DllImport("libc")] + private static extern int setenv(string name, string value, int overwrite); +#endif + + /// + /// Gets the environment variable value. + /// + internal static string? GetValue(string environmentVariableName) + { + return Environment.GetEnvironmentVariable(environmentVariableName); + } + + /// + /// Sets the environment variable value. + /// + internal static void SetValue(string name, string value) + { + /* + * Environment.SetEnvironmentVariable is not setting the value of the parent process in Unix. + * So using the native method directly here. + * */ +#if OS_LINUX + setenv(name, value, 1); +#else + Environment.SetEnvironmentVariable(name, value); +#endif + } + } +} diff --git a/host/src/FunctionsCustomHost/Environment/EnvironmentVariables.cs b/host/src/FunctionsCustomHost/Environment/EnvironmentVariables.cs new file mode 100644 index 000000000..e35e49c05 --- /dev/null +++ b/host/src/FunctionsCustomHost/Environment/EnvironmentVariables.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace FunctionsNetHost; + +internal static class EnvironmentVariables +{ + /// + /// Set value to "1" will prevent the log entries to have the prefix "LanguageWorkerConsoleLog". + /// Set this to see logs when you are debugging FunctionsNetHost locally with WebHost. + /// + internal const string DisableLogPrefix = "AZURE_FUNCTIONS_FUNCTIONSNETHOST_DISABLE_LOGPREFIX"; + + /// + /// Set value to "1" for enabling additional trace logs in FunctionsNetHost. + /// + internal const string EnableTraceLogs = "AZURE_FUNCTIONS_FUNCTIONSNETHOST_TRACE"; + + /// + /// Application pool Id for the placeholder app. Only available in Windows(when running in IIS). + /// + internal const string AppPoolId = "APP_POOL_ID"; + + /// + /// The worker runtime version. Example value: "8.0" (for a .NET8 placeholder) + /// + internal const string FunctionsWorkerRuntimeVersion = "FUNCTIONS_WORKER_RUNTIME_VERSION"; + + internal const string FunctionsWorkerRuntime = "FUNCTIONS_WORKER_RUNTIME"; + + internal const string FunctionsInProcNet8Enabled = "FUNCTIONS_INPROC_NET8_ENABLED"; +} diff --git a/host/src/FunctionsCustomHost/FunctionsCustomHost.csproj b/host/src/FunctionsCustomHost/FunctionsCustomHost.csproj new file mode 100644 index 000000000..1f8eb97dc --- /dev/null +++ b/host/src/FunctionsCustomHost/FunctionsCustomHost.csproj @@ -0,0 +1,40 @@ + + + + Exe + net8.0 + enable + enable + True + true + Speed + true + + + + OS_LINUX + + + + $(MSBuildThisFileDirectory)exports.def + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/host/src/FunctionsCustomHost/Logger.cs b/host/src/FunctionsCustomHost/Logger.cs new file mode 100644 index 000000000..0d9eb25c5 --- /dev/null +++ b/host/src/FunctionsCustomHost/Logger.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Globalization; + +namespace FunctionsNetHost +{ + internal static class Logger + { + /// + /// Logs a trace message if trace level logging is enabled. + /// + internal static void LogTrace(string message) + { + if (Configuration.IsTraceLogEnabled) + { + Log(message); + } + } + + internal static void Log(string message) + { + var ts = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); + Console.WriteLine($"{Configuration.LogPrefix}[{ts}] [FunctionsNetHost] {message}"); + } + } +} diff --git a/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs b/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs new file mode 100644 index 000000000..51001f067 --- /dev/null +++ b/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics; + +namespace FunctionsNetHost.Prelaunch +{ + internal static class PreLauncher + { + private const string DotNet = "dotnet"; + private const string AssemblyName = "App.dll"; + private const string PrelaunchAppsDirName = "prelaunchapps"; + private const int ProcessWaitForExitTimeInMilliSeconds = 10000; + + /// + /// Attempts to start a minimal .NET application to preload/warmup the .NET runtime bits. + /// + internal static void Run() + { + Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsWorkerRuntimeVersion, "8.0"); + Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsWorkerRuntime, "dotnet"); + Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsInProcNet8Enabled, "1"); + + + var runtimeVersion = EnvironmentUtils.GetValue(EnvironmentVariables.FunctionsWorkerRuntimeVersion)!; + if (string.IsNullOrEmpty(runtimeVersion)) + { + Logger.Log($"Environment variable '{EnvironmentVariables.FunctionsWorkerRuntimeVersion}' is not set."); + return; + } + + string appAssemblyPath = string.Empty; + try + { + appAssemblyPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, PrelaunchAppsDirName, runtimeVersion, AssemblyName)); + + if (!File.Exists(appAssemblyPath)) + { + Logger.Log($"File not found: {appAssemblyPath}"); + return; + } + + var process = Process.Start(new ProcessStartInfo + { + FileName = DotNet, + Arguments = $"\"{appAssemblyPath}\"", + UseShellExecute = false + }); + + if (process == null) + { + Logger.Log($"Failed to start process: {appAssemblyPath}"); + return; + } + + Logger.Log($"Started process: {appAssemblyPath}, PID: {process.Id}"); + + process.EnableRaisingEvents = true; + process.Exited += (sender, e) => Logger.Log($"Process exited: {appAssemblyPath}, PID: {process.Id}"); + + if (!process.WaitForExit(ProcessWaitForExitTimeInMilliSeconds)) + { + try + { + process.Kill(); + Logger.Log( + $"Process was still running after 10 seconds and was killed: {appAssemblyPath}, PID: {process.Id}"); + } + catch (Exception ex) + { + Logger.Log($"Failed to kill process: {appAssemblyPath}, PID: {process.Id}. {ex}"); + } + } + } + catch (Exception ex) + { + Logger.Log($"FunctionsNetHost.Prelauncher. Failed to load: {appAssemblyPath}. {ex}"); + } + } + } +} diff --git a/host/src/FunctionsCustomHost/Program.cs b/host/src/FunctionsCustomHost/Program.cs new file mode 100644 index 000000000..515d050db --- /dev/null +++ b/host/src/FunctionsCustomHost/Program.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Transactions; +using FunctionsNetHost.Prelaunch; +using Microsoft.Extensions.Logging; +using Microsoft.VisualBasic; +using Newtonsoft.Json.Linq; + +namespace FunctionsNetHost +{ + internal class Program + { + private const string WindowsExecutableName = "func.exe"; + private const string LinuxExecutableName = "func"; + private const string InProc8DirectoryName = "in-proc8"; + private const string InProc6DirectoryName = "in-proc6"; + static async Task Main(string[] args) + { + try + { + Logger.Log("Starting FunctionsNetHost"); + + PreLauncher.Run(); + + using var appLoader = new AppLoader(); + + var workerRuntime = EnvironmentUtils.GetValue(EnvironmentVariables.FunctionsWorkerRuntime)!; + if (string.IsNullOrEmpty(workerRuntime)) + { + Logger.Log($"Environment variable '{EnvironmentVariables.FunctionsWorkerRuntime}' is not set."); + return; + } + if (workerRuntime == "dotnet") + { + // Start child process for .NET 8 in proc host + if (string.Equals("1", EnvironmentUtils.GetValue(EnvironmentVariables.FunctionsInProcNet8Enabled))) + { + await StartHostAsChildProcessAsync(true); + } + else + { + // Start child process for .NET 6 in proc host + await StartHostAsChildProcessAsync(false); + } + + } + else if (workerRuntime == "dotnet-isolated") + { + // Start process for oop host + } + } + catch (Exception exception) + { + Logger.Log($"An error occurred while running FunctionsNetHost.{exception}"); + } + } + + public static Task StartHostAsChildProcessAsync(bool shouldLaunchNet8Process) + { + var commandLineArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); + var tcs = new TaskCompletionSource(); + + var rootDirectory = GetFunctionAppRootDirectory(Environment.CurrentDirectory, new[] { "Azure.Functions.Cli" }); + var coreToolsDirectory = Path.Combine(rootDirectory, "Azure.Functions.Cli"); + + var executableName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? WindowsExecutableName : LinuxExecutableName; + + var fileName = shouldLaunchNet8Process ? Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName): Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName); + + var childProcessInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = $"{commandLineArguments} --no-build", + WorkingDirectory = Environment.CurrentDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + try + { + var childProcess = Process.Start(childProcessInfo); + + childProcess.EnableRaisingEvents = true; + childProcess.Exited += (sender, args) => + { + tcs.SetResult(); + }; + childProcess.BeginOutputReadLine(); + childProcess.BeginErrorReadLine(); + // Need to add process manager as well?? + + childProcess.WaitForExit(); + } + catch (Exception ex) + { + throw new Exception($"Failed to start the {(shouldLaunchNet8Process ? "inproc8" : "inproc6")} model host. {ex.Message}"); + } + + return tcs.Task; + } + + public static string GetFunctionAppRootDirectory(string startingDirectory, IEnumerable searchDirectories) + { + if (searchDirectories.Any(file => Directory.Exists(Path.Combine(startingDirectory, file)))) + { + return startingDirectory; + } + + var parent = Path.GetDirectoryName(startingDirectory); + + if (parent == null) + { + var files = searchDirectories.Aggregate((accum, file) => $"{accum}, {file}"); + throw new ($"Unable to find project root. Expecting to find one of {files} in project root."); + } + else + { + return GetFunctionAppRootDirectory(parent, searchDirectories); + } + } + } +} diff --git a/host/src/FunctionsCustomHost/Properties/launchSettings.json b/host/src/FunctionsCustomHost/Properties/launchSettings.json new file mode 100644 index 000000000..c6b3919b9 --- /dev/null +++ b/host/src/FunctionsCustomHost/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "FunctionsNetHost": { + "commandName": "Project", + "commandLineArgs": "FunctionsNetHost.exe --host 127.0.0.1 --port 503 --workerId 13a2c943-ee61-449b-97ea-7c2577cbb1db --requestId 78522dbc-3bef-4ced-8988-bb3761c94e00 --grpcMaxMessageLength 2147483647" + } + } +} \ No newline at end of file diff --git a/host/src/FunctionsCustomHost/exports.def b/host/src/FunctionsCustomHost/exports.def new file mode 100644 index 000000000..b350d24ca --- /dev/null +++ b/host/src/FunctionsCustomHost/exports.def @@ -0,0 +1,4 @@ +EXPORTS + get_application_properties + register_callbacks + send_streaming_message diff --git a/host/src/FunctionsCustomHost/global.json b/host/src/FunctionsCustomHost/global.json new file mode 100644 index 000000000..989a69caf --- /dev/null +++ b/host/src/FunctionsCustomHost/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestMinor" + } +} \ No newline at end of file From 3800bab2b401fa090b8b118f8b1681f52a8ba952 Mon Sep 17 00:00:00 2001 From: Aishwarya Bhandari Date: Tue, 17 Sep 2024 16:31:48 -0700 Subject: [PATCH 2/3] updating to load assembly --- host/src/FunctionsCustomHost/Program.cs | 45 ++++++------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/host/src/FunctionsCustomHost/Program.cs b/host/src/FunctionsCustomHost/Program.cs index 515d050db..588550fb0 100644 --- a/host/src/FunctionsCustomHost/Program.cs +++ b/host/src/FunctionsCustomHost/Program.cs @@ -40,18 +40,19 @@ static async Task Main(string[] args) // Start child process for .NET 8 in proc host if (string.Equals("1", EnvironmentUtils.GetValue(EnvironmentVariables.FunctionsInProcNet8Enabled))) { - await StartHostAsChildProcessAsync(true); + await LoadHostAssembly(isOutOfProc: false, isNet8InProc: true); } else { // Start child process for .NET 6 in proc host - await StartHostAsChildProcessAsync(false); + await LoadHostAssembly(isOutOfProc: false, isNet8InProc: false); } } else if (workerRuntime == "dotnet-isolated") { // Start process for oop host + await LoadHostAssembly(isOutOfProc: true, isNet8InProc: false); } } catch (Exception exception) @@ -60,7 +61,7 @@ static async Task Main(string[] args) } } - public static Task StartHostAsChildProcessAsync(bool shouldLaunchNet8Process) + private static Task LoadHostAssembly(bool isOutOfProc, bool isNet8InProc) { var commandLineArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); var tcs = new TaskCompletionSource(); @@ -68,45 +69,21 @@ public static Task StartHostAsChildProcessAsync(bool shouldLaunchNet8Process) var rootDirectory = GetFunctionAppRootDirectory(Environment.CurrentDirectory, new[] { "Azure.Functions.Cli" }); var coreToolsDirectory = Path.Combine(rootDirectory, "Azure.Functions.Cli"); - var executableName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? WindowsExecutableName : LinuxExecutableName; + var executableName = LinuxExecutableName; - var fileName = shouldLaunchNet8Process ? Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName): Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName); + var fileName = isNet8InProc ? Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName): Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName); - var childProcessInfo = new ProcessStartInfo + if (isOutOfProc) { - FileName = fileName, - Arguments = $"{commandLineArguments} --no-build", - WorkingDirectory = Environment.CurrentDirectory, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - - try - { - var childProcess = Process.Start(childProcessInfo); - - childProcess.EnableRaisingEvents = true; - childProcess.Exited += (sender, args) => - { - tcs.SetResult(); - }; - childProcess.BeginOutputReadLine(); - childProcess.BeginErrorReadLine(); - // Need to add process manager as well?? - - childProcess.WaitForExit(); - } - catch (Exception ex) - { - throw new Exception($"Failed to start the {(shouldLaunchNet8Process ? "inproc8" : "inproc6")} model host. {ex.Message}"); + fileName = Path.Combine(coreToolsDirectory, executableName); } + Assembly assembly = Assembly.LoadFrom(fileName); + Logger.Log("Loaded Assembly: " + assembly.FullName); return tcs.Task; } - public static string GetFunctionAppRootDirectory(string startingDirectory, IEnumerable searchDirectories) + private static string GetFunctionAppRootDirectory(string startingDirectory, IEnumerable searchDirectories) { if (searchDirectories.Any(file => Directory.Exists(Path.Combine(startingDirectory, file)))) { From 9caa9ca6ba0767ed1528542d9e56783e9110df1f Mon Sep 17 00:00:00 2001 From: Aishwarya Bhandari Date: Tue, 17 Sep 2024 16:59:03 -0700 Subject: [PATCH 3/3] adding apploader --- .../Prelaunch/Prelauncher.cs | 3 +- host/src/FunctionsCustomHost/Program.cs | 36 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs b/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs index 51001f067..f5988a31b 100644 --- a/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs +++ b/host/src/FunctionsCustomHost/Prelaunch/Prelauncher.cs @@ -17,8 +17,9 @@ internal static class PreLauncher /// internal static void Run() { + // Adding this here for testing; remove later Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsWorkerRuntimeVersion, "8.0"); - Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsWorkerRuntime, "dotnet"); + Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsWorkerRuntime, "dotnet-isolated"); Environment.SetEnvironmentVariable(EnvironmentVariables.FunctionsInProcNet8Enabled, "1"); diff --git a/host/src/FunctionsCustomHost/Program.cs b/host/src/FunctionsCustomHost/Program.cs index 588550fb0..2dcd3fcff 100644 --- a/host/src/FunctionsCustomHost/Program.cs +++ b/host/src/FunctionsCustomHost/Program.cs @@ -1,24 +1,17 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Diagnostics; using System.Reflection; -using System.Runtime.InteropServices; -using System.Transactions; using FunctionsNetHost.Prelaunch; -using Microsoft.Extensions.Logging; -using Microsoft.VisualBasic; -using Newtonsoft.Json.Linq; namespace FunctionsNetHost { internal class Program { - private const string WindowsExecutableName = "func.exe"; - private const string LinuxExecutableName = "func"; + private const string ExecutableName = "func.dll"; private const string InProc8DirectoryName = "in-proc8"; private const string InProc6DirectoryName = "in-proc6"; + static async Task Main(string[] args) { try @@ -37,22 +30,22 @@ static async Task Main(string[] args) } if (workerRuntime == "dotnet") { - // Start child process for .NET 8 in proc host + // Load host assembly for .NET 8 in proc host if (string.Equals("1", EnvironmentUtils.GetValue(EnvironmentVariables.FunctionsInProcNet8Enabled))) { - await LoadHostAssembly(isOutOfProc: false, isNet8InProc: true); + await LoadHostAssembly(appLoader, isOutOfProc: false, isNet8InProc: true); } else { - // Start child process for .NET 6 in proc host - await LoadHostAssembly(isOutOfProc: false, isNet8InProc: false); + // Load host assembly for .NET 6 in proc host + await LoadHostAssembly(appLoader, isOutOfProc: false, isNet8InProc: false); } } else if (workerRuntime == "dotnet-isolated") { // Start process for oop host - await LoadHostAssembly(isOutOfProc: true, isNet8InProc: false); + await LoadHostAssembly(appLoader, isOutOfProc: true, isNet8InProc: false); } } catch (Exception exception) @@ -61,7 +54,7 @@ static async Task Main(string[] args) } } - private static Task LoadHostAssembly(bool isOutOfProc, bool isNet8InProc) + private static Task LoadHostAssembly(AppLoader appLoader, bool isOutOfProc, bool isNet8InProc) { var commandLineArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); var tcs = new TaskCompletionSource(); @@ -69,16 +62,21 @@ private static Task LoadHostAssembly(bool isOutOfProc, bool isNet8InProc) var rootDirectory = GetFunctionAppRootDirectory(Environment.CurrentDirectory, new[] { "Azure.Functions.Cli" }); var coreToolsDirectory = Path.Combine(rootDirectory, "Azure.Functions.Cli"); - var executableName = LinuxExecutableName; + var executableName = ExecutableName; - var fileName = isNet8InProc ? Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName): Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName); + string fileName = ""; if (isOutOfProc) { fileName = Path.Combine(coreToolsDirectory, executableName); } - Assembly assembly = Assembly.LoadFrom(fileName); - Logger.Log("Loaded Assembly: " + assembly.FullName); + else + { + fileName = isNet8InProc ? Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName) : Path.Combine(coreToolsDirectory, InProc8DirectoryName, executableName); + + } + + appLoader.RunApplication(fileName); return tcs.Task; }