3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Collections . ObjectModel ;
6
7
using System . Diagnostics ;
7
8
using System . Globalization ;
8
9
using System . IO ;
15
16
using Microsoft . Azure . WebJobs . Script . Config ;
16
17
using Microsoft . Azure . WebJobs . Script . Diagnostics ;
17
18
using Microsoft . Azure . WebJobs . Script . Eventing ;
19
+ using Microsoft . Azure . WebJobs . Script . Scale ;
18
20
using Microsoft . Extensions . Logging ;
19
21
20
22
namespace Microsoft . Azure . WebJobs . Script
@@ -34,6 +36,9 @@ public class ScriptHostManager : IScriptHostEnvironment, IDisposable
34
36
private readonly IScriptHostEnvironment _environment ;
35
37
private readonly IDisposable _fileEventSubscription ;
36
38
private readonly StructuredLogWriter _structuredLogWriter ;
39
+ private readonly HostPerformanceManager _performanceManager ;
40
+ private readonly Timer _hostHealthCheckTimer ;
41
+ private readonly TimeSpan hostHealthCheckInterval = TimeSpan . FromSeconds ( 15 ) ;
37
42
private ScriptHost _currentInstance ;
38
43
private int _hostStartCount ;
39
44
@@ -57,8 +62,9 @@ public ScriptHostManager(
57
62
ScriptHostConfiguration config ,
58
63
IScriptEventManager eventManager = null ,
59
64
IScriptHostEnvironment environment = null ,
60
- ILoggerFactoryBuilder loggerFactoryBuilder = null )
61
- : this ( config , ScriptSettingsManager . Instance , new ScriptHostFactory ( ) , eventManager , environment , loggerFactoryBuilder )
65
+ ILoggerFactoryBuilder loggerFactoryBuilder = null ,
66
+ HostPerformanceManager hostPerformanceManager = null )
67
+ : this ( config , ScriptSettingsManager . Instance , new ScriptHostFactory ( ) , eventManager , environment , loggerFactoryBuilder , hostPerformanceManager )
62
68
{
63
69
if ( config . FileWatchingEnabled )
64
70
{
@@ -75,8 +81,19 @@ public ScriptHostManager(ScriptHostConfiguration config,
75
81
IScriptHostFactory scriptHostFactory ,
76
82
IScriptEventManager eventManager = null ,
77
83
IScriptHostEnvironment environment = null ,
78
- ILoggerFactoryBuilder loggerFactoryBuilder = null )
84
+ ILoggerFactoryBuilder loggerFactoryBuilder = null ,
85
+ HostPerformanceManager hostPerformanceManager = null )
79
86
{
87
+ if ( config == null )
88
+ {
89
+ throw new ArgumentNullException ( nameof ( config ) ) ;
90
+ }
91
+ if ( settingsManager == null )
92
+ {
93
+ throw new ArgumentNullException ( nameof ( settingsManager ) ) ;
94
+ }
95
+
96
+ scriptHostFactory = scriptHostFactory ?? new ScriptHostFactory ( ) ;
80
97
_environment = environment ?? this ;
81
98
_config = config ;
82
99
_settingsManager = settingsManager ;
@@ -86,8 +103,16 @@ public ScriptHostManager(ScriptHostConfiguration config,
86
103
EventManager = eventManager ?? new ScriptEventManager ( ) ;
87
104
88
105
_structuredLogWriter = new StructuredLogWriter ( EventManager , config . RootLogPath ) ;
106
+ _performanceManager = hostPerformanceManager ?? new HostPerformanceManager ( settingsManager ) ;
107
+
108
+ if ( config . HostHealthMonitorEnabled && settingsManager . IsAzureEnvironment )
109
+ {
110
+ _hostHealthCheckTimer = new Timer ( OnHostHealthCheckTimer , null , TimeSpan . Zero , hostHealthCheckInterval ) ;
111
+ }
89
112
}
90
113
114
+ protected HostPerformanceManager PerformanceManager => _performanceManager ;
115
+
91
116
protected IScriptEventManager EventManager { get ; }
92
117
93
118
public virtual ScriptHost Instance
@@ -137,6 +162,8 @@ public bool CanInvoke()
137
162
State = ScriptHostState . Default ;
138
163
}
139
164
165
+ OnHostStarting ( ) ;
166
+
140
167
// Create a new host config, but keep the host id from existing one
141
168
_config . HostConfig = new JobHostConfiguration ( _settingsManager . Configuration )
142
169
{
@@ -372,7 +399,12 @@ protected virtual void OnHostCreated()
372
399
State = ScriptHostState . Created ;
373
400
}
374
401
375
- protected virtual void OnHostStarted ( )
402
+ protected virtual void OnHostStarting ( )
403
+ {
404
+ IsHostHealthy ( throwWhenUnhealthy : true ) ;
405
+ }
406
+
407
+ protected virtual void OnHostStarted ( )
376
408
{
377
409
var metricsLogger = _config . HostConfig . GetService < IMetricsLogger > ( ) ;
378
410
metricsLogger . LogEvent ( new HostStarted ( Instance ) ) ;
@@ -401,6 +433,7 @@ protected virtual void Dispose(bool disposing)
401
433
}
402
434
}
403
435
436
+ _hostHealthCheckTimer ? . Dispose ( ) ;
404
437
_stopEvent . Dispose ( ) ;
405
438
_restartDelayTokenSource ? . Dispose ( ) ;
406
439
_fileEventSubscription ? . Dispose ( ) ;
@@ -431,6 +464,39 @@ public virtual void Shutdown()
431
464
Process . GetCurrentProcess ( ) . Close ( ) ;
432
465
}
433
466
467
+ private void OnHostHealthCheckTimer ( object state )
468
+ {
469
+ if ( State == ScriptHostState . Running && ! IsHostHealthy ( ) )
470
+ {
471
+ // This periodic check allows us to break out of the host run
472
+ // loop. The health check performed in OnHostStarting will then
473
+ // fail and we'll enter a restart loop (exponentially backing off)
474
+ // until the host is healthy again and we can resume host processing.
475
+ RestartHost ( ) ;
476
+ }
477
+ }
478
+
479
+ internal bool IsHostHealthy ( bool throwWhenUnhealthy = false )
480
+ {
481
+ if ( ! _config . HostHealthMonitorEnabled || ! _settingsManager . IsAzureEnvironment )
482
+ {
483
+ return true ;
484
+ }
485
+
486
+ var exceededCounters = new Collection < string > ( ) ;
487
+ if ( PerformanceManager . IsUnderHighLoad ( exceededCounters ) )
488
+ {
489
+ string formattedCounters = string . Join ( ", " , exceededCounters ) ;
490
+ if ( throwWhenUnhealthy )
491
+ {
492
+ throw new InvalidOperationException ( $ "Host thresholds exceeded: [{ formattedCounters } ]") ;
493
+ }
494
+ return false ;
495
+ }
496
+
497
+ return true ;
498
+ }
499
+
434
500
public async Task < bool > DelayUntilHostReady ( int timeoutSeconds = HostCheckTimeoutSeconds , int pollingIntervalMilliseconds = HostCheckPollingIntervalMilliseconds )
435
501
{
436
502
TimeSpan timeout = TimeSpan . FromSeconds ( timeoutSeconds ) ;
0 commit comments