Skip to content

Commit 1e87ddd

Browse files
committed
Startup improvements for http function invocations
1 parent ab84ce5 commit 1e87ddd

File tree

12 files changed

+121
-52
lines changed

12 files changed

+121
-52
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
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;
65
using System.IO;
7-
using System.Threading.Tasks;
86
using System.Web;
97
using System.Web.Http;
108
using Autofac;

src/WebJobs.Script.WebHost/Controllers/HomeController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Net;
66
using System.Net.Http;
7-
using System.Net.Http.Formatting;
87
using System.Text;
98
using System.Web.Http;
109
using Microsoft.Azure.WebJobs.Script.WebHost.Properties;

src/WebJobs.Script.WebHost/Handlers/WebScriptHostHandler.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,16 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
4949
// initialize. This might happen if http requests come in while the
5050
// host is starting up for the first time, or if it is restarting.
5151
TimeSpan timeWaited = TimeSpan.Zero;
52-
while (!scriptHostManager.IsRunning && scriptHostManager.LastError == null && (timeWaited < _hostTimeoutSeconds))
52+
while (!scriptHostManager.CanInvoke() &&
53+
scriptHostManager.State != ScriptHostState.Error &&
54+
(timeWaited < _hostTimeoutSeconds))
5355
{
5456
await Task.Delay(_hostRunningPollIntervalMs);
5557
timeWaited += TimeSpan.FromMilliseconds(_hostRunningPollIntervalMs);
5658
}
5759

