5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
7
using System . Globalization ;
8
+ using System . IO ;
8
9
using System . Linq ;
9
10
using System . Text ;
10
11
using System . Threading ;
11
12
using System . Threading . Tasks ;
12
13
using Microsoft . Azure . WebJobs . Host ;
13
14
using Microsoft . Azure . WebJobs . Script . Config ;
14
15
using Microsoft . Azure . WebJobs . Script . Diagnostics ;
16
+ using Microsoft . Azure . WebJobs . Script . IO ;
15
17
16
18
namespace Microsoft . Azure . WebJobs . Script
17
19
{
@@ -37,10 +39,17 @@ public class ScriptHostManager : IDisposable
37
39
private TraceWriter _traceWriter ;
38
40
39
41
private ScriptSettingsManager _settingsManager ;
42
+ private AutoRecoveringFileSystemWatcher _scriptFileWatcher ;
43
+ private CancellationTokenSource _restartDelayTokenSource ;
40
44
41
45
public ScriptHostManager ( ScriptHostConfiguration config )
42
46
: this ( config , ScriptSettingsManager . Instance , new ScriptHostFactory ( ) )
43
47
{
48
+ if ( config . FileWatchingEnabled )
49
+ {
50
+ _scriptFileWatcher = new AutoRecoveringFileSystemWatcher ( config . RootScriptPath ) ;
51
+ _scriptFileWatcher . Changed += OnScriptFileChanged ;
52
+ }
44
53
}
45
54
46
55
public ScriptHostManager ( ScriptHostConfiguration config , ScriptSettingsManager settingsManager , IScriptHostFactory scriptHostFactory )
@@ -83,8 +92,7 @@ public bool CanInvoke()
83
92
84
93
public void RunAndBlock ( CancellationToken cancellationToken = default ( CancellationToken ) )
85
94
{
86
- // Start the host and restart it if requested. Host Restarts will happen when
87
- // host level configuration files change
95
+ int consecutiveErrorCount = 0 ;
88
96
do
89
97
{
90
98
ScriptHost newInstance = null ;
@@ -116,8 +124,8 @@ public bool CanInvoke()
116
124
117
125
if ( _traceWriter != null )
118
126
{
119
- string message = string . Format ( "Starting Host (HostId={0}, Version={1}, ProcessId={2}, Debug={3})" ,
120
- newInstance . ScriptConfig . HostConfig . HostId , ScriptHost . Version , Process . GetCurrentProcess ( ) . Id , newInstance . InDebugMode . ToString ( ) ) ;
127
+ string message = string . Format ( "Starting Host (HostId={0}, Version={1}, ProcessId={2}, Debug={3}, Attempt={4} )" ,
128
+ newInstance . ScriptConfig . HostConfig . HostId , ScriptHost . Version , Process . GetCurrentProcess ( ) . Id , newInstance . InDebugMode . ToString ( ) , consecutiveErrorCount ) ;
121
129
_traceWriter . Info ( message ) ;
122
130
}
123
131
newInstance . StartAsync ( cancellationToken ) . GetAwaiter ( ) . GetResult ( ) ;
@@ -131,6 +139,8 @@ public bool CanInvoke()
131
139
// state to Running
132
140
State = ScriptHostState . Running ;
133
141
LastError = null ;
142
+ consecutiveErrorCount = 0 ;
143
+ _restartDelayTokenSource = null ;
134
144
135
145
// Wait for a restart signal. This event will automatically reset.
136
146
// While we're restarting, it is possible for another restart to be
@@ -155,6 +165,7 @@ public bool CanInvoke()
155
165
{
156
166
State = ScriptHostState . Error ;
157
167
LastError = ex ;
168
+ consecutiveErrorCount ++ ;
158
169
159
170
// We need to keep the host running, so we catch and log any errors
160
171
// then restart the host
@@ -177,15 +188,22 @@ public bool CanInvoke()
177
188
} , TaskContinuationOptions . ExecuteSynchronously ) ;
178
189
}
179
190
180
- // Wait for a short period of time before restarting to
181
- // avoid cases where a host level config error might cause
182
- // a rapid restart cycle
183
- Task . Delay ( _config . RestartInterval ) . GetAwaiter ( ) . GetResult ( ) ;
191
+ // attempt restarts using an exponential backoff strategy
192
+ CreateRestartBackoffDelay ( consecutiveErrorCount ) . GetAwaiter ( ) . GetResult ( ) ;
184
193
}
185
194
}
186
195
while ( ! _stopped && ! cancellationToken . IsCancellationRequested ) ;
187
196
}
188
197
198
+ private Task CreateRestartBackoffDelay ( int consecutiveErrorCount )
199
+ {
200
+ _restartDelayTokenSource = new CancellationTokenSource ( ) ;
201
+
202
+ return Utility . DelayWithBackoffAsync ( consecutiveErrorCount , _restartDelayTokenSource . Token ,
203
+ min : TimeSpan . FromSeconds ( 1 ) ,
204
+ max : TimeSpan . FromMinutes ( 2 ) ) ;
205
+ }
206
+
189
207
private static void LogErrors ( ScriptHost host )
190
208
{
191
209
if ( host . FunctionErrors . Count > 0 )
@@ -234,6 +252,19 @@ private async Task Orphan(ScriptHost instance, bool forceStop = false)
234
252
}
235
253
}
236
254
255
+ /// <summary>
256
+ /// Handler for any file changes under the root script path
257
+ /// </summary>
258
+ private void OnScriptFileChanged ( object sender , FileSystemEventArgs e )
259
+ {
260
+ if ( _restartDelayTokenSource != null && ! _restartDelayTokenSource . IsCancellationRequested )
261
+ {
262
+ // if we're currently awaiting an error/restart delay
263
+ // cancel that
264
+ _restartDelayTokenSource . Cancel ( ) ;
265
+ }
266
+ }
267
+
237
268
public async Task StopAsync ( )
238
269
{
239
270
_stopped = true ;
@@ -326,6 +357,8 @@ protected virtual void Dispose(bool disposing)
326
357
}
327
358
328
359
_stopEvent . Dispose ( ) ;
360
+ _restartDelayTokenSource ? . Dispose ( ) ;
361
+ _scriptFileWatcher ? . Dispose ( ) ;
329
362
330
363
_disposed = true ;
331
364
}
0 commit comments