Skip to content

Commit 3614b3b

Browse files
committed
Merge branch 'master' into dev
2 parents 70fdd93 + 2831145 commit 3614b3b

File tree

7 files changed

+119
-13
lines changed

7 files changed

+119
-13
lines changed

src/WebJobs.Script.WebHost/FunctionsSyncService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public Task StartAsync(CancellationToken cancellationToken)
5959

6060
public Task StopAsync(CancellationToken cancellationToken)
6161
{
62-
// cancel the timer
63-
_syncTimer.Change(Timeout.Infinite, Timeout.Infinite);
62+
// cancel the timer if it has been started
63+
_syncTimer?.Change(Timeout.Infinite, Timeout.Infinite);
6464

6565
return Task.CompletedTask;
6666
}

src/WebJobs.Script.WebHost/Management/FunctionsSyncManager.cs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ public class FunctionsSyncManager : IFunctionsSyncManager, IDisposable
4040
private readonly ISecretManagerProvider _secretManagerProvider;
4141
private readonly IConfiguration _configuration;
4242
private readonly IHostIdProvider _hostIdProvider;
43+
private readonly IScriptWebHostEnvironment _webHostEnvironment;
44+
private readonly IEnvironment _environment;
4345
private readonly SemaphoreSlim _syncSemaphore = new SemaphoreSlim(1, 1);
4446

4547
private CloudBlockBlob _hashBlob;
4648

47-
public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IOptions<LanguageWorkerOptions> languageWorkerOptions, ILoggerFactory loggerFactory, HttpClient httpClient, ISecretManagerProvider secretManagerProvider)
49+
public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IOptions<LanguageWorkerOptions> languageWorkerOptions, ILoggerFactory loggerFactory, HttpClient httpClient, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment)
4850
{
4951
_applicationHostOptions = applicationHostOptions;
5052
_logger = loggerFactory?.CreateLogger(ScriptConstants.LogCategoryHostGeneral);
@@ -53,17 +55,27 @@ public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostId
5355
_secretManagerProvider = secretManagerProvider;
5456
_configuration = configuration;
5557
_hostIdProvider = hostIdProvider;
58+
_webHostEnvironment = webHostEnvironment;
59+
_environment = environment;
5660
}
5761

5862
public async Task<SyncTriggersResult> TrySyncTriggersAsync(bool checkHash = false)
5963
{
60-
try
64+
var result = new SyncTriggersResult
6165
{
62-
var result = new SyncTriggersResult
63-
{
64-
Success = true
65-
};
66+
Success = true
67+
};
6668

69+
if (!IsSyncTriggersEnvironment(_webHostEnvironment, _environment))
70+
{
71+
result.Success = false;
72+
result.Error = "Invalid environment for SyncTriggers operation.";
73+
_logger.LogWarning(result.Error);
74+
return result;
75+
}
76+
77+
try
78+
{
6779
await _syncSemaphore.WaitAsync();
6880

6981
var hashBlob = await GetHashBlobAsync();
@@ -96,13 +108,28 @@ public async Task<SyncTriggersResult> TrySyncTriggersAsync(bool checkHash = fals
96108
result.Success = success;
97109
result.Error = error;
98110
}
99-
100-
return result;
111+
}
112+
catch (Exception ex)
113+
{
114+
// best effort - log error and continue
115+
result.Success = false;
116+
result.Error = "SyncTriggers operation failed.";
117+
_logger.LogError(ex, result.Error);
101118
}
102119
finally
103120
{
104121
_syncSemaphore.Release();
105122
}
123+
124+
return result;
125+
}
126+
127+
internal static bool IsSyncTriggersEnvironment(IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment)
128+
{
129+
// only want to do background sync triggers when NOT
130+
// in standby mode and not running locally
131+
return !environment.IsCoreToolsEnvironment() &&
132+
!webHostEnvironment.InStandbyMode && environment.IsContainerReady();
106133
}
107134

108135
internal async Task<string> CheckHashAsync(CloudBlockBlob hashBlob, string content)

