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

Prejit support on FunctionsNetHost #2711

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions eng/ci/host/official-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extends:

jobs:
- template: /eng/ci/templates/official/jobs/build-host-prelaunch-artifacts.yml@self
- template: /eng/ci/templates/official/jobs/build-host-prejit-artifacts.yml@self
- template: /eng/ci/templates/official/jobs/build-host-artifacts-linux.yml@self
parameters:
PoolName: 1es-pool-azfunc
Expand Down
1 change: 1 addition & 0 deletions eng/ci/host/public-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ extends:

jobs:
- template: /eng/ci/templates/official/jobs/build-host-prelaunch-artifacts.yml@self
- template: /eng/ci/templates/official/jobs/build-host-prejit-artifacts.yml@self
- template: /eng/ci/templates/official/jobs/build-host-artifacts-linux.yml@self
parameters:
PoolName: 1es-pool-azfunc-public
Expand Down
30 changes: 30 additions & 0 deletions eng/ci/templates/official/jobs/build-host-prejit-artifacts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
jobs:

- job: BuildPreJitApp
displayName: Build Pre-jit placeholder app artifacts

templateContext:
outputParentDirectory: $(Build.ArtifactStagingDirectory)
outputs:
- output: pipelineArtifact
displayName: Publish Pre-jit placeholder app artifacts
path: $(Build.ArtifactStagingDirectory)/_preJitAppPackages
artifact: _preJitAppPackages

variables:
dotnetVersions: 'net8.0,net9.0'

steps:
- template: /eng/ci/templates/steps/install-dotnet.yml@self

- ${{ each version in split(variables.dotnetVersions, ',') }}:
- task: DotNetCoreCLI@2
displayName: ${{ version }} publish of pre-jit app
inputs:
command: publish
publishWebProjects: false
zipAfterPublish: false
modifyOutputPath: false
arguments: -c Release -o $(Build.ArtifactStagingDirectory)/_preJitAppPackages/${{ replace(version, 'net', '') }} -f ${{ version }} -p:UseAppHost=false -p:DebugType=None -p:DebugSymbols=false
projects: host/src/PlaceholderApp/FunctionsNetHost.PlaceholderApp.csproj
workingDirectory: host/src
8 changes: 7 additions & 1 deletion eng/ci/templates/official/jobs/pack-host-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ jobs:
displayName: Download prelaunch artifacts
inputs:
artifactName: _preLaunchAppPackages
path: $(Build.SourcesDirectory)/host/dist/portable
path: $(Build.SourcesDirectory)/host/dist/portable/prelaunchapps

- task: DownloadPipelineArtifact@2
displayName: Download pre-jit artifacts
inputs:
artifactName: _preJitAppPackages
path: $(Build.SourcesDirectory)/host/dist/portable/prejit-placeholder-app

