Skip to content

Commit 940a0f8

Browse files
committed
Implement background SyncTriggers (#3565)
1 parent ae72432 commit 940a0f8

20 files changed

+1018
-248
lines changed

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,6 @@ public async Task<IActionResult> CreateOrUpdate(string name, [FromBody] Function
7878

7979
if (success)
8080
{
81-
if (configChanged)
82-
{
83-
// TODO: sync triggers
84-
}
85-
8681
return Created(Request.GetDisplayUrl(), functionMetadataResponse);
8782
}
8883
else

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,16 @@ public class HostController : Controller
4141
private readonly IWebFunctionsManager _functionsManager;
4242
private readonly IEnvironment _environment;
4343
private readonly IScriptHostManager _scriptHostManager;
44+
private readonly IFunctionsSyncManager _functionsSyncManager;
4445

4546
public HostController(IOptions<ScriptApplicationHostOptions> applicationHostOptions,
4647
IOptions<JobHostOptions> hostOptions,
4748
ILoggerFactory loggerFactory,
4849
IAuthorizationService authorizationService,
4950
IWebFunctionsManager functionsManager,
5051
IEnvironment environment,
51-
IScriptHostManager scriptHostManager)
52+
IScriptHostManager scriptHostManager,
53+
IFunctionsSyncManager functionsSyncManager)
5254
{
5355
_applicationHostOptions = applicationHostOptions;
5456
_hostOptions = hostOptions;
@@ -57,6 +59,7 @@ public HostController(IOptions<ScriptApplicationHostOptions> applicationHostOpti
5759
_functionsManager = functionsManager;
5860
_environment = environment;
5961
_scriptHostManager = scriptHostManager;
62+
_functionsSyncManager = functionsSyncManager;
6063
}
6164

6265
[HttpGet]
@@ -151,12 +154,12 @@ public IActionResult LaunchDebugger()
151154
[Authorize(Policy = PolicyNames.AdminAuthLevel)]
152155
public async Task<IActionResult> SyncTriggers()
153156
{
154-
(var success, var error) = await _functionsManager.TrySyncTriggers();
157+
var result = await _functionsSyncManager.TrySyncTriggersAsync();
155158

156159
// Return a dummy body to make it valid in ARM template action evaluation
157-
return success
160+
return result.Success
158161
? Ok(new { status = "success" })
159-
: StatusCode(StatusCodes.Status500InternalServerError, new { status = error });
162+
: StatusCode(StatusCodes.Status500InternalServerError, new { status = result.Error });
160163
}
161164

162165
[HttpPost]

src/WebJobs.Script.WebHost/Extensions/FunctionMetadataExtensions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ public static class FunctionMetadataExtensions
2020
/// Maps FunctionMetadata to FunctionMetadataResponse.
2121
/// </summary>
2222
/// <param name="functionMetadata">FunctionMetadata to be mapped.</param>
23-
/// <param name="request">Current HttpRequest</param>
2423
/// <param name="hostOptions">The host options</param>
2524
/// <returns>Promise of a FunctionMetadataResponse</returns>
26-
public static async Task<FunctionMetadataResponse> ToFunctionMetadataResponse(this FunctionMetadata functionMetadata, HttpRequest request, ScriptJobHostOptions hostOptions, string routePrefix)
25+
public static async Task<FunctionMetadataResponse> ToFunctionMetadataResponse(this FunctionMetadata functionMetadata, ScriptJobHostOptions hostOptions, string routePrefix, string baseUrl)
2726
{
2827
var functionPath = Path.Combine(hostOptions.RootScriptPath, functionMetadata.Name);
2928
var functionMetadataFilePath = Path.Combine(functionPath, ScriptConstants.FunctionMetadataFileName);
30-
var baseUrl = request != null
31-
? $"{request.Scheme}://{request.Host}"
32-
: "https://localhost/";
29+
if (string.IsNullOrEmpty(baseUrl))
30+
{
31+
baseUrl = "https://localhost/";
32+
}
3333

3434
var response = new FunctionMetadataResponse
3535
{
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
8+
using Microsoft.Extensions.Hosting;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.WebHost
12+
{
13+
/// <summary>
14+
/// This hosted service is responsible for performing a background SyncTriggers
15+
/// operation after a successful host startup.
16+
/// </summary>
17+
public class FunctionsSyncService : IHostedService, IDisposable
18+
{
19+
private readonly ILogger _logger;
20+
private readonly IScriptHostManager _scriptHostManager;
21+
private readonly IPrimaryHostStateProvider _primaryHostStateProvider;
22+
private readonly IFunctionsSyncManager _functionsSyncManager;
23+
private Timer _syncTimer;
24+
private bool _disposed = false;
25+
26+
public FunctionsSyncService(ILoggerFactory loggerFactory, IScriptHostManager scriptHostManager, IPrimaryHostStateProvider primaryHostStateProvider, IFunctionsSyncManager functionsSyncManager)
27+
{
28+
if (loggerFactory == null)
29+
{
30+
throw new ArgumentNullException(nameof(loggerFactory));
31+
}
32+
33+
DueTime = 30 * 1000;
34+
_scriptHostManager = scriptHostManager;
35+
_primaryHostStateProvider = primaryHostStateProvider;
36+
_functionsSyncManager = functionsSyncManager;
37+
_logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryHostGeneral);
38+
}
39+
40+
// exposed for testing
41+
internal int DueTime { get; set; }
42+
43+
internal bool ShouldSyncTriggers
44+
{
45+
get
46+
{
47+
return _primaryHostStateProvider.IsPrimary &&
48+
(_scriptHostManager.State == ScriptHostState.Running);
49+
}
50+
}
51+
52+
public Task StartAsync(CancellationToken cancellationToken)
53+
{
54+
// create a onetime invocation timer
55+
_syncTimer = new Timer(OnSyncTimerTick, cancellationToken, DueTime, Timeout.Infinite);
56+
57+
return Task.CompletedTask;
58+
}
59+
60+
public Task StopAsync(CancellationToken cancellationToken)
61+
{
62+
// cancel the timer
63+
_syncTimer.Change(Timeout.Infinite, Timeout.Infinite);
64+
65+
return Task.CompletedTask;
66+
}
67+
68+
private async void OnSyncTimerTick(object state)
69+
{
70+
try
71+
{
72+
var cancellationToken = (CancellationToken)state;
73+
74+
if (!cancellationToken.IsCancellationRequested && ShouldSyncTriggers)
75+
{
76+
_logger.LogDebug("Initiating background SyncTriggers operation");
77+
await _functionsSyncManager.TrySyncTriggersAsync(checkHash: true);
78+
}
79+
}
80+
catch (Exception exc) when (!exc.IsFatal())
81+
{
82+
// failures are already logged in the sync triggers call
83+
// we need to suppress background exceptions from the timer thread
84+
}
85+
}
86+
87+
public void Dispose()
88+
{
89+
if (!_disposed)
90+
{
91+
_syncTimer?.Dispose();
92+
_disposed = true;
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)