Skip to content

Commit

Permalink
Dynamic Concurrency support (Azure#2720)
Browse files Browse the repository at this point in the history
  • Loading branch information
mathewc authored Jun 17, 2021
1 parent 0178683 commit 206e194
Show file tree
Hide file tree
Showing 83 changed files with 6,916 additions and 57 deletions.
13 changes: 13 additions & 0 deletions TestChildProcess/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace TestChildProcess
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Child process started");
Console.ReadLine();
}
}
}
8 changes: 8 additions & 0 deletions TestChildProcess/TestChildProcess.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

</Project>
9 changes: 8 additions & 1 deletion WebJobs.sln
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProject
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpFunctions", "test\TestProjects\FSharpFunctions\FSharpFunctions.fsproj", "{11702A4B-8402-4082-BE38-4F0C2CBBF61D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{78F8086D-8313-477A-B24F-E475A690880A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{78F8086D-8313-477A-B24F-E475A690880A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestChildProcess", "TestChildProcess\TestChildProcess.csproj", "{CEBC078A-9DD2-4558-829B-DBAE6BEA4264}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Expand Down Expand Up @@ -134,6 +136,10 @@ Global
{78F8086D-8313-477A-B24F-E475A690880A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78F8086D-8313-477A-B24F-E475A690880A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78F8086D-8313-477A-B24F-E475A690880A}.Release|Any CPU.Build.0 = Release|Any CPU
{CEBC078A-9DD2-4558-829B-DBAE6BEA4264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CEBC078A-9DD2-4558-829B-DBAE6BEA4264}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CEBC078A-9DD2-4558-829B-DBAE6BEA4264}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CEBC078A-9DD2-4558-829B-DBAE6BEA4264}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -150,6 +156,7 @@ Global
{C5E1A8E8-711F-4377-A8BD-7DB58E6C580D} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{11702A4B-8402-4082-BE38-4F0C2CBBF61D} = {C5E1A8E8-711F-4377-A8BD-7DB58E6C580D}
{78F8086D-8313-477A-B24F-E475A690880A} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{CEBC078A-9DD2-4558-829B-DBAE6BEA4264} = {C5E1A8E8-711F-4377-A8BD-7DB58E6C580D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {371BFA14-0980-4A43-A18A-CA1C1A9CB784}
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ install:
- ps: |
$env:CommitHash = "$env:APPVEYOR_REPO_COMMIT"
.\dotnet-install.ps1 -Version 2.1.300 -Architecture x86
.\dotnet-install.ps1 -Version 3.1.410 -Architecture x86
build_script:
- ps: |
$buildNumber = 0
Expand Down
4 changes: 2 additions & 2 deletions build/common.props
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<!-- Packages can have independent versions and only increment when released -->
<Version>3.0.29$(VersionSuffix)</Version>
<Version>3.0.30$(VersionSuffix)</Version>
<ExtensionsStorageVersion>4.0.5$(VersionSuffix)</ExtensionsStorageVersion>
<HostStorageVersion>4.0.3$(VersionSuffix)</HostStorageVersion>
<HostStorageVersion>4.0.4$(VersionSuffix)</HostStorageVersion>
<LoggingVersion>4.0.2$(VersionSuffix)</LoggingVersion>

<TargetFramework>netstandard2.0</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

#nullable enable

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Microsoft.Azure.WebJobs.Host
{
internal class BlobStorageConcurrencyStatusRepository : IConcurrencyStatusRepository
{
private const string HostContainerName = "azure-webjobs-hosts";
private readonly IHostIdProvider _hostIdProvider;
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
private CloudBlobContainer? _blobContainer;

public BlobStorageConcurrencyStatusRepository(IConfiguration configuration, IHostIdProvider hostIdProvider, ILoggerFactory loggerFactory)
{
_configuration = configuration;
_hostIdProvider = hostIdProvider;
_logger = loggerFactory.CreateLogger(LogCategories.Concurrency);
}

public async Task<HostConcurrencySnapshot?> ReadAsync(CancellationToken cancellationToken)
{
string blobPath = await GetBlobPathAsync(cancellationToken);

try
{
CloudBlobContainer? container = await GetContainerAsync(cancellationToken);
if (container != null)
{
CloudBlockBlob blob = container.GetBlockBlobReference(blobPath);
string content = await blob.DownloadTextAsync(cancellationToken);
if (!string.IsNullOrEmpty(content))
{
var result = JsonConvert.DeserializeObject<HostConcurrencySnapshot>(content);
return result;
}
}
}
catch (StorageException stex) when (stex.RequestInformation?.HttpStatusCode == 404)
{
return null;
}
catch (Exception e)
{
_logger.LogError(e, $"Error reading snapshot blob {blobPath}");
throw e;
}

return null;
}

public async Task WriteAsync(HostConcurrencySnapshot snapshot, CancellationToken cancellationToken)
{
string blobPath = await GetBlobPathAsync(cancellationToken);

try
{
CloudBlobContainer? container = await GetContainerAsync(cancellationToken);
if (container != null)
{
CloudBlockBlob blob = container.GetBlockBlobReference(blobPath);

using (StreamWriter writer = new StreamWriter(await blob.OpenWriteAsync(cancellationToken)))
{
var content = JsonConvert.SerializeObject(snapshot);
await writer.WriteAsync(content);
}
}
}
catch (Exception e)
{
_logger.LogError(e, $"Error writing snapshot blob {blobPath}");
throw e;
}
}

internal async Task<CloudBlobContainer?> GetContainerAsync(CancellationToken cancellationToken)
{
if (_blobContainer == null)
{
string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
if (!string.IsNullOrEmpty(storageConnectionString) && CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account))
{
var client = account.CreateCloudBlobClient();
_blobContainer = client.GetContainerReference(HostContainerName);

await _blobContainer.CreateIfNotExistsAsync(cancellationToken);
}
}

return _blobContainer;
}

internal async Task<string> GetBlobPathAsync(CancellationToken cancellationToken)
{
string hostId = await _hostIdProvider.GetHostIdAsync(cancellationToken);
return $"concurrency/{hostId}/concurrencyStatus.json";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -50,6 +51,8 @@ public static void AddAzureStorageCoreServices(this IServiceCollection services)
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<JobHostInternalStorageOptions>, CoreWebJobsOptionsSetup<JobHostInternalStorageOptions>>());

services.TryAddSingleton<IDelegatingHandlerProvider, DefaultDelegatingHandlerProvider>();

services.AddSingleton<IConcurrencyStatusRepository, BlobStorageConcurrencyStatusRepository>();
}

// This is only called if the host didn't already provide an implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<Version>$(HostStorageVersion)</Version>
<InformationalVersion>$(Version) Commit hash: $(CommitHash)</InformationalVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AssemblyName>Microsoft.Azure.WebJobs.Host.Storage</AssemblyName>
</PropertyGroup>

Expand Down
97 changes: 97 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/Config/ConcurrencyOptionsSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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 Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.WebJobs.Host.Config
{
internal class ConcurrencyOptionsSetup : IConfigureOptions<ConcurrencyOptions>
{
private const int BytesPerGB = 1024 * 1024 * 1024;

public void Configure(ConcurrencyOptions options)
{
// TODO: Once Memory monitoring is public add this back.
// For now, the memory throttle is internal only for testing.
// https://github.com/Azure/azure-webjobs-sdk/issues/2733
//ConfigureMemoryOptions(options);
}

internal static void ConfigureMemoryOptions(ConcurrencyOptions options)
{
string sku = Utility.GetWebsiteSku();
int numCores = Utility.GetEffectiveCoresCount();
ConfigureMemoryOptions(options, sku, numCores);
}

internal static void ConfigureMemoryOptions(ConcurrencyOptions options, string sku, int numCores)
{
long memoryLimitBytes = GetMemoryLimitBytes(sku, numCores);
if (memoryLimitBytes > 0)
{
// if we're able to determine the memory limit, apply it
options.TotalAvailableMemoryBytes = memoryLimitBytes;
}
}

internal static long GetMemoryLimitBytes(string sku, int numCores)
{
if (!string.IsNullOrEmpty(sku))
{
float memoryGBPerCore = GetMemoryGBPerCore(sku);

if (memoryGBPerCore > 0)
{
double memoryLimitBytes = memoryGBPerCore * numCores * BytesPerGB;

if (string.Equals(sku, "IsolatedV2", StringComparison.OrdinalIgnoreCase) && numCores == 8)
{
// special case for upper tier IsolatedV2 where GB per Core
// isn't cleanly linear
memoryLimitBytes = (float)23 * BytesPerGB;
}

return (long)memoryLimitBytes;
}
}

// unable to determine memory limit
return -1;
}

internal static float GetMemoryGBPerCore(string sku)
{
if (string.IsNullOrEmpty(sku))
{
return -1;
}

// These memory allowances are based on published limits:
// Dynamic SKU: https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#service-limits
// Premium SKU: https://docs.microsoft.com/en-us/azure/azure-functions/functions-premium-plan?tabs=portal#available-instance-skus
// Dedicated SKUs: https://azure.microsoft.com/en-us/pricing/details/app-service/windows/
switch (sku.ToLower())
{
case "free":
case "shared":
return 1;
case "dynamic":
return 1.5F;
case "basic":
case "standard":
return 1.75F;
case "premiumv2":
case "isolated":
case "elasticpremium":
return 3.5F;
case "premiumv3":
case "isolatedv2":
return 4;
default:
return -1;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 Microsoft.Azure.WebJobs.Host.Scale;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.WebJobs.Host.Config
{
internal class PrimaryHostCoordinatorOptionsSetup : IConfigureOptions<PrimaryHostCoordinatorOptions>
{
private readonly IOptions<ConcurrencyOptions> _concurrencyOptions;

public PrimaryHostCoordinatorOptionsSetup(IOptions<ConcurrencyOptions> concurrencyOptions)
{
_concurrencyOptions = concurrencyOptions;
}

public void Configure(PrimaryHostCoordinatorOptions options)
{
// in most WebJobs SDK scenarios, primary host coordination is not needed
// however, some features require it
if (_concurrencyOptions.Value.DynamicConcurrencyEnabled)
{
options.Enabled = true;
}
}
}
}
3 changes: 3 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ internal static class Constants
public const string EnvironmentSettingName = "AzureWebJobsEnv";
public const string DevelopmentEnvironmentValue = "Development";
public const string DynamicSku = "Dynamic";
public const string ElasticPremiumSku = "ElasticPremium";
public const string AzureWebsiteSku = "WEBSITE_SKU";
public const string AzureWebJobsShutdownFile = "WEBJOBS_SHUTDOWN_FILE";
public const string AzureWebsiteInstanceId = "WEBSITE_INSTANCE_ID";
public const string AzureWebsiteContainerName = "CONTAINER_NAME";
public const string DateTimeFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK";
}
}
Loading

0 comments on commit 206e194

Please sign in to comment.