Skip to content

Commit d1b5281

Browse files
committed
Enhancements to runtime assembly match/binding behavior
1 parent 887e524 commit d1b5281

File tree

6 files changed

+1555
-208
lines changed

6 files changed

+1555
-208
lines changed

src/WebJobs.Script/Description/DotNet/ExtensionSharedAssemblyProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Reflection;
67
using System.Runtime.Loader;
@@ -23,7 +24,7 @@ public class ExtensionSharedAssemblyProvider : ISharedAssemblyProvider
2324
/// <param name="bindingProviders">The collection of <see cref="ScriptBindingProvider"/>s.</param>
2425
public ExtensionSharedAssemblyProvider(ICollection<ScriptBindingProvider> bindingProviders)
2526
{
26-
_bindingProviders = bindingProviders;
27+
_bindingProviders = bindingProviders ?? throw new ArgumentNullException(nameof(bindingProviders));
2728
}
2829

2930
public bool TryResolveAssembly(string assemblyName, AssemblyLoadContext targetContext, out Assembly assembly)

src/WebJobs.Script/Description/DotNet/FunctionAssemblyLoadContext.cs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.Extensions.DependencyModel;
1616
using Newtonsoft.Json.Linq;
1717
using static Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
18+
using ResolutionPolicyEvaluator = System.Func<System.Reflection.AssemblyName, System.Reflection.Assembly, bool>;
1819

