Skip to content

Commit 34090bd

Browse files
committed
Performance optimizations in startup path
1 parent af32927 commit 34090bd

File tree

7 files changed

+101
-75
lines changed

7 files changed

+101
-75
lines changed

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

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Threading.Tasks;
78
using System.Web;
89
using System.Web.Http;
910
using Autofac;
@@ -27,16 +28,6 @@ public static void Register(HttpConfiguration config, ScriptSettingsManager sett
2728
settingsManager = settingsManager ?? ScriptSettingsManager.Instance;
2829
settings = settings ?? GetDefaultSettings(settingsManager);
2930

30-
// Delete hostingstart.html if any. Azure creates that in all sites by default
31-
string hostingStart = Path.Combine(settings.ScriptPath, "hostingstart.html");
32-
if (File.Exists(hostingStart))
33-
{
34-
File.Delete(hostingStart);
35-
}
36-
37-
// Add necessary folders to the %PATH%
38-
PrependFoldersToEnvironmentPath(settingsManager);
39-
4031
var builder = new ContainerBuilder();
4132
builder.RegisterApiControllers(typeof(FunctionsController).Assembly);
4233
AutofacBootstrap.Initialize(settingsManager, builder, settings);
@@ -81,32 +72,6 @@ public static void Register(HttpConfiguration config, ScriptSettingsManager sett
8172
config.InitializeReceiveSalesforceWebHooks();
8273
}
8374

84-
private static void PrependFoldersToEnvironmentPath(ScriptSettingsManager settingsManager)
85-
{
86-
// Only do this when %HOME% is defined (normally on Azure)
87-
string home = settingsManager.GetSetting(EnvironmentSettingNames.AzureWebsiteHomePath);
88-
if (!string.IsNullOrEmpty(home))
89-
{
90-
// Create the tools folder if it doesn't exist
91-
string toolsPath = Path.Combine(home, @"site\tools");
92-
Directory.CreateDirectory(toolsPath);
93-
94-
var folders = new List<string>();
95-
folders.Add(Path.Combine(home, @"site\tools"));
96-
97-
string path = Environment.GetEnvironmentVariable("PATH");
98-
string additionalPaths = String.Join(";", folders);
99-
100-
// Make sure we haven't already added them. This can happen if the appdomain restart (since it's still same process)
101-
if (!path.Contains(additionalPaths))
102-
{
103-
path = additionalPaths + ";" + path;
104-
105-
Environment.SetEnvironmentVariable("PATH", path);
106-
}
107-
}
108-
}
109-
11075
private static WebHostSettings GetDefaultSettings(ScriptSettingsManager settingsManager)
11176
{
11277
WebHostSettings settings = new WebHostSettings();

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

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

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
78
using System.Reflection;
9+
using System.Threading.Tasks;
810
using Microsoft.Azure.WebJobs.Script.Config;
911
using Microsoft.Azure.WebJobs.Script.WebHost.WebHooks;
1012

@@ -110,7 +112,7 @@ private void EnsureInitialized(WebHostSettings settings)
110112
_activeSecretManager = GetSecretManager(_settingsManager, settings.SecretsPath);
111113
_activeReceiverManager = new WebHookReceiverManager(_activeSecretManager);
112114
_activeHostManager = new WebScriptHostManager(_activeScriptHostConfig, _activeSecretManager, _settingsManager, settings);
113-
115+
114116
(_standbySecretManager as IDisposable)?.Dispose();
115117
_standbyHostManager?.Dispose();
116118
_standbyReceiverManager?.Dispose();
@@ -149,22 +151,7 @@ private static void ReinitializeAppSettings()
149151

150152
private static ScriptHostConfiguration GetScriptHostConfiguration(string scriptPath, string logPath)
151153
{
152-
string home = _settingsManager.GetSetting(EnvironmentSettingNames.AzureWebsiteHomePath);
153-
if (!string.IsNullOrEmpty(home))
154-
{
155-
// Create the tools folder if it doesn't exist
156-
string toolsPath = Path.Combine(home, @"site\tools");
157-
Directory.CreateDirectory(toolsPath);
158-
}
159-
160-
Directory.CreateDirectory(scriptPath);
161-
162-
// Delete hostingstart.html if any. Azure creates that in all sites by default
163-
string hostingStart = Path.Combine(scriptPath, "hostingstart.html");
164-
if (File.Exists(hostingStart))
165-
{
166-
File.Delete(hostingStart);
167-
}
154+
InitializeFileSystem(scriptPath);
168155

169156
var scriptHostConfig = new ScriptHostConfiguration()
170157
{
@@ -193,6 +180,50 @@ private static ScriptHostConfiguration GetScriptHostConfiguration(string scriptP
193180
return scriptHostConfig;
194181
}
195182

183+
private static void InitializeFileSystem(string scriptPath)
184+
{
185+
if (ScriptSettingsManager.Instance.IsAzureEnvironment)
186+
{
187+
// When running on Azure, we kick this off on the background
188+
Task.Run(() =>
189+
{
190+
string home = ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.AzureWebsiteHomePath);
191+
if (!string.IsNullOrEmpty(home))
192+
{
193+
// Delete hostingstart.html if any. Azure creates that in all sites by default
194+
string hostingStart = Path.Combine(scriptPath, "hostingstart.html");
195+
if (File.Exists(hostingStart))
196+
{
197+
File.Delete(hostingStart);
198+
}
199+
200+
// Create the tools folder if it doesn't exist
201+
string toolsPath = Path.Combine(home, @"site\tools");
202+
Directory.CreateDirectory(toolsPath);
203+
204+
var folders = new List<string>();
205+
folders.Add(Path.Combine(home, @"site\tools"));
206+
207+
string path = Environment.GetEnvironmentVariable("PATH");
208+
string additionalPaths = String.Join(";", folders);
209+
210+
// Make sure we haven't already added them. This can happen if the appdomain restart (since it's still same process)
211+
if (!path.Contains(additionalPaths))
212+
{
213+
path = additionalPaths + ";" + path;
214+
215+
Environment.SetEnvironmentVariable("PATH", path);
216+
}
217+
}
218+
});
219+
}
220+
else
221+
{
222+
// Ensure we have our scripts directory in non-Azure scenarios
223+
Directory.CreateDirectory(scriptPath);
224+
}
225+
}
226+
196227
private static ISecretManager GetSecretManager(ScriptSettingsManager settingsManager, string secretsPath) => new SecretManager(settingsManager, secretsPath);
197228

198229
public void Dispose()

src/WebJobs.Script.WebHost/Global.asax.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,26 @@
33

44
using System;
55
using System.Web.Http;
6+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
7+
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
68

79
namespace Microsoft.Azure.WebJobs.Script.WebHost
810
{
911
public class WebApiApplication : System.Web.HttpApplication
1012
{
1113
protected void Application_Start()
1214
{
13-
GlobalConfiguration.Configure(c => WebApiConfig.Register(c));
15+
using (var metricsLogger = new WebHostMetricsLogger())
16+
using (metricsLogger.LatencyEvent(MetricEventNames.ApplicationStartLatency))
17+
{
18+
GlobalConfiguration.Configure(c => WebApiConfig.Register(c));
1419

15-
var scriptHostManager = GlobalConfiguration.Configuration.DependencyResolver.GetService<WebScriptHostManager>();
20+
var scriptHostManager = GlobalConfiguration.Configuration.DependencyResolver.GetService<WebScriptHostManager>();
1621

17-
if (scriptHostManager != null && !scriptHostManager.Initialized)
18-
{
19-
scriptHostManager.Initialize();
22+
if (scriptHostManager != null && !scriptHostManager.Initialized)
23+
{
24+
scriptHostManager.Initialize();
25+
}
2026
}
2127
}
2228

src/WebJobs.Script.WebHost/WebScriptHostManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ internal static void AddRouteDataToRequest(IHttpRouteData routeData, HttpRequest
361361
}
362362

363363
request.Properties.Add(ScriptConstants.AzureFunctionsHttpRouteDataKey, routeDataValues);
364-
}
364+
}
365365
}
366366

367367
protected override void OnInitializeConfig(ScriptHostConfiguration config)

src/WebJobs.Script/Description/Node/NodeFunctionInvoker.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ public class NodeFunctionInvoker : FunctionInvokerBase
3737
private static string _functionTemplate;
3838
private static string _clearRequireCacheScript;
3939
private static string _globalInitializationScript;
40+
private static bool _initialized = false;
41+
private static readonly object _initializationSyncRoot = new object();
4042

4143
static NodeFunctionInvoker()
4244
{
4345
// node cwd is edge nuget package (double_edge.js)
4446
_functionTemplate = @"return require('../Content/Script/functions.js').createFunction(require('{0}'));";
4547
_clearRequireCacheScript = @"return require('../Content/Script/functions.js').clearRequireCache;";
4648
_globalInitializationScript = @"return require('../Content/Script/functions.js').globalInitialization;";
47-
48-
Initialize();
4949
}
5050

5151
internal NodeFunctionInvoker(ScriptHost host, BindingMetadata trigger, FunctionMetadata functionMetadata,
@@ -109,6 +109,8 @@ private static Func<object, Task<object>> ClearRequireCacheFunc
109109

110110
protected override async Task InvokeCore(object[] parameters, FunctionInvocationContext context)
111111
{
112+
EnsureInitialized();
113+
112114
object input = parameters[0];
113115
string invocationId = context.ExecutionContext.InvocationId.ToString();
114116
DataType dataType = _trigger.DataType ?? DataType.String;
@@ -124,6 +126,20 @@ protected override async Task InvokeCore(object[] parameters, FunctionInvocation
124126
await ProcessOutputBindingsAsync(_outputBindings, input, context.Binder, bindingData, scriptExecutionContext, functionResult);
125127
}
126128

129+
private static void EnsureInitialized()
130+
{
131+
if (!_initialized)
132+
{
133+
lock (_initializationSyncRoot)
134+
{
135+
if (!_initialized)
136+
{
137+
Initialize();
138+
}
139+
}
140+
}
141+
}
142+
127143
private async Task ProcessInputBindingsAsync(Binder binder, Dictionary<string, object> executionContext, Dictionary<string, object> bindingData)
128144
{
129145
var bindings = (Dictionary<string, object>)executionContext["bindings"];

src/WebJobs.Script/Diagnostics/MetricEventNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics
66
public static class MetricEventNames
77
{
88
// host level events
9+
public const string ApplicationStartLatency = "host.application.start";
910
public const string HostStartupLatency = "host.startup.latency";
1011

1112
// function level events

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,19 @@ protected virtual void Initialize()
269269
_debugModeFileWatcher.Created += OnDebugModeFileChanged;
270270
_debugModeFileWatcher.Changed += OnDebugModeFileChanged;
271271

272+
var storageString = AmbientConnectionStringProvider.Instance.GetConnectionString(ConnectionStringNames.Storage);
273+
Task<BlobLeaseManager> blobManagerCreation = null;
274+
if (storageString == null)
275+
{
276+
// Disable core storage
277+
ScriptConfig.HostConfig.StorageConnectionString = null;
278+
blobManagerCreation = Task.FromResult<BlobLeaseManager>(null);
279+
}
280+
else
281+
{
282+
blobManagerCreation = BlobLeaseManager.CreateAsync(storageString, TimeSpan.FromSeconds(15), ScriptConfig.HostConfig.HostId, InstanceId, TraceWriter);
283+
}
284+
272285
var bindingProviders = LoadBindingProviders(ScriptConfig, hostConfig, TraceWriter);
273286
ScriptConfig.BindingProviders = bindingProviders;
274287

@@ -305,20 +318,6 @@ protected virtual void Initialize()
305318
// take a snapshot so we can detect function additions/removals
306319
_directoryCountSnapshot = Directory.EnumerateDirectories(ScriptConfig.RootScriptPath).Count();
307320

308-
var storageString = AmbientConnectionStringProvider.Instance.GetConnectionString(ConnectionStringNames.Storage);
309-
if (storageString == null)
310-
{
311-
// Disable core storage
312-
ScriptConfig.HostConfig.StorageConnectionString = null;
313-
}
314-
else
315-
{
316-
// Create the lease manager that will keep handle the primary host blob lease acquisition and renewal
317-
// and subscribe for change notifications.
318-
_blobLeaseManager = BlobLeaseManager.Create(storageString, TimeSpan.FromSeconds(15), ScriptConfig.HostConfig.HostId, InstanceId, TraceWriter);
319-
_blobLeaseManager.HasLeaseChanged += BlobLeaseManagerHasLeaseChanged;
320-
}
321-
322321
List<FunctionDescriptorProvider> descriptionProviders = new List<FunctionDescriptorProvider>()
323322
{
324323
new ScriptFunctionDescriptorProvider(this, ScriptConfig),
@@ -342,6 +341,14 @@ protected virtual void Initialize()
342341
}
343342
}
344343

344+
// Create the lease manager that will keep handle the primary host blob lease acquisition and renewal
345+
// and subscribe for change notifications.
346+
_blobLeaseManager = blobManagerCreation.GetAwaiter().GetResult();
347+
if (_blobLeaseManager != null)
348+
{
349+
_blobLeaseManager.HasLeaseChanged += BlobLeaseManagerHasLeaseChanged;
350+
}
351+
345352
// read all script functions and apply to JobHostConfiguration
346353
Collection<FunctionDescriptor> functions = ReadFunctions(descriptionProviders);
347354
Collection<CustomAttributeBuilder> typeAttributes = CreateTypeAttributes(ScriptConfig);

0 commit comments

Comments
 (0)