Skip to content

Commit cb712cc

Browse files
committed
Add a background specialization monitor (#2201)
1 parent 51d6ac5 commit cb712cc

File tree

4 files changed

+96
-82
lines changed

4 files changed

+96
-82
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.Azure.WebJobs.Host;
66
using Microsoft.Azure.WebJobs.Script.Config;
77
using Microsoft.Azure.WebJobs.Script.Eventing;
8-
using Microsoft.Azure.WebJobs.Script.Scale;
98
using Microsoft.Azure.WebJobs.Script.WebHost.WebHooks;
109
using Microsoft.Extensions.Logging;
1110

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

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

44
using System;
5-
using System.IO;
6-
using System.Web;
75
using System.Web.Http;
86
using System.Web.Http.ExceptionHandling;
97
using Autofac;
108
using Autofac.Integration.WebApi;
119
using Microsoft.Azure.WebJobs.Script.Config;
1210
using Microsoft.Azure.WebJobs.Script.WebHost.Controllers;
13-
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
1411
using Microsoft.Azure.WebJobs.Script.WebHost.Handlers;
1512

1613
namespace Microsoft.Azure.WebJobs.Script.WebHost

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

Lines changed: 94 additions & 77 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;
78
using System.Threading.Tasks;
89
using Microsoft.Azure.WebJobs.Script.Config;
910
using Microsoft.Azure.WebJobs.Script.Eventing;
@@ -15,21 +16,19 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost
1516
{
1617
public sealed class WebHostResolver : IDisposable
1718
{
19+
private static ScriptSettingsManager _settingsManager;
20+
private static ILoggerFactory _emptyLoggerFactory = new LoggerFactory();
1821
private static object _syncLock = new object();
1922

2023
private readonly ISecretManagerFactory _secretManagerFactory;
2124
private readonly IScriptEventManager _eventManager;
2225
private ScriptHostConfiguration _standbyScriptHostConfig;
2326
private WebScriptHostManager _standbyHostManager;
2427
private WebHookReceiverManager _standbyReceiverManager;
25-
2628
private ScriptHostConfiguration _activeScriptHostConfig;
2729
private WebScriptHostManager _activeHostManager;
2830
private WebHookReceiverManager _activeReceiverManager;
29-
30-
private static ScriptSettingsManager _settingsManager;
31-
32-
private static ILoggerFactory _emptyLoggerFactory = new LoggerFactory();
31+
private Timer _specializationTimer;
3332

3433
public WebHostResolver(ScriptSettingsManager settingsManager, ISecretManagerFactory secretManagerFactory, IScriptEventManager eventManager)
3534
{
@@ -61,17 +60,7 @@ public ISwaggerDocumentManager GetSwaggerDocumentManager(WebHostSettings setting
6160

6261
public ScriptHostConfiguration GetScriptHostConfiguration(WebHostSettings settings)
6362
{
64-
if (_activeScriptHostConfig != null)
65-
{
66-
return _activeScriptHostConfig;
67-
}
68-
69-
lock (_syncLock)
70-
{
71-
EnsureInitialized(settings);
72-
73-
return _activeScriptHostConfig ?? _standbyScriptHostConfig;
74-
}
63+
return GetActiveInstance(settings, ref _activeScriptHostConfig, ref _standbyScriptHostConfig);
7564
}
7665

7766
public ISecretManager GetSecretManager(WebHostSettings settings)
@@ -81,83 +70,79 @@ public ISecretManager GetSecretManager(WebHostSettings settings)
8170

8271
public WebScriptHostManager GetWebScriptHostManager(WebHostSettings settings)
8372
{
84-
if (_activeHostManager != null)
85-
{
86-
return _activeHostManager;
87-
}
88-
89-
lock (_syncLock)
90-
{
91-
EnsureInitialized(settings);
92-
93-
return _activeHostManager ?? _standbyHostManager;
94-
}
73+
return GetActiveInstance(settings, ref _activeHostManager, ref _standbyHostManager);
9574
}
9675

9776
public WebHookReceiverManager GetWebHookReceiverManager(WebHostSettings settings)
9877
{
99-
if (_activeReceiverManager != null)
100-
{
101-
return _activeReceiverManager;
102-
}
103-
104-
lock (_syncLock)
105-
{
106-
EnsureInitialized(settings);
107-
108-
return _activeReceiverManager ?? _standbyReceiverManager;
109-
}
78+
return GetActiveInstance(settings, ref _activeReceiverManager, ref _standbyReceiverManager);
11079
}
11180

81+
/// <summary>
82+
/// This method ensures that all services managed by this class are initialized
83+
/// correctly taking into account specialization state transitions.
84+
/// </summary>
11285
internal void EnsureInitialized(WebHostSettings settings)
11386
{
114-
if (!WebScriptHostManager.InStandbyMode)
87+
lock (_syncLock)
11588
{
116-
// standby mode can only change from true to false
117-
// when standby mode changes, we reset all instances
118-
if (_activeHostManager == null)
89+
if (!WebScriptHostManager.InStandbyMode)
11990
{
120-
_settingsManager.Reset();
91+
// standby mode can only change from true to false
92+
// when standby mode changes, we reset all instances
93+
if (_activeHostManager == null)
94+
{
95+
_settingsManager.Reset();
96+
_specializationTimer?.Dispose();
97+
_specializationTimer = null;
12198

122-
_activeScriptHostConfig = CreateScriptHostConfiguration(settings);
123-
_activeHostManager = new WebScriptHostManager(_activeScriptHostConfig, _secretManagerFactory, _eventManager, _settingsManager, settings);
124-
_activeReceiverManager = new WebHookReceiverManager(_activeHostManager.SecretManager);
125-
InitializeFileSystem();
99+
_activeScriptHostConfig = CreateScriptHostConfiguration(settings);
100+
_activeHostManager = new WebScriptHostManager(_activeScriptHostConfig, _secretManagerFactory, _eventManager, _settingsManager, settings);
101+
_activeReceiverManager = new WebHookReceiverManager(_activeHostManager.SecretManager);
102+
InitializeFileSystem();
126103

127-
if (_standbyHostManager != null)
128-
{
129-
// we're starting the one and only one
130-
// standby mode specialization
131-
_activeScriptHostConfig.TraceWriter.Info(Resources.HostSpecializationTrace);
132-
133-
// After specialization, we need to ensure that custom timezone
134-
// settings configured by the user (WEBSITE_TIME_ZONE) are honored.
135-
// DateTime caches timezone information, so we need to clear the cache.
136-
TimeZoneInfo.ClearCachedData();
137-
}
104+
if (_standbyHostManager != null)
105+
{
106+
// we're starting the one and only one
107+
// standby mode specialization
108+
_activeScriptHostConfig.TraceWriter.Info(Resources.HostSpecializationTrace);
109+
110+
// After specialization, we need to ensure that custom timezone
111+
// settings configured by the user (WEBSITE_TIME_ZONE) are honored.
112+
// DateTime caches timezone information, so we need to clear the cache.
113+
TimeZoneInfo.ClearCachedData();
114+
}
138115

139-
if (_standbyHostManager != null)
140-
{
141-
_standbyHostManager.Stop();
142-
_standbyHostManager.Dispose();
116+
if (_standbyHostManager != null)
117+
{
118+
_standbyHostManager.Stop();
119+
_standbyHostManager.Dispose();
120+
}
121+
_standbyReceiverManager?.Dispose();
122+
_standbyScriptHostConfig = null;
123+
_standbyHostManager = null;
124+
_standbyReceiverManager = null;
143125
}
144-
_standbyReceiverManager?.Dispose();
145-
_standbyScriptHostConfig = null;
146-
_standbyHostManager = null;
147-
_standbyReceiverManager = null;
148126
}
149-
}
150-
else
151-
{
152-
if (_standbyHostManager == null)
127+
else
153128
{
154-
var standbySettings = CreateStandbySettings(settings);
155-
_standbyScriptHostConfig = CreateScriptHostConfiguration(standbySettings, true);
156-
_standbyHostManager = new WebScriptHostManager(_standbyScriptHostConfig, _secretManagerFactory, _eventManager, _settingsManager, standbySettings);
157-
_standbyReceiverManager = new WebHookReceiverManager(_standbyHostManager.SecretManager);
158-
159-
InitializeFileSystem();
160-
StandbyManager.Initialize(_standbyScriptHostConfig);
129+
// we're in standby (placeholder) mode
130+
if (_standbyHostManager == null)
131+
{
132+
var standbySettings = CreateStandbySettings(settings);
133+
_standbyScriptHostConfig = CreateScriptHostConfiguration(standbySettings, true);
134+
_standbyHostManager = new WebScriptHostManager(_standbyScriptHostConfig, _secretManagerFactory, _eventManager, _settingsManager, standbySettings);
135+
_standbyReceiverManager = new WebHookReceiverManager(_standbyHostManager.SecretManager);
136+
137+
InitializeFileSystem();
138+
StandbyManager.Initialize(_standbyScriptHostConfig);
139+
140+
// start a background timer to identify when specialization happens
141+
// specialization usually happens via an http request (e.g. scale controller
142+
// ping) but this timer is started as well to handle cases where we
143+
// might not receive a request
144+
_specializationTimer = new Timer(OnSpecializationTimerTick, settings, 1000, 1000);
145+
}
161146
}
162147
}
163148
}
@@ -206,6 +191,37 @@ internal static ScriptHostConfiguration CreateScriptHostConfiguration(WebHostSet
206191
return scriptHostConfig;
207192
}
208193

194+
/// <summary>
195+
/// Helper function used to manage active/standby transitions for objects managed
196+
/// by this class.
197+
/// </summary>
198+
private TInstance GetActiveInstance<TInstance>(WebHostSettings settings, ref TInstance activeInstance, ref TInstance standbyInstance)
199+
{
200+
if (activeInstance != null)
201+
{
202+
// if we have an active (specialized) instance, return it
203+
return activeInstance;
204+
}
205+
206+
// we're either uninitialized or in standby mode
207+
// perform intitialization
208+
EnsureInitialized(settings);
209+
210+
if (activeInstance != null)
211+
{
212+
return activeInstance;
213+
}
214+
else
215+
{
216+
return standbyInstance;
217+
}
218+
}
219+
220+
private void OnSpecializationTimerTick(object state)
221+
{
222+
EnsureInitialized((WebHostSettings)state);
223+
}
224+
209225
private static void InitializeFileSystem()
210226
{
211227
if (ScriptSettingsManager.Instance.IsAzureEnvironment)
@@ -252,6 +268,7 @@ public void Dispose()
252268

253269
_activeHostManager?.Dispose();
254270
_activeReceiverManager?.Dispose();
271+
_specializationTimer?.Dispose();
255272
}
256273
}
257274
}

src/WebJobs.Script.WebHost/GlobalSuppressions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,5 @@
108108
[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)")]
109109
[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)")]
110110
[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)")]
111-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "settings", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#GetPerformanceManager(Microsoft.Azure.WebJobs.Script.WebHost.WebHostSettings)")]
111+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "settings", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#GetPerformanceManager(Microsoft.Azure.WebJobs.Script.WebHost.WebHostSettings)")]
112+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_specializationTimer", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.WebHostResolver.#Dispose()")]

0 commit comments

Comments
 (0)