src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
1111
using Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection;
1212
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
13+
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
1314
using Microsoft.Extensions.DependencyInjection;
1415
using Microsoft.Extensions.DependencyInjection.Extensions;
1516
using Microsoft.Extensions.Hosting;
@@ -77,7 +78,13 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP
7778
})
7879
.ConfigureServices(services =>
7980
{
80-
services.AddSingleton<IHostedService, FunctionsSyncService>();
81+
var webHostEnvironment = rootServiceProvider.GetService<IScriptWebHostEnvironment>();
82+
var environment = rootServiceProvider.GetService<IEnvironment>();
83+
if (FunctionsSyncManager.IsSyncTriggersEnvironment(webHostEnvironment, environment))
84+
{
85+
services.AddSingleton<IHostedService, FunctionsSyncService>();
86+
}
87+
8188
services.AddSingleton<HttpRequestQueue>();
8289
services.AddSingleton<IHostLifetime, JobHostHostLifetime>();
8390
services.TryAddSingleton<IWebJobsExceptionHandler, WebScriptHostExceptionHandler>();

test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder b
3939
var services = new ServiceCollection();
4040
AddMockedSingleton<IDebugStateProvider>(services);
4141
AddMockedSingleton<IScriptHostManager>(services);
42+
AddMockedSingleton<IEnvironment>(services);
4243
AddMockedSingleton<IScriptWebHostEnvironment>(services);
4344
AddMockedSingleton<IEventGenerator>(services);
4445
AddMockedSingleton<AspNetCore.Hosting.IApplicationLifetime>(services);

test/WebJobs.Script.Tests/FunctionsSyncServiceTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public class FunctionsSyncServiceTests
2121
private readonly Mock<IScriptHostManager> _mockScriptHostManager;
2222
private readonly Mock<IPrimaryHostStateProvider> _mockPrimaryHostStateProviderMock;
2323
private readonly Mock<IFunctionsSyncManager> _mockSyncManager;
24+
private readonly Mock<IScriptWebHostEnvironment> _mockWebHostEnvironment;
25+
private readonly Mock<IEnvironment> _mockEnvironment;
2426
private readonly int _testDueTime = 250;
2527

2628
public FunctionsSyncServiceTests()
@@ -37,6 +39,12 @@ public FunctionsSyncServiceTests()
3739
_mockScriptHostManager.Setup(p => p.State).Returns(ScriptHostState.Running);
3840
_mockSyncManager.Setup(p => p.TrySyncTriggersAsync(true)).ReturnsAsync(new SyncTriggersResult { Success = true });
3941

42+
_mockWebHostEnvironment = new Mock<IScriptWebHostEnvironment>(MockBehavior.Strict);
43+
_mockWebHostEnvironment.SetupGet(p => p.InStandbyMode).Returns(false);
44+
_mockEnvironment = new Mock<IEnvironment>(MockBehavior.Strict);
45+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteContainerReady)).Returns("1");
46+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.CoreToolsEnvironment)).Returns((string)null);
47+
4048
_syncService = new FunctionsSyncService(loggerFactory, _mockScriptHostManager.Object, _mockPrimaryHostStateProviderMock.Object, _mockSyncManager.Object);
4149
_syncService.DueTime = _testDueTime;
4250
}
@@ -98,5 +106,30 @@ public async Task UnhandledExceptions_HandledInCallback()
98106

99107
_mockSyncManager.Verify(p => p.TrySyncTriggersAsync(true), Times.Once);
100108
}
109+
110+
[Theory]
111+
[InlineData(true, true, false)]
112+
[InlineData(true, false, false)]
113+
[InlineData(false, true, true)]
114+
[InlineData(false, false, false)]
115+
public void IsSyncTriggersEnvironment_StandbyMode_ReturnsExpectedResult(bool standbyMode, bool containerReady, bool expected)
116+
{
117+
_mockWebHostEnvironment.SetupGet(p => p.InStandbyMode).Returns(standbyMode);
118+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteContainerReady)).Returns(containerReady ? "1" : null);
119+
120+
var result = FunctionsSyncManager.IsSyncTriggersEnvironment(_mockWebHostEnvironment.Object, _mockEnvironment.Object);
121+
Assert.Equal(expected, result);
122+
}
123+
124+
[Theory]
125+
[InlineData(true, false)]
126+
[InlineData(false, true)]
127+
public void IsSyncTriggersEnvironment_LocalEnvironment_ReturnsExpectedResult(bool coreToolsEnvironment, bool expected)
128+
{
129+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.CoreToolsEnvironment)).Returns(coreToolsEnvironment ? "1" : null);
130+
131+
var result = FunctionsSyncManager.IsSyncTriggersEnvironment(_mockWebHostEnvironment.Object, _mockEnvironment.Object);
132+
Assert.Equal(expected, result);
133+
}
101134
}
102135
}

test/WebJobs.Script.Tests/Managment/FunctionsSyncManagerTests.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class FunctionsSyncManagerTests : IDisposable
3737
private readonly string _expectedSyncTriggersPayload;
3838
private readonly MockHttpHandler _mockHttpHandler;
3939
private readonly TestLoggerProvider _loggerProvider;
40+
private readonly Mock<IScriptWebHostEnvironment> _mockWebHostEnvironment;
41+
private readonly Mock<IEnvironment> _mockEnvironment;
4042

