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

[Bug Fix] Refactor Linux container management to avoid race condition that leads the host to initialize placeholder (warmup) function #10848

Open
wants to merge 7 commits into
base: dev
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ private async Task ApplyStartContextIfPresent(CancellationToken cancellationToke
var assignmentContext = _startupContextProvider.SetContext(encryptedAssignmentContext);
await SpecializeMSISideCar(assignmentContext);

bool success = _instanceManager.StartAssignment(assignmentContext);
_logger.LogDebug($"StartAssignment invoked (Success={success})");
bool success = await _instanceManager.StartAssignment(assignmentContext);
_logger.LogDebug("StartAssignment invoked (Success={success})", success);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public async Task<IActionResult> Assign([FromBody] EncryptedHostAssignmentContex
return StatusCode(StatusCodes.Status500InternalServerError, error);
}

var succeeded = _instanceManager.StartAssignment(assignmentContext);
var succeeded = await _instanceManager.StartAssignment(assignmentContext);

return succeeded
? Accepted()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface IInstanceManager

Task<string> ValidateContext(HostAssignmentContext assignmentContext);

bool StartAssignment(HostAssignmentContext assignmentContext);
Task<bool> StartAssignment(HostAssignmentContext assignmentContext);
Copy link
Contributor

@jviau jviau Feb 14, 2025

Choose a reason for hiding this comment

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

Maybe we should rename this. It is no longer a Start anymore with the Task. Maybe AssignInstanceAsync?


Task<string> SpecializeMSISidecar(HostAssignmentContext assignmentContext);
}
Expand Down
51 changes: 26 additions & 25 deletions src/WebJobs.Script.WebHost/Management/LinuxInstanceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.File;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
using Microsoft.Azure.WebJobs.Script.WebHost.Management.LinuxSpecialization;
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace Microsoft.Azure.WebJobs.Script.WebHost.Management
{
Expand Down Expand Up @@ -44,32 +36,21 @@ public LinuxInstanceManager(IHttpClientFactory httpClientFactory, IScriptWebHost

public abstract Task<string> SpecializeMSISidecar(HostAssignmentContext context);

public bool StartAssignment(HostAssignmentContext context)
public async Task<bool> StartAssignment(HostAssignmentContext context)
{
if (!_webHostEnvironment.InStandbyMode)
{
// This is only true when specializing pinned containers.
if (!context.Environment.TryGetValue(EnvironmentSettingNames.ContainerStartContext, out string startContext))
{
_logger.LogError("Assign called while host is not in placeholder mode and start context is not present.");
return false;
}
}

if (_environment.IsContainerReady())
if (!IsValidEnvironment(context))
{
_logger.LogError("Assign called while container is marked as specialized.");
return false;
}

if (context.IsWarmupRequest)
{
// Based on profiling download code jit-ing holds up cold start.
// Pre-jit to avoid paying the cost later.
Task.Run(async () => await DownloadWarmupAsync(context.GetRunFromPkgContext()));
await DownloadWarmupAsync(context.GetRunFromPkgContext());
return true;
}
else if (_assignmentContext == null)
else if (_assignmentContext is null)
{
lock (_assignmentLock)
{
Expand All @@ -91,8 +72,7 @@ public bool StartAssignment(HostAssignmentContext context)
_webHostEnvironment.DelayRequests();

// start the specialization process in the background
Task.Run(async () => await AssignAsync(context));

await AssignAsync(context);
return true;
}
else
Expand All @@ -104,6 +84,27 @@ public bool StartAssignment(HostAssignmentContext context)

public abstract Task<string> ValidateContext(HostAssignmentContext assignmentContext);

private bool IsValidEnvironment(HostAssignmentContext context)
{
if (!_webHostEnvironment.InStandbyMode)
{
// This is only true when specializing pinned containers.
if (!context.Environment.TryGetValue(EnvironmentSettingNames.ContainerStartContext, out string startContext))
{
_logger.LogError("Assign called while host is not in placeholder mode and start context is not present.");
return false;
}
}

if (_environment.IsContainerReady())
{
_logger.LogError("Assign called while container is marked as specialized.");
return false;
}

return true;
}

private async Task AssignAsync(HostAssignmentContext assignmentContext)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public async Task Assigns_Context_From_CONTAINER_START_CONTEXT()
m.SpecializeMSISidecar(It.Is<HostAssignmentContext>(context =>
hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(Task.FromResult(string.Empty));

_instanceManagerMock.Setup(manager => manager.StartAssignment(It.Is<HostAssignmentContext>(context => hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(true);
_instanceManagerMock.Setup(manager => manager.StartAssignment(It.Is<HostAssignmentContext>(context => hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(Task.FromResult(true));

var initializationHostService = new AtlasContainerInitializationHostedService(_environment, _instanceManagerMock.Object, NullLogger<AtlasContainerInitializationHostedService>.Instance, _startupContextProvider);
await initializationHostService.StartAsync(CancellationToken.None);
Expand Down Expand Up @@ -155,7 +155,7 @@ public async Task Assigns_Even_If_MSI_Specialization_Fails()
m.SpecializeMSISidecar(It.Is<HostAssignmentContext>(context =>
hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(Task.FromResult("msi-specialization-error"));

_instanceManagerMock.Setup(manager => manager.StartAssignment(It.Is<HostAssignmentContext>(context => hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(true);
_instanceManagerMock.Setup(manager => manager.StartAssignment(It.Is<HostAssignmentContext>(context => hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(Task.FromResult(true));

var initializationHostService = new AtlasContainerInitializationHostedService(_environment, _instanceManagerMock.Object, NullLogger<AtlasContainerInitializationHostedService>.Instance, _startupContextProvider);
await initializationHostService.StartAsync(CancellationToken.None);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public async Task Assigns_Context_From_CONTAINER_START_CONTEXT()
m.SpecializeMSISidecar(It.Is<HostAssignmentContext>(context =>
hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(Task.FromResult(string.Empty));

_instanceManagerMock.Setup(manager => manager.StartAssignment(It.Is<HostAssignmentContext>(context => hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(true);
_instanceManagerMock.Setup(manager => manager.StartAssignment(It.Is<HostAssignmentContext>(context => hostAssignmentContext.Equals(context) && !context.IsWarmupRequest))).Returns(Task.FromResult(true));

var initializationHostService = new LegionContainerInitializationHostedService(_environment, _instanceManagerMock.Object, NullLogger<LegionContainerInitializationHostedService>.Instance, _startupContextProvider);
await initializationHostService.StartAsync(CancellationToken.None);
Expand Down
Loading
Loading