- task: DownloadPipelineArtifact@2
displayName: Download host artifacts - linux
Expand Down
13 changes: 13 additions & 0 deletions host/src/FunctionsNetHost.Shared/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace FunctionsNetHost.Shared
{
public static class Constants
{
public const string LogCategory = "FunctionsNetHost";
public const string DefaultLogPrefix = "LanguageWorkerConsoleLog";
public const string NetHostWaitHandleName = "AzureFunctionsNetHostSpecializationWaitHandle";
public const string LogTimeStampFormat = "yyyy-MM-dd HH:mm:ss.fff";
}
}
23 changes: 23 additions & 0 deletions host/src/FunctionsNetHost.Shared/EnvironmentVariables.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace FunctionsNetHost.Shared
{
public static class EnvironmentVariables
{
/// <summary>
/// The environment variable which is used to specify the specialized (function app payload) entry assembly.
/// </summary>
public const string SpecializedEntryAssembly = "AZURE_FUNCTIONS_NETHOST_SPECIALIZED_ENTRY_ASSEMBLY";

/// <summary>
/// The environment variable which is used to specify the path to the jittrace file which will be used for prejitting.
/// </summary>
public const string PreJitFilePath = "AZURE_FUNCTIONS_NETHOST_PREJIT_FILE_PATH";

/// <summary>
/// The .NET startup hooks environment variable.
/// </summary>
public const string DotnetStartupHooks = "DOTNET_STARTUP_HOOKS";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>
6 changes: 6 additions & 0 deletions host/src/FunctionsNetHost.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionsNetHost", "FunctionsNetHost\FunctionsNetHost.csproj", "{6C05D0AC-F6AC-45FB-8A73-A3F44DF131BC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionsNetHost.Shared", "FunctionsNetHost.Shared\FunctionsNetHost.Shared.csproj", "{91867A34-8B79-4E01-81BC-592F1CD1CCCE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +17,10 @@ Global
{6C05D0AC-F6AC-45FB-8A73-A3F44DF131BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C05D0AC-F6AC-45FB-8A73-A3F44DF131BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C05D0AC-F6AC-45FB-8A73-A3F44DF131BC}.Release|Any CPU.Build.0 = Release|Any CPU
{91867A34-8B79-4E01-81BC-592F1CD1CCCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91867A34-8B79-4E01-81BC-592F1CD1CCCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91867A34-8B79-4E01-81BC-592F1CD1CCCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91867A34-8B79-4E01-81BC-592F1CD1CCCE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
4 changes: 1 addition & 3 deletions host/src/FunctionsNetHost/Configuration/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ namespace FunctionsNetHost
/// </summary>
internal static class Configuration
{
private const string DefaultLogPrefix = "LanguageWorkerConsoleLog";

static Configuration()
{
Reload();
Expand All @@ -22,7 +20,7 @@ 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;
LogPrefix = disableLogPrefix ? string.Empty : Shared.Constants.DefaultLogPrefix;
}

/// <summary>
Expand Down
11 changes: 8 additions & 3 deletions host/src/FunctionsNetHost/Environment/EnvironmentVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ 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.
/// </summary>
internal const string DisableLogPrefix = "AZURE_FUNCTIONS_FUNCTIONSNETHOST_DISABLE_LOGPREFIX";
internal const string DisableLogPrefix = "AZURE_FUNCTIONS_NETHOST_DISABLE_LOGPREFIX";

/// <summary>
/// Set value to "1" for enabling additional trace logs in FunctionsNetHost.
/// </summary>
internal const string EnableTraceLogs = "AZURE_FUNCTIONS_FUNCTIONSNETHOST_TRACE";
internal const string EnableTraceLogs = "AZURE_FUNCTIONS_NETHOST_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";
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";

/// <summary>
/// The environment variable that disables prejit. If set to "1," prejit will be disabled.
/// </summary>
internal const string DisablePrejit = "AZURE_FUNCTIONS_NETHOST_DISABLE_PREJIT";
}
12 changes: 9 additions & 3 deletions host/src/FunctionsNetHost/FunctionsNetHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<EnablePreviewFeatures>True</EnablePreviewFeatures>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
Expand All @@ -28,12 +30,16 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<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" />
<PackageReference Include="System.Text.Json" Version="9.0.0-preview.6.24327.7" />
<PackageReference Include="Microsoft.NETCore.DotNetAppHost" Version="9.0.0-preview.6.24327.7" />
</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>

<ItemGroup>
<ProjectReference Include="..\FunctionsNetHost.Shared\FunctionsNetHost.Shared.csproj" />
</ItemGroup>

</Project>
16 changes: 8 additions & 8 deletions host/src/FunctionsNetHost/Grpc/GrpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ internal sealed class GrpcClient
{
private readonly Channel<StreamingMessage> _outgoingMessageChannel;
private readonly IncomingGrpcMessageHandler _messageHandler;
private readonly GrpcWorkerStartupOptions _grpcWorkerStartupOptions;
private readonly NetHostRunOptions _netHostRunOptions;

internal GrpcClient(GrpcWorkerStartupOptions grpcWorkerStartupOptions, AppLoader appLoader)
internal GrpcClient(NetHostRunOptions netHostRunOptions, AppLoader appLoader)
{
_grpcWorkerStartupOptions = grpcWorkerStartupOptions;
_netHostRunOptions = netHostRunOptions;
var channelOptions = new UnboundedChannelOptions
{
SingleWriter = false,
Expand All @@ -28,12 +28,12 @@ internal GrpcClient(GrpcWorkerStartupOptions grpcWorkerStartupOptions, AppLoader

_outgoingMessageChannel = Channel.CreateUnbounded<StreamingMessage>(channelOptions);

_messageHandler = new IncomingGrpcMessageHandler(appLoader, _grpcWorkerStartupOptions);
_messageHandler = new IncomingGrpcMessageHandler(appLoader, _netHostRunOptions);
}

internal async Task InitAsync()
{
var endpoint = _grpcWorkerStartupOptions.ServerUri.AbsoluteUri;
var endpoint = _netHostRunOptions.WorkerStartupOptions.ServerUri.AbsoluteUri;
Logger.LogTrace($"Grpc service endpoint:{endpoint}");

var functionRpcClient = CreateFunctionRpcClient(endpoint);
Expand Down Expand Up @@ -69,7 +69,7 @@ private async Task SendStartStreamMessageAsync(IClientStreamWriter<StreamingMess
{
var startStreamMsg = new StartStream()
{
WorkerId = _grpcWorkerStartupOptions.WorkerId
WorkerId = _netHostRunOptions.WorkerStartupOptions.WorkerId
};

var startStream = new StreamingMessage()
Expand All @@ -89,8 +89,8 @@ private FunctionRpcClient CreateFunctionRpcClient(string endpoint)

var grpcChannel = GrpcChannel.ForAddress(grpcUri, new GrpcChannelOptions()
{
MaxReceiveMessageSize = _grpcWorkerStartupOptions.GrpcMaxMessageLength,
MaxSendMessageSize = _grpcWorkerStartupOptions.GrpcMaxMessageLength,
MaxReceiveMessageSize = _netHostRunOptions.WorkerStartupOptions.GrpcMaxMessageLength,
MaxSendMessageSize = _netHostRunOptions.WorkerStartupOptions.GrpcMaxMessageLength,
Credentials = ChannelCredentials.Insecure
});

Expand Down
36 changes: 20 additions & 16 deletions host/src/FunctionsNetHost/Grpc/IncomingGrpcMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ internal sealed class IncomingGrpcMessageHandler
{
private bool _specializationDone;
private readonly AppLoader _appLoader;
private readonly GrpcWorkerStartupOptions _grpcWorkerStartupOptions;
private readonly NetHostRunOptions _netHostRunOptions;

internal IncomingGrpcMessageHandler(AppLoader appLoader, GrpcWorkerStartupOptions grpcWorkerStartupOptions)
internal IncomingGrpcMessageHandler(AppLoader appLoader, NetHostRunOptions netHostRunOptions)
{
_appLoader = appLoader;
_grpcWorkerStartupOptions = grpcWorkerStartupOptions;
_netHostRunOptions = netHostRunOptions;
}

internal Task ProcessMessageAsync(StreamingMessage message)
Expand Down Expand Up @@ -60,12 +60,15 @@ private async Task Process(StreamingMessage msg)
};
break;
}
case StreamingMessage.ContentOneofCase.FunctionEnvironmentReloadRequest:
case StreamingMessage.ContentOneofCase.FunctionEnvironmentReloadRequest:

Configuration.Reload();
Logger.LogTrace("Specialization request received.");

var envReloadRequest = msg.FunctionEnvironmentReloadRequest;
Logger.Log("Specialization request received.");
var envReloadRequest = msg.FunctionEnvironmentReloadRequest;
foreach (var kv in envReloadRequest.EnvironmentVariables)
{
EnvironmentUtils.SetValue(kv.Key, kv.Value);
}
Configuration.Reload();

var workerConfig = await WorkerConfigUtils.GetWorkerConfig(envReloadRequest.FunctionAppDirectory);

Expand All @@ -88,19 +91,20 @@ private async Task Process(StreamingMessage msg)
var applicationExePath = Path.Combine(envReloadRequest.FunctionAppDirectory, workerConfig.Description.DefaultWorkerPath!);
Logger.LogTrace($"application path {applicationExePath}");

foreach (var kv in envReloadRequest.EnvironmentVariables)
if (_netHostRunOptions.IsPreJitSupported)
{
EnvironmentUtils.SetValue(kv.Key, kv.Value);
EnvironmentUtils.SetValue(Shared.EnvironmentVariables.SpecializedEntryAssembly, applicationExePath);
// Signal so that startup hook load the payload assembly.
SpecializationSyncManager.WaitHandle.Set();
}

else
{
#pragma warning disable CS4014
Task.Run(() =>
Task.Run(() => _appLoader.RunApplication(applicationExePath));
#pragma warning restore CS4014
{
_ = _appLoader.RunApplication(applicationExePath);
});
}

Logger.LogTrace($"Will wait for worker loaded signal.");
Logger.LogTrace("Will wait for worker loaded signal.");
WorkerLoadStatusSignalManager.Instance.Signal.WaitOne();

var logMessage = $"FunctionApp assembly loaded successfully. ProcessId:{Environment.ProcessId}";
Expand Down
63 changes: 63 additions & 0 deletions host/src/FunctionsNetHost/Grpc/NetHostRunOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using FunctionsNetHost.Grpc;

namespace FunctionsNetHost
{
/// <summary>
/// Encapsulates various configuration options required to run the FunctionsNetHost application.
/// </summary>
internal sealed class NetHostRunOptions
{
//.NET 8.0 is the minimum version that supports pre-jitting.
private const int MinimumNetTfmToSupportPreJit = 8;

/// <summary>
/// Gets a value indicating whether pre-jitting is supported.
/// </summary>
public bool IsPreJitSupported { get; }

/// <summary>
/// Gets the worker startup options.
/// </summary>
public GrpcWorkerStartupOptions WorkerStartupOptions { get; }

/// <summary>
/// Gets the runtime version. This usually corresponds to the .NET runtime version.
/// Example value: 8.0.
/// </summary>
public string RuntimeVersion { get; }

/// <summary>
/// Gets the directory where the FunctionsNetHost executable is located.
/// </summary>
public string ExecutableDirectory { get; }

public NetHostRunOptions(GrpcWorkerStartupOptions workerStartupOptions, string executableDirectory)
{
WorkerStartupOptions = workerStartupOptions;
ExecutableDirectory = executableDirectory;
RuntimeVersion = EnvironmentUtils.GetValue(EnvironmentVariables.FunctionsWorkerRuntimeVersion)!;
IsPreJitSupported = IsPrejitSupported(RuntimeVersion);
}

private static bool IsPrejitSupported(string runtimeVersion)
{
if (string.IsNullOrEmpty(runtimeVersion))
{
return false;
}

var disablePrejitEnvironmentVaValue = EnvironmentUtils.GetValue(EnvironmentVariables.DisablePrejit);
if (string.Equals(disablePrejitEnvironmentVaValue, "1"))
{
Logger.Log($"PreJitting is disabled due to the environment variable '{EnvironmentVariables.DisablePrejit}' being set to '{disablePrejitEnvironmentVaValue}'.");
return false;
}

return decimal.TryParse(runtimeVersion, out var value) && value >= MinimumNetTfmToSupportPreJit;
}
}
}

2 changes: 1 addition & 1 deletion host/src/FunctionsNetHost/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal static void LogTrace(string message)

internal static void Log(string message)
{
var ts = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
var ts = DateTime.UtcNow.ToString(Shared.Constants.LogTimeStampFormat, CultureInfo.InvariantCulture);
Console.WriteLine($"{Configuration.LogPrefix}[{ts}] [FunctionsNetHost] {message}");
}
}
Expand Down
Loading
Loading