diff --git a/Kudu.Console/Program.cs b/Kudu.Console/Program.cs index 786c2c14e..ff628f5e0 100644 --- a/Kudu.Console/Program.cs +++ b/Kudu.Console/Program.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net; using System.Net.Http; +using System.Threading.Tasks; using Kudu.Console.Services; using Kudu.Contracts.Infrastructure; using Kudu.Contracts.Settings; @@ -163,8 +164,16 @@ private static int Main(string[] args) { try { - deploymentManager.DeployAsync(gitRepository, changeSet: null, deployer: deployer, clean: false) - .Wait(); + // although the api is called DeployAsync, most expensive works are done synchronously. + // need to launch separate task to go async explicitly (consistent with FetchDeploymentManager) + var deploymentTask = Task.Run(async () => await deploymentManager.DeployAsync(gitRepository, changeSet: null, deployer: deployer, clean: false)); + +#pragma warning disable 4014 + // Track pending task + PostDeploymentHelper.TrackPendingOperation(deploymentTask, TimeSpan.Zero); +#pragma warning restore 4014 + + deploymentTask.Wait(); if (PostDeploymentHelper.IsAutoSwapEnabled()) { diff --git a/Kudu.Core/Deployment/FetchDeploymentManager.cs b/Kudu.Core/Deployment/FetchDeploymentManager.cs index 18b693e73..7a5f86945 100644 --- a/Kudu.Core/Deployment/FetchDeploymentManager.cs +++ b/Kudu.Core/Deployment/FetchDeploymentManager.cs @@ -369,6 +369,11 @@ private bool ShouldDeploy(IRepository repository, DeploymentInfoBase deploymentI } }); +#pragma warning disable 4014 + // Track pending task + PostDeploymentHelper.TrackPendingOperation(deploymentTask, TimeSpan.Zero); +#pragma warning restore 4014 + // When the frontend/ARM calls /deploy with isAsync=true, it starts polling // the deployment status immediately, so it's important that the temp deployment // is created before we return. diff --git a/Kudu.Core/Helpers/PostDeploymentHelper.cs b/Kudu.Core/Helpers/PostDeploymentHelper.cs index 1c5b525f0..0360aef85 100644 --- a/Kudu.Core/Helpers/PostDeploymentHelper.cs +++ b/Kudu.Core/Helpers/PostDeploymentHelper.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using System.Web.Script.Serialization; using Kudu.Contracts.Settings; -using Microsoft.Win32.SafeHandles; +using Kudu.Core.Infrastructure; namespace Kudu.Core.Helpers { @@ -394,6 +394,51 @@ public static void RunPostDeploymentScripts(TraceListener tracer) } } + /// + /// As long as the task was not completed, we will keep updating the marker file. + /// The routine completes when either task completes or timeout. + /// If task is completed, we will remove the marker. + /// If timeout, we will leave the stale marker. + /// + public static async Task TrackPendingOperation(Task task, TimeSpan timeout) + { + const int DefaultTimeoutMinutes = 30; + const int DefaultUpdateMarkerIntervalMS = 10000; + const string MarkerFilePath = @"%TEMP%\SCMPendingOperation.txt"; + + // only applicable to azure env + if (!Environment.IsAzureEnvironment()) + { + return; + } + + if (timeout <= TimeSpan.Zero || timeout >= TimeSpan.FromMinutes(DefaultTimeoutMinutes)) + { + // track at most N mins by default + timeout = TimeSpan.FromMinutes(DefaultTimeoutMinutes); + } + + var start = DateTime.UtcNow; + var markerFile = System.Environment.ExpandEnvironmentVariables(MarkerFilePath); + while (start.Add(timeout) >= DateTime.UtcNow) + { + // create or update marker timestamp + OperationManager.SafeExecute(() => File.WriteAllText(markerFile, start.ToString("o"))); + + var cancelation = new CancellationTokenSource(); + var delay = Task.Delay(DefaultUpdateMarkerIntervalMS, cancelation.Token); + var completed = await Task.WhenAny(delay, task); + if (completed != delay) + { + cancelation.Cancel(); + break; + } + } + + // remove marker + OperationManager.SafeExecute(() => File.Delete(markerFile)); + } + private static void ExecuteScript(string file) { var fi = new FileInfo(file); diff --git a/Kudu.Core/SiteExtensions/SiteExtensionManager.cs b/Kudu.Core/SiteExtensions/SiteExtensionManager.cs index c3c26d1cb..ffaf81c49 100644 --- a/Kudu.Core/SiteExtensions/SiteExtensionManager.cs +++ b/Kudu.Core/SiteExtensions/SiteExtensionManager.cs @@ -18,6 +18,7 @@ using Kudu.Contracts.SiteExtensions; using Kudu.Contracts.Tracing; using Kudu.Core.Deployment.Generator; +using Kudu.Core.Helpers; using Kudu.Core.Infrastructure; using Kudu.Core.Settings; using Kudu.Core.Tracing; @@ -210,8 +211,19 @@ public async Task GetLocalExtension(string id, bool checkLate return info; } - // - public async Task InstallExtension(string id, string version, string feedUrl, SiteExtensionInfo.SiteExtensionType type, ITracer tracer, string installationArgs = null) + public Task InstallExtension(string id, string version, string feedUrl, SiteExtensionInfo.SiteExtensionType type, ITracer tracer, string installationArgs = null) + { + var installationTask = InstallExtensionCore(id, version, feedUrl, type, tracer, installationArgs); + +#pragma warning disable 4014 + // Track pending task + PostDeploymentHelper.TrackPendingOperation(installationTask, TimeSpan.Zero); +#pragma warning restore 4014 + + return installationTask; + } + + private async Task InstallExtensionCore(string id, string version, string feedUrl, SiteExtensionInfo.SiteExtensionType type, ITracer tracer, string installationArgs) { try {