58-
// if the host is not running after our wait time has expired
59-
// return a 503
60-
if (!scriptHostManager.IsRunning)
60+
// if the host is not ready after our wait time has expired return a 503
61+
if (!scriptHostManager.CanInvoke())
6162
{
6263
return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
6364
{

src/WebJobs.Script.WebHost/WebScriptHostManager.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,16 +392,21 @@ protected override void OnInitializeConfig(ScriptHostConfiguration config)
392392
hostConfig.DashboardConnectionString = null; // disable slow logging
393393
}
394394

395-
protected override void OnHostStarted()
395+
protected override void OnHostCreated()
396396
{
397-
base.OnHostStarted();
398-
399397
// whenever the host is created (or recreated) we build a cache map of
400398
// all http function routes
401399
InitializeHttpFunctions(Instance.Functions);
402400

401+
base.OnHostCreated();
402+
}
403+
404+
protected override void OnHostStarted()
405+
{
403406
// Purge any old Function secrets
404407
_secretManager.PurgeOldFiles(Instance.ScriptConfig.RootScriptPath, Instance.TraceWriter);
408+
409+
base.OnHostStarted();
405410
}
406411

407412
internal void InitializeHttpFunctions(IEnumerable<FunctionDescriptor> functions)

src/WebJobs.Script/Host/ScriptHostManager.cs

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ public ScriptHostManager(ScriptHostConfiguration config, ScriptSettingsManager s
5050
_scriptHostFactory = scriptHostFactory;
5151
}
5252

53-
/// <summary>
54-
/// Returns true if the <see cref="ScriptHost"/> is up and running and ready to
55-
/// process requests.
56-
/// </summary>
57-
public virtual bool IsRunning { get; private set; }
58-
5953
public virtual ScriptHost Instance
6054
{
6155
get
@@ -65,10 +59,28 @@ public virtual ScriptHost Instance
6559
}
6660

6761
/// <summary>
68-
/// Gets the last host <see cref="Exception"/> that has occurred.
62+
/// Gets or sets the current state of the host.
63+
/// </summary>
64+
public virtual ScriptHostState State { get; private set; }
65+
66+
/// <summary>
67+
/// Gets or sets the last host <see cref="Exception"/> that has occurred.
6968
/// </summary>
7069
public virtual Exception LastError { get; private set; }
7170

71+
/// <summary>
72+
/// Returns a value indicating whether the host can accept function invoke requests.
73+
/// </summary>
74+
/// <remarks>
75+
/// The host doesn't have to be fully started for it to allow direct function invocations
76+
/// to be processed.
77+
/// </remarks>
78+
/// <returns>True if the host can accept invoke requests, false otherwise.</returns>
79+
public bool CanInvoke()
80+
{
81+
return State == ScriptHostState.Created || State == ScriptHostState.Running;
82+
}
83+
7284
public void RunAndBlock(CancellationToken cancellationToken = default(CancellationToken))
7385
{
7486
// Start the host and restart it if requested. Host Restarts will happen when
@@ -78,7 +90,12 @@ public virtual ScriptHost Instance
7890
ScriptHost newInstance = null;
7991
try
8092
{
81-
IsRunning = false;
93+
// if we were in an error state retain that,
94+
// otherwise move to default
95+
if (State != ScriptHostState.Error)
96+
{
97+
State = ScriptHostState.Default;
98+
}
8299

83100
// Create a new host config, but keep the host id from existing one
84101
_config.HostConfig = new JobHostConfiguration
@@ -87,9 +104,16 @@ public virtual ScriptHost Instance
87104
};
88105
OnInitializeConfig(_config);
89106
newInstance = _scriptHostFactory.Create(_settingsManager, _config);
90-
91107
_traceWriter = newInstance.TraceWriter;
92108

109+
_currentInstance = newInstance;
110+
lock (_liveInstances)
111+
{
112+
_liveInstances.Add(newInstance);
113+
}
114+
115+
OnHostCreated();
116+
93117
if (_traceWriter != null)
94118
{
95119
string message = string.Format("Starting Host (HostId={0}, Version={1}, ProcessId={2}, Debug={3})",
@@ -101,15 +125,11 @@ public virtual ScriptHost Instance
101125
// log any function initialization errors
102126
LogErrors(newInstance);
103127

104-
lock (_liveInstances)
105-
{
106-
_liveInstances.Add(newInstance);
107-
}
108-
_currentInstance = newInstance;
109128
OnHostStarted();
110129

111-
// only after ALL initialization is complete do we set this flag
112-
IsRunning = true;
130+
// only after ALL initialization is complete do we set the
131+
// state to Running
132+
State = ScriptHostState.Running;
113133
LastError = null;
114134

115135
// Wait for a restart signal. This event will automatically reset.
@@ -131,7 +151,7 @@ public virtual ScriptHost Instance
131151
}
132152
catch (Exception ex)
133153
{
134-
IsRunning = false;
154+
State = ScriptHostState.Error;
135155
LastError = ex;
136156

137157
// We need to keep the host running, so we catch and log any errors
@@ -221,15 +241,10 @@ public async Task StopAsync()
221241
_stopEvent.Set();
222242
ScriptHost[] instances = GetLiveInstancesAndClear();
223243

224-
Task[] tasksStop = Array.ConvertAll(instances, instance => instance.StopAsync());
244+
Task[] tasksStop = Array.ConvertAll(instances, p => StopAndDisposeAsync(p));
225245
await Task.WhenAll(tasksStop);
226246

227-
foreach (var instance in instances)
228-
{
229-
instance.Dispose();
230-
}
231-
232-
IsRunning = false;
247+
State = ScriptHostState.Default;
233248
}
234249
catch
235250
{
@@ -242,6 +257,22 @@ public void Stop()
242257
StopAsync().GetAwaiter().GetResult();
243258
}
244259

260+
private static async Task StopAndDisposeAsync(ScriptHost instance)
261+
{
262+
try
263+
{
264+
await instance.StopAsync();
265+
}
266+
catch
267+
{
268+
// best effort
269+
}
270+
finally
271+
{
272+
instance.Dispose();
273+
}
274+
}
275+
245276
private ScriptHost[] GetLiveInstancesAndClear()
246277
{
247278
ScriptHost[] instances;
@@ -258,6 +289,11 @@ protected virtual void OnInitializeConfig(ScriptHostConfiguration config)
258289
{
259290
}
260291

292+
protected virtual void OnHostCreated()
293+
{
294+
State = ScriptHostState.Created;
295+
}
296+
261297
protected virtual void OnHostStarted()
262298
{
263299
var metricsLogger = _config.HostConfig.GetService<IMetricsLogger>();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Azure.WebJobs.Script
5+
{
6+
public enum ScriptHostState
7+
{
8+
/// <summary>
9+
/// The host has not yet been created
10+
/// </summary>
11+
Default,
12+
/// <summary>
13+
/// The host has been created and can accept direct function
14+
/// invocations, but listeners are not yet started.
15+
/// </summary>
16+
Created,
17+
/// <summary>
18+
/// The host is fully running.
19+
/// </summary>
20+
Running,
21+
/// <summary>
22+
/// The host is in an error state
23+
/// </summary>
24+
Error
25+
}
26+
}

src/WebJobs.Script/WebJobs.Script.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@
430430
<Compile Include="DictionaryJsonConverter.cs" />
431431
<Compile Include="Extensions\ActionExtensions.cs" />
432432
<Compile Include="Diagnostics\FileTraceWriter.cs" />
433+
<Compile Include="Host\ScriptHostState.cs" />
433434
<Compile Include="HttpMethodJsonConverter.cs" />
434435
<Compile Include="Host\IScriptHostFactory.cs" />
435436
<Compile Include="Diagnostics\NullTraceWriter.cs" />

test/WebJobs.Script.Tests/EndToEndTimeoutTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ private async Task<MockScriptHostManager> CreateAndStartScriptHostManager(string
159159

160160
var scriptHostManager = new MockScriptHostManager(config);
161161
ThreadPool.QueueUserWorkItem((s) => scriptHostManager.RunAndBlock());
162-
await TestHelpers.Await(() => scriptHostManager.IsRunning);
162+
await TestHelpers.Await(() => scriptHostManager.State == ScriptHostState.Running);
163163

164164
return scriptHostManager;
165165
}

test/WebJobs.Script.Tests/FunctionGeneratorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ internal async Task GeneratedMethods_WithOutParams_DoNotCauseDeadlocks(string fi
241241

242242
await TestHelpers.Await(() =>
243243
{
244-
return manager.IsRunning;
244+
return manager.State == ScriptHostState.Running;
245245
});
246246

247247
var request = new HttpRequestMessage(HttpMethod.Get, String.Format("http://localhost/api/httptrigger-{0}", fixture));

test/WebJobs.Script.Tests/ScriptHostManagerTests.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public async Task UpdateFileAndRestart()
4646
Thread t = new Thread(_ =>
4747
{
4848
// don't start until the manager is running
49-
TestHelpers.Await(() => manager.IsRunning).Wait();
49+
TestHelpers.Await(() => manager.State == ScriptHostState.Running).Wait();
5050

5151
try
5252
{
@@ -128,7 +128,8 @@ public async Task RunAndBlock_SetsLastError_WhenExceptionIsThrown()
128128
// we expect a host exception immediately
129129
await Task.Delay(2000);
130130

131-
Assert.False(hostManager.IsRunning);
131+
Assert.Equal(ScriptHostState.Error, hostManager.State);
132+
Assert.False(hostManager.CanInvoke());
132133
Assert.NotNull(hostManager.LastError);
133134
Assert.Equal("Kaboom!", hostManager.LastError.Message);
134135

@@ -137,10 +138,12 @@ public async Task RunAndBlock_SetsLastError_WhenExceptionIsThrown()
137138
scriptHostFactory.Throw = false;
138139
await TestHelpers.Await(() =>
139140
{
140-
return hostManager.IsRunning;
141+
return hostManager.State == ScriptHostState.Running;
141142
});
142143

143144
Assert.Null(hostManager.LastError);
145+
Assert.True(hostManager.CanInvoke());
146+
Assert.Equal(ScriptHostState.Running, hostManager.State);
144147
}
145148

146149
[Fact]
@@ -168,10 +171,10 @@ public async Task EmptyHost_StartsSuccessfully()
168171

169172
Task runTask = Task.Run(() => hostManager.RunAndBlock());
170173

171-
await TestHelpers.Await(() => hostManager.IsRunning, timeout: 10000);
174+
await TestHelpers.Await(() => hostManager.State == ScriptHostState.Running, timeout: 10000);
172175

173176
hostManager.Stop();
174-
Assert.False(hostManager.IsRunning);
177+
Assert.Equal(ScriptHostState.Default, hostManager.State);
175178

176179
await Task.Delay(FileTraceWriter.LogFlushIntervalMs);
177180

0 commit comments

Comments
 (0)