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 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider making this a variable

artifact: _preJitAppPackages

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

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

- ${{ each version in split(variables.dotnetVersions, ',') }}:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit: use framework terminology instead of version.

- 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we supposed to use DownloadPipelineArtifact? 1ES has its own syntax for artifact inputs:

https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-pipeline-templates/features/inputs/pipeline-artifact

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_FUNCTIONSNETHOST_SPECIALIZED_ENTRY_ASSEMBLY";
kshyju marked this conversation as resolved.
Show resolved Hide resolved

/// <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_FUNCTIONSNETHOST_PREJIT_FILE_PATH";
kshyju marked this conversation as resolved.
Show resolved Hide resolved

/// <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
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ internal static class EnvironmentVariables
/// <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_FUNCTIONSNETHOST_DISABLE_PREJIT";
kshyju marked this conversation as resolved.
Show resolved Hide resolved
}
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