4143
public FunctionsSyncManagerTests()
4244
{
@@ -78,7 +80,12 @@ public FunctionsSyncManagerTests()
7880
var configuration = ScriptSettingsManager.BuildDefaultConfiguration();
7981
var hostIdProviderMock = new Mock<IHostIdProvider>(MockBehavior.Strict);
8082
hostIdProviderMock.Setup(p => p.GetHostIdAsync(CancellationToken.None)).ReturnsAsync("testhostid123");
81-
_functionsSyncManager = new FunctionsSyncManager(configuration, hostIdProviderMock.Object, optionsMonitor, new OptionsWrapper<LanguageWorkerOptions>(CreateLanguageWorkerConfigSettings()), loggerFactory, httpClient, secretManagerProviderMock.Object);
83+
_mockWebHostEnvironment = new Mock<IScriptWebHostEnvironment>(MockBehavior.Strict);
84+
_mockWebHostEnvironment.SetupGet(p => p.InStandbyMode).Returns(false);
85+
_mockEnvironment = new Mock<IEnvironment>(MockBehavior.Strict);
86+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteContainerReady)).Returns("1");
87+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.CoreToolsEnvironment)).Returns((string)null);
88+
_functionsSyncManager = new FunctionsSyncManager(configuration, hostIdProviderMock.Object, optionsMonitor, new OptionsWrapper<LanguageWorkerOptions>(CreateLanguageWorkerConfigSettings()), loggerFactory, httpClient, secretManagerProviderMock.Object, _mockWebHostEnvironment.Object, _mockEnvironment.Object);
8289

8390
_expectedSyncTriggersPayload = "[{\"authLevel\":\"anonymous\",\"type\":\"httpTrigger\",\"direction\":\"in\",\"name\":\"req\",\"functionName\":\"function1\"}," +
8491
"{\"name\":\"myQueueItem\",\"type\":\"orchestrationTrigger\",\"direction\":\"in\",\"queueName\":\"myqueue-items\",\"connection\":\"DurableStorage\",\"functionName\":\"function2\",\"taskHubName\":\"TestHubValue\"}," +
@@ -91,6 +98,32 @@ private void ResetMockFileSystem(string hostJsonContent = null)
9198
FileUtility.Instance = fileSystem;
9299
}
93100

101+
[Fact]
102+
public async Task TrySyncTriggers_StandbyMode_ReturnsFalse()
103+
{
104+
using (var env = new TestScopedEnvironmentVariable(_vars))
105+
{
106+
_mockWebHostEnvironment.SetupGet(p => p.InStandbyMode).Returns(true);
107+
var result = await _functionsSyncManager.TrySyncTriggersAsync();
108+
Assert.False(result.Success);
109+
var expectedMessage = "Invalid environment for SyncTriggers operation.";
110+
Assert.Equal(expectedMessage, result.Error);
111+
}
112+
}
113+
114+
[Fact]
115+
public async Task TrySyncTriggers_LocalEnvironment_ReturnsFalse()
116+
{
117+
using (var env = new TestScopedEnvironmentVariable(_vars))
118+
{
119+
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.CoreToolsEnvironment)).Returns("1");
120+
var result = await _functionsSyncManager.TrySyncTriggersAsync();
121+
Assert.False(result.Success);
122+
var expectedMessage = "Invalid environment for SyncTriggers operation.";
123+
Assert.Equal(expectedMessage, result.Error);
124+
}
125+
}
126+
94127
[Fact]
95128
public async Task TrySyncTriggers_PostsExpectedContent()
96129
{

test/WebJobs.Script.Tests/Managment/WebFunctionsManagerTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,12 @@ public void ReadFunctionsMetadataSucceeds()
6767

6868
var configurationMock = new Mock<IConfiguration>(MockBehavior.Strict);
6969
var hostIdProviderMock = new Mock<IHostIdProvider>(MockBehavior.Strict);
70-
var functionsSyncManager = new FunctionsSyncManager(configurationMock.Object, hostIdProviderMock.Object, optionsMonitor, new OptionsWrapper<LanguageWorkerOptions>(CreateLanguageWorkerConfigSettings()), loggerFactory, httpClient, secretManagerProviderMock.Object);
70+
var mockWebHostEnvironment = new Mock<IScriptWebHostEnvironment>(MockBehavior.Strict);
71+
mockWebHostEnvironment.SetupGet(p => p.InStandbyMode).Returns(false);
72+
var mockEnvironment = new Mock<IEnvironment>(MockBehavior.Strict);
73+
mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteContainerReady)).Returns("1");
74+
mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.CoreToolsEnvironment)).Returns((string)null);
75+
var functionsSyncManager = new FunctionsSyncManager(configurationMock.Object, hostIdProviderMock.Object, optionsMonitor, new OptionsWrapper<LanguageWorkerOptions>(CreateLanguageWorkerConfigSettings()), loggerFactory, httpClient, secretManagerProviderMock.Object, mockWebHostEnvironment.Object, mockEnvironment.Object);
7176
var webManager = new WebFunctionsManager(optionsMonitor, new OptionsWrapper<LanguageWorkerOptions>(CreateLanguageWorkerConfigSettings()), loggerFactory, httpClient, secretManagerProviderMock.Object, functionsSyncManager);
7277

7378
FileUtility.Instance = fileSystem;

0 commit comments

Comments
 (0)