Skip to content

Commit b490a2c

Browse files
committed
Ensure cached proxies metadata is reset on proxy changes
1 parent 122c169 commit b490a2c

File tree

2 files changed

+118
-4
lines changed

2 files changed

+118
-4
lines changed

src/WebJobs.Script/Host/ProxyMetadataManager.cs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,74 @@
77
using System.Collections.ObjectModel;
88
using System.IO;
99
using System.Linq;
10+
using System.Reactive.Linq;
1011
using System.Text.RegularExpressions;
12+
using System.Threading;
1113
using Microsoft.Azure.AppService.Proxy.Client;
1214
using Microsoft.Azure.WebJobs.Logging;
1315
using Microsoft.Azure.WebJobs.Script.Description;
16+
using Microsoft.Azure.WebJobs.Script.Eventing;
1417
using Microsoft.Extensions.Logging;
1518
using Microsoft.Extensions.Options;
1619
using Newtonsoft.Json.Linq;
1720

1821
namespace Microsoft.Azure.WebJobs.Script
1922
{
20-
public class ProxyMetadataManager : IProxyMetadataManager
23+
public class ProxyMetadataManager : IProxyMetadataManager, IDisposable
2124
{
2225
private static readonly Regex ProxyNameValidationRegex = new Regex(@"[^a-zA-Z0-9_-]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
23-
private readonly Lazy<ProxyMetadataInfo> _metadata;
26+
private readonly ReaderWriterLockSlim _metadataLock = new ReaderWriterLockSlim();
2427
private readonly IOptionsMonitor<ScriptApplicationHostOptions> _applicationHostOptions;
2528
private readonly IEnvironment _environment;
2629
private readonly ILogger _logger;
30+
private readonly IDisposable _fileChangeSubscription;
31+
private Lazy<ProxyMetadataInfo> _metadata;
32+
private bool _disposed = false;
2733

28-
public ProxyMetadataManager(IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IEnvironment environment, ILoggerFactory loggerFactory)
34+
public ProxyMetadataManager(IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IEnvironment environment, IScriptEventManager eventManager, ILoggerFactory loggerFactory)
2935
{
3036
_applicationHostOptions = applicationHostOptions;
3137
_environment = environment;
3238
_logger = loggerFactory.CreateLogger(LogCategories.Startup);
3339
_metadata = new Lazy<ProxyMetadataInfo>(LoadFunctionMetadata);
40+
41+
_fileChangeSubscription = eventManager.OfType<FileEvent>()
42+
.Where(f => string.Equals(f.Source, EventSources.ScriptFiles, StringComparison.Ordinal) &&
43+
string.Equals(Path.GetFileName(f.FileChangeArguments.Name), ScriptConstants.ProxyMetadataFileName, StringComparison.OrdinalIgnoreCase))
44+
.Subscribe(e => HandleProxyFileChange());
3445
}
3546

36-
public ProxyMetadataInfo ProxyMetadata => _metadata.Value;
47+
public ProxyMetadataInfo ProxyMetadata
48+
{
49+
get
50+
{
51+
_metadataLock.EnterReadLock();
52+
try
53+
{
54+
return _metadata.Value;
55+
}
56+
finally
57+
{
58+
_metadataLock.ExitReadLock();
59+
}
60+
}
61+
}
62+
63+
private void HandleProxyFileChange()
64+
{
65+
_metadataLock.EnterWriteLock();
66+
try
67+
{
68+
if (_metadata.IsValueCreated)
69+
{
70+
_metadata = new Lazy<ProxyMetadataInfo>(LoadFunctionMetadata);
71+
}
72+
}
73+
finally
74+
{
75+
_metadataLock.ExitWriteLock();
76+
}
77+
}
3778

3879
private ProxyMetadataInfo LoadFunctionMetadata()
3980
{
@@ -137,5 +178,23 @@ internal static string NormalizeProxyName(string name)
137178
{
138179
return ProxyNameValidationRegex.Replace(name, string.Empty);
139180
}
181+
182+
protected virtual void Dispose(bool disposing)
183+
{
184+
if (!_disposed)
185+
{
186+
if (disposing)
187+
{
188+
_fileChangeSubscription?.Dispose();
189+
}
190+
191+
_disposed = true;
192+
}
193+
}
194+
195+
public void Dispose()
196+
{
197+
Dispose(true);
198+
}
140199
}
141200
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.Collections.Generic;
6+
using System.IO;
7+
using System.Text;
8+
using Microsoft.Azure.WebJobs.Script.Eventing;
9+
using Microsoft.Extensions.Logging.Abstractions;
10+
using WebJobs.Script.Tests;
11+
using Xunit;
12+
13+
namespace Microsoft.Azure.WebJobs.Script.Tests
14+
{
15+
public class ProxyMetadataManagerTests
16+
{
17+
[Fact]
18+
public void ProxyMetadata_WhenProxyFileChanges_IsRefreshed()
19+
{
20+
using (var tempDirectory = new TempDirectory())
21+
{
22+
var testProxiesPath = Path.Combine(Environment.CurrentDirectory, @"TestScripts\Proxies");
23+
var monitor = new TestOptionsMonitor<ScriptApplicationHostOptions>(new ScriptApplicationHostOptions
24+
{
25+
ScriptPath = tempDirectory.Path
26+
});
27+
28+
var environment = new TestEnvironment();
29+
var eventManager = new ScriptEventManager();
30+
31+
var manager = new ProxyMetadataManager(monitor, environment, eventManager, NullLoggerFactory.Instance);
32+
33+
// Get metadata before proxies exist
34+
ProxyMetadataInfo proxyMetadata1 = manager.ProxyMetadata;
35+
ProxyMetadataInfo proxyMetadata2 = manager.ProxyMetadata;
36+
37+
Assert.True(proxyMetadata2.Functions.IsDefaultOrEmpty);
38+
Assert.Same(proxyMetadata1, proxyMetadata2);
39+
40+
// Update our proxies definition
41+
FileUtility.CopyDirectory(testProxiesPath, tempDirectory.Path);
42+
43+
// Simulate a file change notification
44+
eventManager.Publish(new FileEvent(EventSources.ScriptFiles,
45+
new FileSystemEventArgs(WatcherChangeTypes.Changed, tempDirectory.Path, ScriptConstants.ProxyMetadataFileName)));
46+
47+
ProxyMetadataInfo proxyMetadata3 = manager.ProxyMetadata;
48+
49+
Assert.NotSame(proxyMetadata1, proxyMetadata3);
50+
51+
Assert.Equal(16, proxyMetadata3.Functions.Length);
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)