Skip to content

Commit 10e57bd

Browse files
committed
Making ScriptHostManager error handling more robust (Keeping host alive)
1 parent 01e37fa commit 10e57bd

File tree

4 files changed

+66
-46
lines changed

4 files changed

+66
-46
lines changed

CustomDictionary.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
<Word>ScopeId</Word>
7474
<Word>FunctionMetadata</Word>
7575
<Word>Debounce</Word>
76+
<Word>ScriptHost</Word>
7677
</Recognized>
7778
<Deprecated/>
7879
<Compound>
@@ -92,6 +93,7 @@
9293
<Term>SingletonAttribute</Term>
9394
<Term>ScopeId</Term>
9495
<Term>FunctionMetadata</Term>
96+
<Term>ScriptHost</Term>
9597
</DiscreteExceptions>
9698
</Words>
9799
<Acronyms>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ internal static void Initialize(ContainerBuilder builder)
6060
WebHookReceiverManager webHookRecieverManager = new WebHookReceiverManager(secretManager);
6161
builder.RegisterInstance<WebHookReceiverManager>(webHookRecieverManager);
6262

63-
Task.Run(() => scriptHostManager.RunAndBlock(CancellationToken.None));
63+
HostingEnvironment.QueueBackgroundWorkItem((ct) => scriptHostManager.RunAndBlock(ct));
6464
}
6565
}
6666
}

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public class ScriptHost : JobHost
2424
private const string HostAssemblyName = "ScriptHost";
2525
private const string HostConfigFileName = "host.json";
2626
internal const string FunctionConfigFileName = "function.json";
27-
private readonly TraceWriter _traceWriter;
2827
private readonly AutoResetEvent _restartEvent = new AutoResetEvent(false);
2928
private Action<FileSystemEventArgs> _restart;
3029
private FileSystemWatcher _fileWatcher;
@@ -38,12 +37,12 @@ protected ScriptHost(ScriptHostConfiguration scriptConfig)
3837
if (scriptConfig.FileLoggingEnabled)
3938
{
4039
string hostLogFilePath = Path.Combine(scriptConfig.RootLogPath, "Host");
41-
_traceWriter = new FileTraceWriter(hostLogFilePath, TraceLevel.Verbose);
42-
scriptConfig.HostConfig.Tracing.Tracers.Add(_traceWriter);
40+
TraceWriter = new FileTraceWriter(hostLogFilePath, TraceLevel.Verbose);
41+
scriptConfig.HostConfig.Tracing.Tracers.Add(TraceWriter);
4342
}
4443
else
4544
{
46-
_traceWriter = NullTraceWriter.Instance;
45+
TraceWriter = NullTraceWriter.Instance;
4746
}
4847

4948
if (scriptConfig.TraceWriter != null)
@@ -75,8 +74,8 @@ protected ScriptHost(ScriptHostConfiguration scriptConfig)
7574
// restart after ALL the operations are complete and there is a quiet period.
7675
_restart = (e) =>
7776
{
78-
_traceWriter.Verbose(string.Format(CultureInfo.InvariantCulture, "File change of type '{0}' detected for '{1}'", e.ChangeType, e.FullPath));
79-
_traceWriter.Verbose("Host configuration has changed. Signaling restart.");
77+
TraceWriter.Verbose(string.Format(CultureInfo.InvariantCulture, "File change of type '{0}' detected for '{1}'", e.ChangeType, e.FullPath));
78+
TraceWriter.Verbose("Host configuration has changed. Signaling restart.");
8079

8180
// signal host restart
8281
_restartEvent.Set();
@@ -87,6 +86,8 @@ protected ScriptHost(ScriptHostConfiguration scriptConfig)
8786
_directoryCountSnapshot = Directory.EnumerateDirectories(ScriptConfig.RootScriptPath).Count();
8887
}
8988

89+
public TraceWriter TraceWriter { get; private set; }
90+
9091
public ScriptHostConfiguration ScriptConfig { get; private set; }
9192

9293
public Collection<FunctionDescriptor> Functions { get; private set; }
@@ -134,7 +135,7 @@ protected virtual void Initialize()
134135
File.WriteAllText(hostConfigFilePath, "{}");
135136
}
136137

137-
_traceWriter.Verbose(string.Format(CultureInfo.InvariantCulture, "Reading host configuration file '{0}'", hostConfigFilePath));
138+
TraceWriter.Verbose(string.Format(CultureInfo.InvariantCulture, "Reading host configuration file '{0}'", hostConfigFilePath));
138139
string json = File.ReadAllText(hostConfigFilePath);
139140
JObject hostConfig = JObject.Parse(json);
140141
ApplyConfiguration(hostConfig, ScriptConfig);
@@ -143,7 +144,7 @@ protected virtual void Initialize()
143144
Collection<FunctionDescriptor> functions = ReadFunctions(ScriptConfig, descriptionProviders);
144145
string defaultNamespace = "Host";
145146
string typeName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", defaultNamespace, "Functions");
146-
_traceWriter.Verbose(string.Format(CultureInfo.InvariantCulture, "Generating {0} job function(s)", functions.Count));
147+
TraceWriter.Verbose(string.Format(CultureInfo.InvariantCulture, "Generating {0} job function(s)", functions.Count));
147148
Type type = FunctionGenerator.Generate(HostAssemblyName, typeName, functions);
148149
List<Type> types = new List<Type>();
149150
types.Add(type);
@@ -168,14 +169,13 @@ public static ScriptHost Create(ScriptHostConfiguration scriptConfig = null)
168169
}
169170

