diff --git a/build/common.props b/build/common.props
index bbf48b9159..afa45088b8 100644
--- a/build/common.props
+++ b/build/common.props
@@ -5,7 +5,7 @@
latest
4
27
- 4
+ 5
0
diff --git a/release_notes.md b/release_notes.md
index 60f1211c12..b39d1d8dd7 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -5,16 +5,11 @@
-->
- Update Python Worker Version to [4.20.0](https://github.com/Azure/azure-functions-python-worker/releases/tag/4.20.0)
- Update Java Worker Version to [2.13.0](https://github.com/Azure/azure-functions-java-worker/releases/tag/2.13.0)
-- Update WebJobsScriptHostService to remove hardcoded sleep during application shut down (#9520)
+- Increased maximum HTTP request content size to 210000000 Bytes (~200MB)
+- Update Node.js Worker Version to [3.8.1](https://github.com/Azure/azure-functions-nodejs-worker/releases/tag/v3.8.1)
+- Update PowerShell 7.4 Worker Version to [4.0.2975](https://github.com/Azure/azure-functions-powershell-worker/releases/tag/v4.0.2975)
- Update PowerShell 7.2 Worker Version to [4.0.2974](https://github.com/Azure/azure-functions-powershell-worker/releases/tag/v4.0.2974)
- Update PowerShell 7.0 Worker Version to [4.0.2973](https://github.com/Azure/azure-functions-powershell-worker/releases/tag/v4.0.2973)
-- Add support for standalone executable (ie: `dotnet build --standalone`) for out-of-proc workers in Linux Consumption. (#9550)
-- Bug fix: Do not restart worker channels or JobHost when an API request is made to get or update the function metadata (unless the config was changed) (#9510)
- - This fixes a bug where requests to 'admin/functions' lead to a "Did not find initialized workers" error when
- worker indexing is enabled.
-- Bug fix: If there are no channels created and the host is running, restart the JobHost instead of shutting down worker channels (#9510)
- - This fixes a bug with worker indexing where we are shutting down worker channels and creating a new channel that never
- gets properly initialized as the invocation buffers are not created - this leads to a "Did not find initialized workers" error.
-- Check if a blob container or table exists before trying to create it (#9555)
- Limit dotnet-isolated specialization to 64 bit host process (#9548)
- Sending command line arguments to language workers with `functions-` prefix to prevent conflicts (#9514)
+- Update WebJobsScriptHostService to remove hardcoded sleep during application shut down (#9520)
diff --git a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs
index f9a77c497e..9366a3b83e 100644
--- a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs
+++ b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs
@@ -374,12 +374,18 @@ internal void FunctionEnvironmentReloadResponse(FunctionEnvironmentReloadRespons
ApplyCapabilities(res.Capabilities, res.CapabilitiesUpdateStrategy.ToGrpcCapabilitiesUpdateStrategy());
- if (res.Result.IsFailure(out Exception reloadEnvironmentVariablesException))
+ if (res.Result.IsFailure(IsUserCodeExceptionCapabilityEnabled(), out var reloadEnvironmentVariablesException))
{
- _workerChannelLogger.LogError(reloadEnvironmentVariablesException, "Failed to reload environment variables");
- _reloadTask.SetException(reloadEnvironmentVariablesException);
+ if (res.Result.Exception is not null && reloadEnvironmentVariablesException is not null)
+ {
+ _workerChannelLogger.LogWarning(reloadEnvironmentVariablesException, reloadEnvironmentVariablesException.Message);
+ }
+ _reloadTask.SetResult(false);
+ }
+ else
+ {
+ _reloadTask.SetResult(true);
}
- _reloadTask.SetResult(true);
latencyEvent.Dispose();
}
@@ -414,6 +420,15 @@ internal void WorkerInitResponse(GrpcEvent initEvent)
_workerInitTask.TrySetResult(true);
}
+ private bool IsUserCodeExceptionCapabilityEnabled()
+ {
+ var enableUserCodeExceptionCapability = string.Equals(
+ _workerCapabilities.GetCapabilityState(RpcWorkerConstants.EnableUserCodeException), bool.TrueString,
+ StringComparison.OrdinalIgnoreCase);
+
+ return enableUserCodeExceptionCapability;
+ }
+
private void LogWorkerMetadata(WorkerMetadata workerMetadata)
{
if (workerMetadata == null)
@@ -546,7 +561,7 @@ internal FunctionLoadRequestCollection GetFunctionLoadRequestCollection(IEnumera
return functionLoadRequestCollection;
}
- public Task SendFunctionEnvironmentReloadRequest()
+ public Task SendFunctionEnvironmentReloadRequest()
{
_functionsIndexingTask = new TaskCompletionSource>(TaskCreationOptions.RunContinuationsAsynchronously);
_functionMetadataRequestSent = false;
@@ -1576,4 +1591,4 @@ private void OnTimeout()
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/WebJobs.Script.Grpc/MessageExtensions/StatusResultExtensions.cs b/src/WebJobs.Script.Grpc/MessageExtensions/StatusResultExtensions.cs
index b555e6c192..077a0185e3 100644
--- a/src/WebJobs.Script.Grpc/MessageExtensions/StatusResultExtensions.cs
+++ b/src/WebJobs.Script.Grpc/MessageExtensions/StatusResultExtensions.cs
@@ -3,20 +3,18 @@
using System;
using System.Threading.Tasks;
-using Grpc.Core;
-using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
namespace Microsoft.Azure.WebJobs.Script.Grpc
{
internal static class StatusResultExtensions
{
- public static bool IsFailure(this StatusResult statusResult, out Exception exception)
+ public static bool IsFailure(this StatusResult statusResult, bool enableUserCodeExceptionCapability, out Exception exception)
{
switch (statusResult.Status)
{
case StatusResult.Types.Status.Failure:
- exception = GetRpcException(statusResult);
+ exception = GetRpcException(statusResult, enableUserCodeExceptionCapability);
return true;
case StatusResult.Types.Status.Cancelled:
@@ -29,6 +27,11 @@ public static bool IsFailure(this StatusResult statusResult, out Exception excep
}
}
+ public static bool IsFailure(this StatusResult statusResult, out Exception exception)
+ {
+ return IsFailure(statusResult, false, out exception);
+ }
+
///
/// This method is only hit on the invocation code path.
/// enableUserCodeExceptionCapability = feature flag exposed as a capability that is set by the worker.
@@ -68,4 +71,4 @@ public static Workers.Rpc.RpcException GetRpcException(StatusResult statusResult
return new Workers.Rpc.RpcException(status, string.Empty, string.Empty);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/WebJobs.Script/Environment/IEnvironment.cs b/src/WebJobs.Script/Environment/IEnvironment.cs
index 2889e3b7e2..e502b8effd 100644
--- a/src/WebJobs.Script/Environment/IEnvironment.cs
+++ b/src/WebJobs.Script/Environment/IEnvironment.cs
@@ -12,6 +12,11 @@ namespace Microsoft.Azure.WebJobs.Script
///
public interface IEnvironment
{
+ ///
+ /// Gets a value indicating whether the current process is a 64-bit process.
+ ///
+ public bool Is64BitProcess { get; }
+
///
/// Returns the value of an environment variable for the current .
///
@@ -20,7 +25,7 @@ public interface IEnvironment
string GetEnvironmentVariable(string name);
///
- /// Creates, modifies, or deletes an environment variable stored in the current
+ /// Creates, modifies, or deletes an environment variable stored in the current .
///
/// The environment variable name.
/// The value to assign to the variable.
diff --git a/src/WebJobs.Script/Environment/SystemEnvironment.cs b/src/WebJobs.Script/Environment/SystemEnvironment.cs
index ed3e862225..d8ac86e36a 100644
--- a/src/WebJobs.Script/Environment/SystemEnvironment.cs
+++ b/src/WebJobs.Script/Environment/SystemEnvironment.cs
@@ -17,6 +17,8 @@ private SystemEnvironment()
public static SystemEnvironment Instance => _instance.Value;
+ public bool Is64BitProcess => Environment.Is64BitProcess;
+
private static SystemEnvironment CreateInstance()
{
return new SystemEnvironment();
diff --git a/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs b/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs
index afd3563a3a..3c88df31f3 100644
--- a/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs
+++ b/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs
@@ -71,7 +71,7 @@ public bool IsLegacyExtensionBundle()
///
/// Attempts to locate the extension bundle inside the probing paths and download paths. If the extension bundle is not found then it will download the extension bundle.
///
- /// Path of the extension bundle
+ /// Path of the extension bundle.
public async Task GetExtensionBundlePath()
{
using (var httpClient = new HttpClient())
@@ -83,8 +83,8 @@ public async Task GetExtensionBundlePath()
///
/// Attempts to locate the extension bundle inside the probing paths and download paths. If the extension bundle is not found then it will download the extension bundle.
///
- /// HttpClient used to download the extension bundle
- /// Path of the extension bundle
+ /// HttpClient used to download the extension bundle.
+ /// Path of the extension bundle.
public async Task GetExtensionBundlePath(HttpClient httpClient)
{
return await GetBundle(httpClient);
diff --git a/src/WebJobs.Script/ScriptConstants.cs b/src/WebJobs.Script/ScriptConstants.cs
index 75b2275166..b5d982a4e8 100644
--- a/src/WebJobs.Script/ScriptConstants.cs
+++ b/src/WebJobs.Script/ScriptConstants.cs
@@ -88,6 +88,7 @@ public static class ScriptConstants
public const string DefaultMasterKeyName = "master";
public const string DefaultFunctionKeyName = "default";
public const string ColdStartEventName = "ColdStart";
+ public const string PlaceholderMissDueToBitnessEventName = "PlaceholderMissDueToBitness";
public const string FunctionsUserAgent = "AzureFunctionsRuntime";
public const string HttpScaleUserAgent = "HttpScaleManager";
diff --git a/src/WebJobs.Script/WebJobs.Script.csproj b/src/WebJobs.Script/WebJobs.Script.csproj
index 413bd27b75..7c00814776 100644
--- a/src/WebJobs.Script/WebJobs.Script.csproj
+++ b/src/WebJobs.Script/WebJobs.Script.csproj
@@ -47,7 +47,7 @@
-
+
@@ -57,11 +57,11 @@
-
+
-
-
-
+
+
+
diff --git a/src/WebJobs.Script/Workers/Rpc/IRpcWorkerChannel.cs b/src/WebJobs.Script/Workers/Rpc/IRpcWorkerChannel.cs
index df9bde5278..d9d3e6eb9c 100644
--- a/src/WebJobs.Script/Workers/Rpc/IRpcWorkerChannel.cs
+++ b/src/WebJobs.Script/Workers/Rpc/IRpcWorkerChannel.cs
@@ -22,7 +22,7 @@ public interface IRpcWorkerChannel : IWorkerChannel
void SendFunctionLoadRequests(ManagedDependencyOptions managedDependencyOptions, TimeSpan? functionTimeout);
- Task SendFunctionEnvironmentReloadRequest();
+ Task SendFunctionEnvironmentReloadRequest();
void SendWorkerWarmupRequest();
diff --git a/src/WebJobs.Script/Workers/Rpc/RpcWorkerContext.cs b/src/WebJobs.Script/Workers/Rpc/RpcWorkerContext.cs
index 50dd7d54ee..e39ce55adb 100644
--- a/src/WebJobs.Script/Workers/Rpc/RpcWorkerContext.cs
+++ b/src/WebJobs.Script/Workers/Rpc/RpcWorkerContext.cs
@@ -44,7 +44,10 @@ public RpcWorkerContext(string requestId,
public override string GetFormattedArguments()
{
- return $" --host {ServerUri.Host} --port {ServerUri.Port} --workerId {WorkerId} --requestId {RequestId} --grpcMaxMessageLength {MaxMessageLength}";
+ // Adding a second copy of the commandline arguments with the "functions-" prefix to prevent any conflicts caused by the existing generic names.
+ // Language workers are advised to use the "functions-" prefix ones and if not present fallback to existing ones.
+
+ return $" --host {ServerUri.Host} --port {ServerUri.Port} --workerId {WorkerId} --requestId {RequestId} --grpcMaxMessageLength {MaxMessageLength} --functions-uri {ServerUri.AbsoluteUri} --functions-worker-id {WorkerId} --functions-request-id {RequestId} --functions-grpc-max-message-length {MaxMessageLength}";
}
}
}
diff --git a/src/WebJobs.Script/Workers/Rpc/WebHostRpcWorkerChannelManager.cs b/src/WebJobs.Script/Workers/Rpc/WebHostRpcWorkerChannelManager.cs
index af2863f7cc..8e0ce86d50 100644
--- a/src/WebJobs.Script/Workers/Rpc/WebHostRpcWorkerChannelManager.cs
+++ b/src/WebJobs.Script/Workers/Rpc/WebHostRpcWorkerChannelManager.cs
@@ -123,12 +123,14 @@ public async Task SpecializeAsync()
if (_workerRuntime != null && rpcWorkerChannel != null)
{
+ bool envReloadRequestResultSuccessful = false;
if (UsePlaceholderChannel(rpcWorkerChannel))
{
_logger.LogDebug("Loading environment variables for runtime: {runtime}", _workerRuntime);
- await rpcWorkerChannel.SendFunctionEnvironmentReloadRequest();
+ envReloadRequestResultSuccessful = await rpcWorkerChannel.SendFunctionEnvironmentReloadRequest();
}
- else
+
+ if (envReloadRequestResultSuccessful == false)
{
_logger.LogDebug("Shutting down placeholder worker. Worker is not compatible for runtime: {runtime}", _workerRuntime);
// If we need to allow file edits, we should shutdown the webhost channel on specialization.
@@ -181,6 +183,15 @@ private bool UsePlaceholderChannel(IRpcWorkerChannel channel)
return false;
}
+ // We support specialization of dotnet-isolated only on 64bit host process.
+ if (!_environment.Is64BitProcess)
+ {
+ _logger.LogInformation(new EventId(421, ScriptConstants.PlaceholderMissDueToBitnessEventName),
+ "This app is configured as 32-bit and therefore does not leverage all performance optimizations. See https://aka.ms/azure-functions/dotnet/placeholders for more information.");
+
+ return false;
+ }
+
// Do not specialize if the placeholder is 6.0 but the site is 7.0 (for example).
var currentWorkerRuntimeVersion = _environment.GetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName);
channel.WorkerProcess.Process.StartInfo.Environment.TryGetValue(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName, out string placeholderWorkerRuntimeVersion);
diff --git a/test/DotNetIsolated60/DotNetIsolated60.csproj b/test/DotNetIsolated60/DotNetIsolated60.csproj
index 8e602a3cd7..f6a818fb17 100644
--- a/test/DotNetIsolated60/DotNetIsolated60.csproj
+++ b/test/DotNetIsolated60/DotNetIsolated60.csproj
@@ -6,13 +6,15 @@
enable
enable
True
+ True
+ True
-
-
-
+
+
+
-
+
diff --git a/test/DotNetIsolated60/DotNetIsolated60.sln b/test/DotNetIsolated60/DotNetIsolated60.sln
index 1f839fdf73..286c56b973 100644
--- a/test/DotNetIsolated60/DotNetIsolated60.sln
+++ b/test/DotNetIsolated60/DotNetIsolated60.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetIsolated60", "DotNetIsolated60.csproj", "{1DA92227-F28E-408D-96B1-20C72571E4AE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetIsolatedUnsupportedWorker", "..\DotNetIsolatedUnsupportedWorker\DotNetIsolatedUnsupportedWorker.csproj", "{3F15B936-6365-447E-9EC6-4E996B30C55F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{1DA92227-F28E-408D-96B1-20C72571E4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DA92227-F28E-408D-96B1-20C72571E4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DA92227-F28E-408D-96B1-20C72571E4AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F15B936-6365-447E-9EC6-4E996B30C55F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F15B936-6365-447E-9EC6-4E996B30C55F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F15B936-6365-447E-9EC6-4E996B30C55F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F15B936-6365-447E-9EC6-4E996B30C55F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/test/DotNetIsolated60/Program.cs b/test/DotNetIsolated60/Program.cs
index 2d2691d7d4..29dd670851 100644
--- a/test/DotNetIsolated60/Program.cs
+++ b/test/DotNetIsolated60/Program.cs
@@ -11,14 +11,12 @@
if (useProxy)
{
hostBuilder
- .ConfigureFunctionsWebApplication()
- .ConfigureGeneratedFunctionMetadataProvider();
+ .ConfigureFunctionsWebApplication();
}
else
{
hostBuilder
- .ConfigureFunctionsWorkerDefaults()
- .ConfigureGeneratedFunctionMetadataProvider();
+ .ConfigureFunctionsWorkerDefaults();
}
var host = hostBuilder.Build();
diff --git a/test/DotNetIsolatedUnsupportedWorker/DotNetIsolatedUnsupportedWorker.csproj b/test/DotNetIsolatedUnsupportedWorker/DotNetIsolatedUnsupportedWorker.csproj
new file mode 100644
index 0000000000..81c3461eaf
--- /dev/null
+++ b/test/DotNetIsolatedUnsupportedWorker/DotNetIsolatedUnsupportedWorker.csproj
@@ -0,0 +1,29 @@
+
+
+ net6.0
+ v4
+ Exe
+ enable
+ enable
+ True
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/DotNetIsolatedUnsupportedWorker/HttpRequestDataFunction.cs b/test/DotNetIsolatedUnsupportedWorker/HttpRequestDataFunction.cs
new file mode 100644
index 0000000000..f494114440
--- /dev/null
+++ b/test/DotNetIsolatedUnsupportedWorker/HttpRequestDataFunction.cs
@@ -0,0 +1,30 @@
+using System.Net;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Logging;
+
+namespace DotNetIsolatedUnsupportedWorker
+{
+ public class HttpRequestDataFunction
+ {
+ private readonly ILogger _logger;
+
+ public HttpRequestDataFunction(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ [Function("HttpRequestDataFunction")]
+ public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
+ {
+ _logger.LogInformation("C# HTTP trigger function processed a request.");
+
+ var response = req.CreateResponse(HttpStatusCode.OK);
+ response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
+
+ response.WriteString("Welcome to Azure Functions!");
+
+ return response;
+ }
+ }
+}
diff --git a/test/DotNetIsolatedUnsupportedWorker/HttpRequestFunction.cs b/test/DotNetIsolatedUnsupportedWorker/HttpRequestFunction.cs
new file mode 100644
index 0000000000..ba6df7f15b
--- /dev/null
+++ b/test/DotNetIsolatedUnsupportedWorker/HttpRequestFunction.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Logging;
+
+namespace DotNetIsolatedUnsupportedWorker
+{
+ public class HttpRequestFunction
+ {
+ private readonly ILogger _logger;
+
+ public HttpRequestFunction(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ [Function(nameof(HttpRequestFunction))]
+ public Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
+ {
+ _logger.LogInformation("C# HTTP trigger function processed a request.");
+ return req.HttpContext.Response.WriteAsync("Welcome to Azure Functions!");
+ }
+ }
+}
diff --git a/test/DotNetIsolatedUnsupportedWorker/Program.cs b/test/DotNetIsolatedUnsupportedWorker/Program.cs
new file mode 100644
index 0000000000..29dd670851
--- /dev/null
+++ b/test/DotNetIsolatedUnsupportedWorker/Program.cs
@@ -0,0 +1,23 @@
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Hosting;
+
+//Debugger.Launch();
+
+// Tests can set an env var that will swap this to use the proxy
+bool useProxy = Environment.GetEnvironmentVariable("UseProxyInTest")?.Contains("1") ?? false;
+
+var hostBuilder = new HostBuilder();
+
+if (useProxy)
+{
+ hostBuilder
+ .ConfigureFunctionsWebApplication();
+}
+else
+{
+ hostBuilder
+ .ConfigureFunctionsWorkerDefaults();
+}
+
+var host = hostBuilder.Build();
+host.Run();
diff --git a/test/DotNetIsolatedUnsupportedWorker/QueueFunction.cs b/test/DotNetIsolatedUnsupportedWorker/QueueFunction.cs
new file mode 100644
index 0000000000..69dd183369
--- /dev/null
+++ b/test/DotNetIsolatedUnsupportedWorker/QueueFunction.cs
@@ -0,0 +1,21 @@
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Logging;
+
+namespace DotNetIsolatedUnsupportedWorker
+{
+ public class QueueFunction
+ {
+ private readonly ILogger _logger;
+
+ public QueueFunction(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ [Function("QueueFunction")]
+ public void Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string myQueueItem)
+ {
+ _logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
+ }
+ }
+}
diff --git a/test/DotNetIsolatedUnsupportedWorker/host.json b/test/DotNetIsolatedUnsupportedWorker/host.json
new file mode 100644
index 0000000000..ee5cf5f83f
--- /dev/null
+++ b/test/DotNetIsolatedUnsupportedWorker/host.json
@@ -0,0 +1,12 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ },
+ "enableLiveMetricsFilters": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/EmptyScriptRoot/host.json b/test/EmptyScriptRoot/host.json
new file mode 100644
index 0000000000..55d16424d6
--- /dev/null
+++ b/test/EmptyScriptRoot/host.json
@@ -0,0 +1,7 @@
+{
+ "version": "2.0",
+ "extensionBundle": {
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
+ "version": "[4.*, 5.0.0)"
+ }
+}
\ No newline at end of file
diff --git a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SpecializationE2ETests.cs b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SpecializationE2ETests.cs
index ceb4160c8c..79ca16577d 100644
--- a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SpecializationE2ETests.cs
+++ b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SpecializationE2ETests.cs
@@ -51,6 +51,8 @@ public class SpecializationE2ETests
private static readonly string _scriptRootConfigPath = ConfigurationPath.Combine(ConfigurationSectionNames.WebHost, nameof(ScriptApplicationHostOptions.ScriptPath));
private static readonly string _dotnetIsolated60Path = Path.GetFullPath(@"..\..\..\..\DotNetIsolated60\bin\Debug\net6.0");
+ private static readonly string _dotnetIsolatedUnsuppportedPath = Path.GetFullPath(@"..\..\..\..\DotNetIsolatedUnsupportedWorker\bin\Debug\net6.0");
+ private static readonly string _dotnetIsolatedEmptyScriptRoot = Path.GetFullPath(@"..\..\..\..\EmptyScriptRoot");
private const string _specializedScriptRoot = @"TestScripts\CSharp";
@@ -799,7 +801,7 @@ public async Task Specialization_JobHostInternalStorageOptionsUpdatesWithActiveH
[Fact]
public async Task DotNetIsolated_PlaceholderHit()
{
- var builder = InitializeDotNetIsolatedPlaceholderBuilder("HttpRequestDataFunction");
+ var builder = InitializeDotNetIsolatedPlaceholderBuilder(_dotnetIsolated60Path, "HttpRequestDataFunction");
using var testServer = new TestServer(builder);
@@ -839,7 +841,7 @@ public async Task DotNetIsolated_PlaceholderHit_WithProxies()
{
// This test ensures that capabilities are correctly applied in EnvironmentReload during
// specialization
- var builder = InitializeDotNetIsolatedPlaceholderBuilder("HttpRequestFunction");
+ var builder = InitializeDotNetIsolatedPlaceholderBuilder(_dotnetIsolated60Path, "HttpRequestFunction");
using var testServer = new TestServer(builder);
@@ -888,19 +890,37 @@ public async Task DotNetIsolated_PlaceholderHit_WithProxies()
public async Task DotNetIsolated_PlaceholderMiss_EnvVar()
{
// Placeholder miss if the WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED env var is not set
- await DotNetIsolatedPlaceholderMiss();
+ await DotNetIsolatedPlaceholderMiss(_dotnetIsolated60Path);
var log = _loggerProvider.GetLog();
Assert.Contains("UsePlaceholderDotNetIsolated: False", log);
Assert.Contains("Shutting down placeholder worker. Worker is not compatible for runtime: dotnet-isolated", log);
}
+ [Fact]
+ public async Task DotNetIsolated_PlaceholderMiss_Not64Bit()
+ {
+ _environment.SetProcessBitness(is64Bitness: false);
+
+ // We only specialize when host process is 64 bit process.
+ await DotNetIsolatedPlaceholderMiss(_dotnetIsolated60Path, () =>
+ {
+ _environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteUsePlaceholderDotNetIsolated, "1");
+ _environment.SetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName, "6.0");
+ });
+
+ var log = _loggerProvider.GetLog();
+ Assert.Contains("UsePlaceholderDotNetIsolated: True", log);
+ Assert.Contains("This app is configured as 32-bit and therefore does not leverage all performance optimizations. See https://aka.ms/azure-functions/dotnet/placeholders for more information.", log);
+ Assert.Contains("Shutting down placeholder worker. Worker is not compatible for runtime: dotnet-isolated", log);
+ }
+
[Fact]
public async Task DotNetIsolated_PlaceholderMiss_DotNetVer()
{
// Even with placeholders enabled via the WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED env var,
// if the dotnet version does not match, we should not use the placeholder
- await DotNetIsolatedPlaceholderMiss(() =>
+ await DotNetIsolatedPlaceholderMiss(_dotnetIsolated60Path, () =>
{
_environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteUsePlaceholderDotNetIsolated, "1");
_environment.SetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName, "7.0");
@@ -912,6 +932,36 @@ await DotNetIsolatedPlaceholderMiss(() =>
Assert.Contains("Shutting down placeholder worker. Worker is not compatible for runtime: dotnet-isolated", log);
}
+ [Fact]
+ public async Task DotNetIsolated_PlaceholderMiss_UnsupportedWorkerPackage()
+ {
+ await DotNetIsolatedPlaceholderMiss(_dotnetIsolatedUnsuppportedPath, () =>
+ {
+ _environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteUsePlaceholderDotNetIsolated, "1");
+ _environment.SetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName, "6.0");
+ });
+
+ var log = _loggerProvider.GetLog();
+ Assert.Contains("UsePlaceholderDotNetIsolated: True", log);
+ Assert.Contains("Placeholder runtime version: '6.0'. Site runtime version: '6.0'. Match: True", log);
+ Assert.Contains("Shutting down placeholder worker. Worker is not compatible for runtime: dotnet-isolated", log);
+ }
+
+ [Fact]
+ public async Task DotNetIsolated_PlaceholderMiss_EmptyScriptRoot()
+ {
+ await DotNetIsolatedPlaceholderMiss(_dotnetIsolatedEmptyScriptRoot, () =>
+ {
+ _environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteUsePlaceholderDotNetIsolated, "1");
+ _environment.SetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName, "6.0");
+ });
+
+ var log = _loggerProvider.GetLog();
+ Assert.Contains("UsePlaceholderDotNetIsolated: True", log);
+ Assert.Contains("Placeholder runtime version: '6.0'. Site runtime version: '6.0'. Match: True", log);
+ Assert.Contains("Shutting down placeholder worker. Worker is not compatible for runtime: dotnet-isolated", log);
+ }
+
[Fact]
// Fix for https://github.com/Azure/azure-functions-host/issues/9288
public async Task SpecializedSite_StopsHostBeforeWorker()
@@ -925,7 +975,7 @@ public async Task SpecializedSite_StopsHostBeforeWorker()
await queue.CreateIfNotExistsAsync();
await queue.ClearAsync();
- var builder = InitializeDotNetIsolatedPlaceholderBuilder("HttpRequestDataFunction", "QueueFunction");
+ var builder = InitializeDotNetIsolatedPlaceholderBuilder(_dotnetIsolated60Path, "HttpRequestDataFunction", "QueueFunction");
using var testServer = new TestServer(builder);
@@ -987,9 +1037,9 @@ await TestHelpers.Await(() =>
Assert.Empty(completedLogs.Where(p => p.Level == LogLevel.Error));
}
- private async Task DotNetIsolatedPlaceholderMiss(Action additionalSpecializedSetup = null)
+ private async Task DotNetIsolatedPlaceholderMiss(string scriptRootPath, Action additionalSpecializedSetup = null)
{
- var builder = InitializeDotNetIsolatedPlaceholderBuilder("HttpRequestDataFunction");
+ var builder = InitializeDotNetIsolatedPlaceholderBuilder(scriptRootPath, "HttpRequestDataFunction");
// remove WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED
_environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteUsePlaceholderDotNetIsolated, null);
@@ -1015,18 +1065,26 @@ private async Task DotNetIsolatedPlaceholderMiss(Action additionalSpecializedSet
additionalSpecializedSetup?.Invoke();
response = await client.GetAsync("api/HttpRequestDataFunction");
- response.EnsureSuccessStatusCode();
+ if (scriptRootPath == _dotnetIsolatedEmptyScriptRoot)
+ {
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+ else
+ {
+ response.EnsureSuccessStatusCode();
- // Placeholder miss; new channel should be started using the deployed worker directly
- var specializedChannel = await webChannelManager.GetChannels("dotnet-isolated").Single().Value.Task;
- Assert.Contains("dotnet.exe", specializedChannel.WorkerProcess.Process.StartInfo.FileName);
- Assert.Contains("DotNetIsolated60", specializedChannel.WorkerProcess.Process.StartInfo.Arguments);
- runningProcess = Process.GetProcessById(specializedChannel.WorkerProcess.Id);
- Assert.Contains(runningProcess.ProcessName, "dotnet");
-
- // Ensure other process is gone.
- Assert.DoesNotContain(Process.GetProcesses(), p => p.ProcessName.Contains("FunctionsNetHost"));
- Assert.Throws(() => placeholderChannel.WorkerProcess.Process.Id);
+ var expectedProcessName = scriptRootPath == _dotnetIsolated60Path ? "DotNetIsolated60" : "DotNetIsolatedUnsupported";
+ // Placeholder miss; new channel should be started using the deployed worker directly
+ var specializedChannel = await webChannelManager.GetChannels("dotnet-isolated").Single().Value.Task;
+ Assert.Contains("dotnet.exe", specializedChannel.WorkerProcess.Process.StartInfo.FileName);
+ Assert.Contains(expectedProcessName, specializedChannel.WorkerProcess.Process.StartInfo.Arguments);
+ runningProcess = Process.GetProcessById(specializedChannel.WorkerProcess.Id);
+ Assert.Contains(runningProcess.ProcessName, "dotnet");
+
+ // Ensure other process is gone.
+ Assert.DoesNotContain(Process.GetProcesses(), p => p.ProcessName.Contains("FunctionsNetHost"));
+ Assert.Throws(() => placeholderChannel.WorkerProcess.Process.Id);
+ }
}
private static void BuildDotnetIsolated60()
@@ -1035,7 +1093,7 @@ private static void BuildDotnetIsolated60()
p.WaitForExit();
}
- private IWebHostBuilder InitializeDotNetIsolatedPlaceholderBuilder(params string[] functions)
+ private IWebHostBuilder InitializeDotNetIsolatedPlaceholderBuilder(string scriptRootPath, params string[] functions)
{
BuildDotnetIsolated60();
@@ -1050,7 +1108,7 @@ private IWebHostBuilder InitializeDotNetIsolatedPlaceholderBuilder(params string
{
config.AddInMemoryCollection(new Dictionary
{
- { _scriptRootConfigPath, _dotnetIsolated60Path },
+ { _scriptRootConfigPath, scriptRootPath },
});
});
diff --git a/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj b/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj
index 72548c0bce..48052e5676 100644
--- a/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj
+++ b/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj
@@ -44,7 +44,7 @@
-
+
diff --git a/test/WebJobs.Script.Tests.Shared/TestEnvironment.cs b/test/WebJobs.Script.Tests.Shared/TestEnvironment.cs
index 1bd73bea94..d8af0c5f26 100644
--- a/test/WebJobs.Script.Tests.Shared/TestEnvironment.cs
+++ b/test/WebJobs.Script.Tests.Shared/TestEnvironment.cs
@@ -12,17 +12,26 @@ namespace Microsoft.Azure.WebJobs.Script.Tests
public class TestEnvironment : IEnvironment
{
private readonly IDictionary _variables;
+ private bool _is64BitProcess;
public TestEnvironment()
- : this(new Dictionary())
+ : this(new Dictionary())
{
}
public TestEnvironment(IDictionary variables)
+ : this(variables, is64BitProcess: true)
+ {
+ }
+
+ public TestEnvironment(IDictionary variables, bool is64BitProcess)
{
_variables = variables ?? throw new ArgumentNullException(nameof(variables));
+ _is64BitProcess = is64BitProcess;
}
+ public bool Is64BitProcess => _is64BitProcess;
+
public void Clear()
{
_variables.Clear();
@@ -54,5 +63,10 @@ public static TestEnvironment FromEnvironmentVariables()
return new TestEnvironment(variables);
}
+
+ public void SetProcessBitness(bool is64Bitness)
+ {
+ _is64BitProcess = is64Bitness;
+ }
}
}
diff --git a/test/WebJobs.Script.Tests/Workers/DefaultWorkerProcessFactoryTests.cs b/test/WebJobs.Script.Tests/Workers/DefaultWorkerProcessFactoryTests.cs
index 2286612603..5952481759 100644
--- a/test/WebJobs.Script.Tests/Workers/DefaultWorkerProcessFactoryTests.cs
+++ b/test/WebJobs.Script.Tests/Workers/DefaultWorkerProcessFactoryTests.cs
@@ -82,7 +82,7 @@ public void DefaultWorkerProcessFactory_Returns_ExpectedProcess(WorkerContext wo
}
if (workerContext is RpcWorkerContext)
{
- Assert.Equal(" httpvalue1 TestVal httpvalue2 --host localhost --port 80 --workerId testWorkerId --requestId testId --grpcMaxMessageLength 2147483647", childProcess.StartInfo.Arguments);
+ Assert.Equal(" httpvalue1 TestVal httpvalue2 --host localhost --port 80 --workerId testWorkerId --requestId testId --grpcMaxMessageLength 2147483647 --functions-uri http://localhost/ --functions-worker-id testWorkerId --functions-request-id testId --functions-grpc-max-message-length 2147483647", childProcess.StartInfo.Arguments);
}
else
{
diff --git a/test/WebJobs.Script.Tests/Workers/Rpc/TestRpcWorkerChannel.cs b/test/WebJobs.Script.Tests/Workers/Rpc/TestRpcWorkerChannel.cs
index c65e15d7fa..c7b6145565 100644
--- a/test/WebJobs.Script.Tests/Workers/Rpc/TestRpcWorkerChannel.cs
+++ b/test/WebJobs.Script.Tests/Workers/Rpc/TestRpcWorkerChannel.cs
@@ -74,10 +74,10 @@ public void SendFunctionLoadRequests(ManagedDependencyOptions managedDependencie
_testLogger.LogInformation("RegisterFunctions called");
}
- public Task SendFunctionEnvironmentReloadRequest()
+ public Task SendFunctionEnvironmentReloadRequest()
{
_testLogger.LogInformation("SendFunctionEnvironmentReloadRequest called");
- return Task.CompletedTask;
+ return Task.FromResult(true);
}
public void SendInvocationRequest(ScriptInvocationContext context)