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
{