Skip to content

Commit 642d3b1

Browse files
committed
Placeholder mode updates
1 parent 0bf733e commit 642d3b1

File tree

19 files changed

+361
-178
lines changed

19 files changed

+361
-178
lines changed

src/WebJobs.Script.WebHost/App_Start/WebHostResolver.cs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,23 @@ public WebHookReceiverManager GetWebHookReceiverManager(WebHostSettings settings
9797

9898
private void EnsureInitialized(WebHostSettings settings)
9999
{
100-
// standby mode can only change from true to false
101-
// When standby mode changes, we reset all instances
102-
var standbyMode = WebScriptHostManager.InStandbyMode;
103-
if (!standbyMode)
100+
if (!WebScriptHostManager.InStandbyMode)
104101
{
102+
// standby mode can only change from true to false
103+
// When standby mode changes, we reset all instances
105104
if (_activeHostManager == null)
106105
{
107106
_activeScriptHostConfig = CreateScriptHostConfiguration(settings);
108-
109107
_activeHostManager = new WebScriptHostManager(_activeScriptHostConfig, _secretManagerFactory, _eventManager, _settingsManager, settings);
110108
_activeReceiverManager = new WebHookReceiverManager(_activeHostManager.SecretManager);
109+
InitializeFileSystem();
110+
111+
// here we're undergoing the one and only one
112+
// standby mode specialization
113+
_activeScriptHostConfig.TraceWriter.Info("Host has been specialized");
111114

112115
_standbyHostManager?.Dispose();
113116
_standbyReceiverManager?.Dispose();
114-
115117
_standbyScriptHostConfig = null;
116118
_standbyHostManager = null;
117119
_standbyReceiverManager = null;
@@ -122,19 +124,30 @@ private void EnsureInitialized(WebHostSettings settings)
122124
{
123125
if (_standbyHostManager == null)
124126
{
125-
_standbyScriptHostConfig = CreateScriptHostConfiguration(settings);
126-
127+
_standbyScriptHostConfig = CreateStandbyScriptHostConfiguration(settings);
128+
StandbyManager.Initialize(_standbyScriptHostConfig);
127129
_standbyHostManager = new WebScriptHostManager(_standbyScriptHostConfig, _secretManagerFactory, _eventManager, _settingsManager, settings);
128130
_standbyReceiverManager = new WebHookReceiverManager(_standbyHostManager.SecretManager);
131+
InitializeFileSystem();
129132
}
130133
}
131134
}
132135

133-
internal static ScriptHostConfiguration CreateScriptHostConfiguration(WebHostSettings settings)
136+
internal static ScriptHostConfiguration CreateStandbyScriptHostConfiguration(WebHostSettings settings)
134137
{
135-
InitializeFileSystem(settings.ScriptPath);
138+
var scriptHostConfig = CreateScriptHostConfiguration(settings);
139+
140+
scriptHostConfig.FileLoggingMode = FileLoggingMode.Always;
141+
scriptHostConfig.HostConfig.StorageConnectionString = null;
142+
scriptHostConfig.HostConfig.DashboardConnectionString = null;
143+
scriptHostConfig.RootScriptPath = Path.Combine(Path.GetTempPath(), "Functions", "Standby");
144+
145+
return scriptHostConfig;
146+
}
136147

137-
var scriptHostConfig = new ScriptHostConfiguration()
148+
internal static ScriptHostConfiguration CreateScriptHostConfiguration(WebHostSettings settings)
149+
{
150+
var scriptHostConfig = new ScriptHostConfiguration
138151
{
139152
RootScriptPath = settings.ScriptPath,
140153
RootLogPath = settings.LogPath,
@@ -149,18 +162,18 @@ internal static ScriptHostConfiguration CreateScriptHostConfiguration(WebHostSet
149162
return scriptHostConfig;
150163
}
151164

152-
private static void InitializeFileSystem(string scriptPath)
165+
private static void InitializeFileSystem()
153166
{
154167
if (ScriptSettingsManager.Instance.IsAzureEnvironment)
155168
{
156-
// When running on Azure, we kick this off on the background
157169
Task.Run(() =>
158170
{
159171
string home = ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.AzureWebsiteHomePath);
160172
if (!string.IsNullOrEmpty(home))
161173
{
162174
// Delete hostingstart.html if any. Azure creates that in all sites by default
163-
string hostingStart = Path.Combine(scriptPath, "hostingstart.html");
175+
string siteRootPath = Path.Combine(home, @"site\wwwroot");
176+
string hostingStart = Path.Combine(siteRootPath, "hostingstart.html");
164177
if (File.Exists(hostingStart))
165178
{
166179
File.Delete(hostingStart);
@@ -186,11 +199,6 @@ private static void InitializeFileSystem(string scriptPath)
186199
}
187200
});
188201
}
189-
else
190-
{
191-
// Ensure we have our scripts directory in non-Azure scenarios
192-
Directory.CreateDirectory(scriptPath);
193-
}
194202
}
195203

196204
public void Dispose()

src/WebJobs.Script.WebHost/GlobalSuppressions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,5 @@
110110
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.DefaultSecretsRepositoryFactory.#Create(Microsoft.Azure.WebJobs.Script.Config.ScriptSettingsManager,Microsoft.Azure.WebJobs.Script.WebHost.WebHostSettings,Microsoft.Azure.WebJobs.Script.ScriptHostConfiguration)")]
111111
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant")]
112112
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.DefaultSecretManagerFactory.#Create(Microsoft.Azure.WebJobs.Script.Config.ScriptSettingsManager,Microsoft.Azure.WebJobs.Host.TraceWriter,Microsoft.Extensions.Logging.ILoggerFactory,Microsoft.Azure.WebJobs.Script.WebHost.ISecretsRepository)")]
113-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.SecretManager.#.ctor(Microsoft.Azure.WebJobs.Script.WebHost.ISecretsRepository,Microsoft.Azure.WebJobs.Script.WebHost.IKeyValueConverterFactory,Microsoft.Azure.WebJobs.Host.TraceWriter,Microsoft.Extensions.Logging.ILoggerFactory,System.Boolean)")]
113+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.SecretManager.#.ctor(Microsoft.Azure.WebJobs.Script.WebHost.ISecretsRepository,Microsoft.Azure.WebJobs.Script.WebHost.IKeyValueConverterFactory,Microsoft.Azure.WebJobs.Host.TraceWriter,Microsoft.Extensions.Logging.ILoggerFactory,System.Boolean)")]
114+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "host", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.StandbyManager.#WarmUp(Microsoft.Azure.WebJobs.Script.ScriptHost)")]

src/WebJobs.Script.WebHost/Handlers/WebScriptHostHandler.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Net;
65
using System.Net.Http;
76
using System.Threading;
87
using System.Threading.Tasks;
@@ -13,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Handlers
1312
public class WebScriptHostHandler : DelegatingHandler
1413
{
1514
public const int HostTimeoutSeconds = 30;
16-
public const int HostPollingIntervalMilliseconds = 500;
15+
public const int HostPollingIntervalMilliseconds = 25;
1716

1817
private readonly int _hostTimeoutSeconds;
1918
private readonly int _hostRunningPollIntervalMilliseconds;
@@ -49,9 +48,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
4948
}
5049

5150
// some routes do not require the host to be running (most do)
52-
// in standby mode, we don't want to wait for host start
53-
bool bypassHostCheck = request.RequestUri.LocalPath.Trim('/').ToLowerInvariant().EndsWith("admin/host/status") ||
54-
WebScriptHostManager.InStandbyMode;
51+
bool bypassHostCheck = request.MatchRoute("admin/host/status");
5552
if (!bypassHostCheck)
5653
{
5754
// If the host is not running, we'll wait a bit for it to fully
@@ -60,6 +57,11 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
6057
await scriptHostManager.DelayUntilHostReady(_hostTimeoutSeconds, _hostRunningPollIntervalMilliseconds);
6158
}
6259

60+
if (StandbyManager.IsWarmUpRequest(request))
61+
{
62+
await StandbyManager.WarmUp(request, scriptHostManager);
63+
}
64+
6365
return await base.SendAsync(request, cancellationToken);
6466
}
6567

src/WebJobs.Script.WebHost/Resources/Functions/Test-CSharp/function.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/WebJobs.Script.WebHost/Resources/Functions/Test-CSharp/run.csx

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"bindings": [
3+
{
4+
"type": "httpTrigger",
5+
"authLevel": "anonymous",
6+
"name": "req",
7+
"direction": "in",
8+
"methods": [ "get", "post" ],
9+
"route": "{x:regex(^(warmup|csharphttpwarmup)$)}"
10+
},
11+
{
12+
"type": "http",
13+
"name": "$return",
14+
"direction": "out"
15+
}
16+
]
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Net;
2+
3+
public static Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
4+
{
5+
log.Info("WarmUp function invoked.");
6+
7+
var res = new HttpResponseMessage(HttpStatusCode.OK)
8+
{
9+
Content = new StringContent("WarmUp complete.")
10+
};
11+
12+
return Task.FromResult(res);
13+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"id": "12345"
2+
"id": "placeholder-host"
33
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.IO;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Reflection;
8+
using System.Threading.Tasks;
9+
using Microsoft.Azure.WebJobs.Host;
10+
using Microsoft.Azure.WebJobs.Script.Config;
11+
using Microsoft.Azure.WebJobs.Script.Description;
12+
13+
namespace Microsoft.Azure.WebJobs.Script.WebHost
14+
{
15+
/// <summary>
16+
/// Contains methods related to standby mode (placeholder) app initialization.
17+
/// </summary>
18+
public static class StandbyManager
19+
{
20+
private const string WarmUpFunctionName = "WarmUp";
21+
private const string WarmUpAlternateRoute = "CSharpHttpWarmup";
22+
private static object _syncLock = new object();
23+
24+
public static async Task<HttpResponseMessage> WarmUp(HttpRequestMessage request, WebScriptHostManager scriptHostManager)
25+
{
26+
var queryParams = request.GetQueryParameterDictionary();
27+
string value = null;
28+
if (queryParams.TryGetValue("restart", out value) && string.Compare("1", value) == 0)
29+
{
30+
scriptHostManager.RestartHost();
31+
await scriptHostManager.DelayUntilHostReady();
32+
}
33+
34+
await StandbyManager.WarmUp(scriptHostManager.Instance);
35+
36+
return new HttpResponseMessage(HttpStatusCode.OK);
37+
}
38+
39+
public static bool IsWarmUpRequest(HttpRequestMessage request)
40+
{
41+
return ScriptSettingsManager.Instance.IsAzureEnvironment &&
42+
WebScriptHostManager.InStandbyMode &&
43+
request.IsAntaresInternalRequest() &&
44+
(request.MatchRoute($"api/{WarmUpFunctionName}") || request.MatchRoute($"api/{WarmUpAlternateRoute}"));
45+
}
46+
47+
public static async Task WarmUp(ScriptHost host)
48+
{
49+
// exercise the Node pipeline
50+
await NodeFunctionInvoker.InitializeAsync();
51+
}
52+
53+
public static void Initialize(ScriptHostConfiguration config)
54+
{
55+
CreateStandbyFunctions(config.RootScriptPath, config.TraceWriter);
56+
}
57+
58+
private static void CreateStandbyFunctions(string scriptPath, TraceWriter traceWriter)
59+
{
60+
lock (_syncLock)
61+
{
62+
traceWriter.Info($"Creating StandbyMode placeholder function directory ({scriptPath})");
63+
64+
FileUtility.DeleteDirectoryAsync(scriptPath, true).GetAwaiter().GetResult();
65+
FileUtility.EnsureDirectoryExists(scriptPath);
66+
67+
string content = ReadResourceString("Functions.host.json");
68+
File.WriteAllText(Path.Combine(scriptPath, "host.json"), content);
69+
70+
string functionPath = Path.Combine(scriptPath, WarmUpFunctionName);
71+
Directory.CreateDirectory(functionPath);
72+
content = ReadResourceString($"Functions.{WarmUpFunctionName}.function.json");
73+
File.WriteAllText(Path.Combine(functionPath, "function.json"), content);
74+
content = ReadResourceString($"Functions.{WarmUpFunctionName}.run.csx");
75+
File.WriteAllText(Path.Combine(functionPath, "run.csx"), content);
76+
77+
traceWriter.Info($"StandbyMode placeholder function directory created");
78+
}
79+
}
80+
81+
private static string ReadResourceString(string fileName)
82+
{
83+
string resourcePath = string.Format("Microsoft.Azure.WebJobs.Script.WebHost.Resources.{0}", fileName);
84+
Assembly assembly = Assembly.GetExecutingAssembly();
85+
using (StreamReader reader = new StreamReader(assembly.GetManifestResourceStream(resourcePath)))
86+
{
87+
return reader.ReadToEnd();
88+
}
89+
}
90+
}
91+
}

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@
467467
<Compile Include="App_Start\WebApiConfig.cs" />
468468
<Compile Include="App_Start\WebHostResolver.cs" />
469469
<Compile Include="App_Start\WebHostSettings.cs" />
470+
<Compile Include="StandbyManager.cs" />
470471
<Compile Include="Controllers\AdminController.cs" />
471472
<Compile Include="Controllers\FunctionsController.cs" />
472473
<Compile Include="Controllers\HomeController.cs" />
@@ -577,8 +578,8 @@
577578
<None Include="App_Data\secrets\WebHook-Generic.json" />
578579
<None Include="Properties\PublishProfiles\FileSystem.pubxml" />
579580
<EmbeddedResource Include="Resources\Functions\host.json" />
580-
<EmbeddedResource Include="Resources\Functions\Test-CSharp\function.json" />
581-
<EmbeddedResource Include="Resources\Functions\Test-CSharp\run.csx" />
581+
<EmbeddedResource Include="Resources\Functions\WarmUp\function.json" />
582+
<EmbeddedResource Include="Resources\Functions\WarmUp\run.csx" />
582583
<None Include="Web.Debug.config">
583584
<DependentUpon>Web.config</DependentUpon>
584585
</None>

0 commit comments

Comments
 (0)