170171
ScriptHost scriptHost = new ScriptHost(scriptConfig);
171-
172172
try
173173
{
174174
scriptHost.Initialize();
175175
}
176-
catch (Exception e)
176+
catch (Exception ex)
177177
{
178-
scriptHost._traceWriter.Error("Script Host initialization failed", e);
178+
scriptHost.TraceWriter.Error("ScriptHost initialization failed", ex);
179179
throw;
180180
}
181181

src/WebJobs.Script/Host/ScriptHostManager.cs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.Azure.WebJobs.Host;
910

1011
namespace Microsoft.Azure.WebJobs.Script
1112
{
@@ -27,6 +28,7 @@ public class ScriptHostManager : IDisposable
2728
private bool _disposed;
2829
private bool _stopped;
2930
private AutoResetEvent _stopEvent = new AutoResetEvent(false);
31+
private TraceWriter _traceWriter;
3032

3133
public ScriptHostManager(ScriptHostConfiguration config)
3234
{
@@ -53,44 +55,60 @@ public ScriptHost Instance
5355
// host level configuration files change
5456
do
5557
{
56-
IsRunning = false;
57-
58-
// Create a new host config, but keep the host id from existing one
59-
_config.HostConfig = new JobHostConfiguration
58+
try
6059
{
61-
HostId = _config.HostConfig.HostId
62-
};
63-
ScriptHost newInstance = ScriptHost.Create(_config);
64-
65-
// TODO: consider using StartAsync here to speed up
66-
// restarts.
67-
newInstance.Start();
68-
lock (_liveInstances)
60+
IsRunning = false;
61+
62+
// Create a new host config, but keep the host id from existing one
63+
_config.HostConfig = new JobHostConfiguration
64+
{
65+
HostId = _config.HostConfig.HostId
66+
};
67+
ScriptHost newInstance = ScriptHost.Create(_config);
68+
_traceWriter = newInstance.TraceWriter;
69+
70+
newInstance.Start();
71+
lock (_liveInstances)
72+
{
73+
_liveInstances.Add(newInstance);
74+
}
75+
_currentInstance = newInstance;
76+
OnHostStarted();
77+
78+
// only after ALL initialization is complete do we set this flag
79+
IsRunning = true;
80+
81+
// Wait for a restart signal. This event will automatically reset.
82+
// While we're restarting, it is possible for another restart to be
83+
// signaled. That is fine - the restart will be processed immediately
84+
// once we get to this line again. The important thing is that these
85+
// restarts are only happening on a single thread.
86+
WaitHandle.WaitAny(new WaitHandle[] {
87+
newInstance.RestartEvent,
88+
_stopEvent
89+
});
90+
91+
// Orphan the current host instance. We're stopping it, so it won't listen for any new functions
92+
// it will finish any currently executing functions and then clean itself up.
93+
// Spin around and create a new host instance.
94+
Task tIgnore = Orphan(newInstance);
95+
}
96+
catch (Exception ex)
6997
{
70-
_liveInstances.Add(newInstance);
98+
// We need to keep the host running, so we catch and log any errors
99+
// then restart the host
100+
if (_traceWriter != null)
101+
{
102+
_traceWriter.Error("A ScriptHost error occurred", ex);
103+
}
104+
105+
// Wait for a short period of time before restarting to
106+
// avoid cases where a host level config error might cause
107+
// a rapid restart cycle
108+
Task.Delay(5000).Wait();
71109
}
72-
_currentInstance = newInstance;
73-
OnHostStarted();
74-
75-
// only after ALL initialization is complete do we set this flag
76-
IsRunning = true;
77-
78-
// Wait for a restart signal. This event will automatically reset.
79-
// While we're restarting, it is possible for another restart to be
80-
// signaled. That is fine - the restart will be processed immediately
81-
// once we get to this line again. The important thing is that these
82-
// restarts are only happening on a single thread.
83-
WaitHandle.WaitAny(new WaitHandle[] {
84-
newInstance.RestartEvent,
85-
_stopEvent
86-
});
87-
88-
// Orphan the current host instance. We're stopping it, so it won't listen for any new functions
89-
// it will finish any currently executing functions and then clean itself up.
90-
// Spin around and create a new host instance.
91-
Task tIgnore = Orphan(newInstance);
92110
}
93-
while (!_stopped);
111+
while (!_stopped && !cancellationToken.IsCancellationRequested);
94112
}
95113

96114
// Let the existing host instance finish currently executing functions.

0 commit comments

Comments
 (0)