Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Host Changes to support OOP Host #2719

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions host/src/FunctionsCustomHost/AppLoader/AppLoader.cs
Original file line number Diff line number Diff line change
@@ -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

/// <summary>
/// Manages loading hostfxr & worker assembly.
/// </summary>
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(&parameters);
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
}
}
}
46 changes: 46 additions & 0 deletions host/src/FunctionsCustomHost/AppLoader/HostFxr.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
41 changes: 41 additions & 0 deletions host/src/FunctionsCustomHost/AppLoader/NetHost.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Provides a signaling mechanism to wait and get notified about successful load of worker assembly.
/// </summary>
public class WorkerLoadStatusSignalManager
{
private WorkerLoadStatusSignalManager()
{
Signal = new ManualResetEvent(false);
}

public static WorkerLoadStatusSignalManager Instance { get; } = new();

public readonly ManualResetEvent Signal;
}
38 changes: 38 additions & 0 deletions host/src/FunctionsCustomHost/Configuration/Configuration.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents the application configuration.
/// </summary>
internal static class Configuration
{
private const string DefaultLogPrefix = "LanguageWorkerConsoleLog";

static Configuration()
{
Reload();
}

/// <summary>
/// Force the configuration values to be reloaded.
/// </summary>
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;
}

/// <summary>
/// Gets the log prefix for the log messages.
/// </summary>
internal static string? LogPrefix { get; private set; }

/// <summary>
/// Gets a value indicating whether trace level logging is enabled.
/// </summary>
internal static bool IsTraceLogEnabled { get; private set; }
}
}
37 changes: 37 additions & 0 deletions host/src/FunctionsCustomHost/Environment/EnvironmentUtils.cs
Original file line number Diff line number Diff line change
@@ -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

/// <summary>
/// Gets the environment variable value.
/// </summary>
internal static string? GetValue(string environmentVariableName)
{
return Environment.GetEnvironmentVariable(environmentVariableName);
}

/// <summary>
/// Sets the environment variable value.
/// </summary>
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
}
}
}
32 changes: 32 additions & 0 deletions host/src/FunctionsCustomHost/Environment/EnvironmentVariables.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
internal const string DisableLogPrefix = "AZURE_FUNCTIONS_FUNCTIONSNETHOST_DISABLE_LOGPREFIX";

/// <summary>
/// Set value to "1" for enabling additional trace logs in FunctionsNetHost.
/// </summary>
internal const string EnableTraceLogs = "AZURE_FUNCTIONS_FUNCTIONSNETHOST_TRACE";

/// <summary>
/// Application pool Id for the placeholder app. Only available in Windows(when running in IIS).
/// </summary>
internal const string AppPoolId = "APP_POOL_ID";

/// <summary>
/// The worker runtime version. Example value: "8.0" (for a .NET8 placeholder)
/// </summary>
internal const string FunctionsWorkerRuntimeVersion = "FUNCTIONS_WORKER_RUNTIME_VERSION";

internal const string FunctionsWorkerRuntime = "FUNCTIONS_WORKER_RUNTIME";

internal const string FunctionsInProcNet8Enabled = "FUNCTIONS_INPROC_NET8_ENABLED";
}
40 changes: 40 additions & 0 deletions host/src/FunctionsCustomHost/FunctionsCustomHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
<OptimizationPreference>Speed</OptimizationPreference>
<IlcExportUnmanagedEntrypoints>true</IlcExportUnmanagedEntrypoints>
</PropertyGroup>

<PropertyGroup>
<DefineConstants Condition=" '$([MSBuild]::IsOSPlatform(`Linux`))' == 'true' ">OS_LINUX</DefineConstants>
</PropertyGroup>

<PropertyGroup>
<ExportsFile>$(MSBuildThisFileDirectory)exports.def</ExportsFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.2" />
<PackageReference Include="Grpc.Net.Client" Version="2.53.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.53.0" />
<PackageReference Include="Grpc.Tools" Version="2.54.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.Text.Json" Version="8.0.0-preview.4.23259.5" />
<PackageReference Include="Microsoft.NETCore.DotNetAppHost" Version="8.0.0-preview.4.23259.5" />
</ItemGroup>

<ItemGroup>
<Protobuf Include="..\..\..\protos\azure-functions-language-worker-protobuf\**\*.proto" ProtoRoot="..\..\..\protos\azure-functions-language-worker-protobuf\src\proto" GrpcServices="Client" Access="internal" />
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions host/src/FunctionsCustomHost/Logger.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Logs a trace message if trace level logging is enabled.
/// </summary>
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}");
}
}
}
Loading
Loading