1920
namespace Microsoft.Azure.WebJobs.Script.Description
2021
{
@@ -23,11 +24,12 @@ namespace Microsoft.Azure.WebJobs.Script.Description
2324
/// </summary>
2425
public partial class FunctionAssemblyLoadContext : AssemblyLoadContext
2526
{
26-
private readonly List<string> _probingPaths = new List<string>();
27-
private static readonly Lazy<string[]> _runtimeAssemblies = new Lazy<string[]>(GetRuntimeAssemblies);
28-
27+
private static readonly Lazy<Dictionary<string, ScriptRuntimeAssembly>> _runtimeAssemblies = new Lazy<Dictionary<string, ScriptRuntimeAssembly>>(GetRuntimeAssemblies);
28+
private static readonly Lazy<Dictionary<string, ResolutionPolicyEvaluator>> _resolutionPolicyEvaluators = new Lazy<Dictionary<string, ResolutionPolicyEvaluator>>(InitializeLoadPolicyEvaluators);
2929
private static Lazy<FunctionAssemblyLoadContext> _defaultContext = new Lazy<FunctionAssemblyLoadContext>(() => new FunctionAssemblyLoadContext(ResolveFunctionBaseProbingPath()), true);
3030

31+
private readonly List<string> _probingPaths = new List<string>();
32+
3133
public FunctionAssemblyLoadContext(string basePath)
3234
{
3335
if (basePath == null)
@@ -40,13 +42,55 @@ public FunctionAssemblyLoadContext(string basePath)
4042

4143
public static FunctionAssemblyLoadContext Shared => _defaultContext.Value;
4244

45+
private static Dictionary<string, ResolutionPolicyEvaluator> InitializeLoadPolicyEvaluators()
46+
{
47+
return new Dictionary<string, ResolutionPolicyEvaluator>
48+
{
49+
{ "minorMatchOrLower", IsMinorMatchOrLowerPolicyEvaluator },
50+
{ "runtimeVersion", RuntimeVersionPolicyEvaluator }
51+
};
52+
}
53+
54+
/// <summary>
55+
/// A load policy evaluator that accepts the runtime assembly, regardless of version.
56+
/// </summary>
57+
/// <param name="requestedAssembly">The name of the requested assembly.</param>
58+
/// <param name="runtimeAssembly">The runtime assembly.</param>
59+
/// <returns>True if the evaluation succeeds.</returns>
60+
private static bool RuntimeVersionPolicyEvaluator(AssemblyName requestedAssembly, Assembly runtimeAssembly) => true;
61+
62+
/// <summary>
63+
/// A load policy evaluator that verifies if the runtime package is the same major and
64+
/// newer minor version.
65+
/// </summary>
66+
/// <param name="requestedAssembly">The name of the requested assembly.</param>
67+
/// <param name="runtimeAssembly">The runtime assembly.</param>
68+
/// <returns>True if the evaluation succeeds.</returns>
69+
private static bool IsMinorMatchOrLowerPolicyEvaluator(AssemblyName requestedAssembly, Assembly runtimeAssembly)
70+
{
71+
AssemblyName runtimeAssemblyName = AssemblyNameCache.GetName(runtimeAssembly);
72+
73+
return requestedAssembly.Version.Major == runtimeAssemblyName.Version.Major &&
74+
requestedAssembly.Version.Minor <= runtimeAssemblyName.Version.Minor;
75+
}
76+
4377
private bool IsRuntimeAssembly(AssemblyName assemblyName)
78+
=> _runtimeAssemblies.Value.ContainsKey(assemblyName.Name);
79+
80+
private bool TryGetRuntimeAssembly(AssemblyName assemblyName, out ScriptRuntimeAssembly assembly)
81+
=> _runtimeAssemblies.Value.TryGetValue(assemblyName.Name, out assembly);
82+
83+
private ResolutionPolicyEvaluator GetResolutionPolicyEvaluator(string policyName)
4484
{
45-
return _runtimeAssemblies.Value.Contains(assemblyName.Name) ||
46-
assemblyName.Name.StartsWith("system.", StringComparison.OrdinalIgnoreCase);
85+
if (_resolutionPolicyEvaluators.Value.TryGetValue(policyName, out ResolutionPolicyEvaluator policy))
86+
{
87+
return policy;
88+
}
89+
90+
throw new InvalidOperationException($"'{policyName}' is not a valid assembly resolution policy");
4791
}
4892

49-
private Assembly LoadInternal(AssemblyName assemblyName)
93+
private Assembly LoadCore(AssemblyName assemblyName)
5094
{
5195
foreach (var probingPath in _probingPaths)
5296
{
@@ -70,16 +114,15 @@ protected override Assembly Load(AssemblyName assemblyName)
70114
return assembly;
71115
}
72116

73-
if (IsRuntimeAssembly(assemblyName))
117+
if (TryGetRuntimeAssembly(assemblyName, out ScriptRuntimeAssembly scriptRuntimeAssembly))
74118
{
75119
// If there was a failure loading a runtime assembly, ensure we gracefuly unify to
76120
// the runtime version of the assembly if the version falls within a safe range.
77121
if (TryLoadRuntimeAssembly(new AssemblyName(assemblyName.Name), out assembly))
78122
{
79-
AssemblyName runtimeAssemblyName = AssemblyNameCache.GetName(assembly);
123+
var policyEvaluator = GetResolutionPolicyEvaluator(scriptRuntimeAssembly.ResolutionPolicy);
80124

81-
if (runtimeAssemblyName.Version.Major == assemblyName.Version.Major &&
82-
runtimeAssemblyName.Version.Minor == assemblyName.Version.Minor)
125+
if (policyEvaluator.Invoke(assemblyName, assembly))
83126
{
84127
return assembly;
85128
}
@@ -88,7 +131,7 @@ protected override Assembly Load(AssemblyName assemblyName)
88131
return null;
89132
}
90133

91-
return LoadInternal(assemblyName);
134+
return LoadCore(assemblyName);
92135
}
93136

94137
private bool TryLoadRuntimeAssembly(AssemblyName assemblyName, out Assembly assembly)
@@ -221,12 +264,14 @@ protected static string ResolveFunctionBaseProbingPath()
221264
return Path.Combine(basePath, "bin");
222265
}
223266

224-
private static string[] GetRuntimeAssemblies()
267+
private static Dictionary<string, ScriptRuntimeAssembly> GetRuntimeAssemblies()
225268
{
226269
string assembliesJson = GetRuntimeAssembliesJson();
227270
JObject assemblies = JObject.Parse(assembliesJson);
228271

229-
return assemblies["runtimeAssemblies"].ToObject<string[]>();
272+
return assemblies["runtimeAssemblies"]
273+
.ToObject<ScriptRuntimeAssembly[]>()
274+
.ToDictionary(a => a.Name, StringComparer.OrdinalIgnoreCase);
230275
}
231276

232277
private static string GetRuntimeAssembliesJson()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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.Text;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.Description
9+
{
10+
internal sealed class ScriptRuntimeAssembly
11+
{
12+
public string Name { get; set; }
13+
14+
public string ResolutionPolicy { get; set; }
15+
}
16+
}

0 commit comments

Comments
 (0)