perf(scripting): cache ScriptSandbox BCL + pinned metadata references (#603)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -81,10 +81,12 @@ public static class ScriptSandbox
|
||||
// surface (operator-authored hosting helpers, third-party packages, app code)
|
||||
// is not on the list and stays unreachable.
|
||||
var references = new List<MetadataReference>();
|
||||
// #603: both halves are cached. CreateFromFile reads + maps the DLL, and the BCL half is ~170
|
||||
// assemblies, so doing it per compile was the dominant fixed cost of every ScriptEvaluator build.
|
||||
// MetadataReference is immutable and thread-safe, so sharing instances across compilations is safe.
|
||||
foreach (var asm in pinnedAssemblies)
|
||||
references.Add(MetadataReference.CreateFromFile(asm.Location));
|
||||
foreach (var path in EnumerateBclAssemblyPaths())
|
||||
references.Add(MetadataReference.CreateFromFile(path));
|
||||
references.Add(PinnedReferences.GetOrAdd(asm, static a => MetadataReference.CreateFromFile(a.Location)));
|
||||
references.AddRange(BclReferences.Value);
|
||||
|
||||
var imports = new[]
|
||||
{
|
||||
@@ -97,6 +99,13 @@ public static class ScriptSandbox
|
||||
return new SandboxConfig(references, imports);
|
||||
}
|
||||
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<System.Reflection.Assembly, MetadataReference> PinnedReferences = new();
|
||||
|
||||
// The trusted-platform set is process-constant, so the BCL reference list is context-independent.
|
||||
private static readonly Lazy<IReadOnlyList<MetadataReference>> BclReferences = new(
|
||||
static () => EnumerateBclAssemblyPaths().Select(static p => (MetadataReference)MetadataReference.CreateFromFile(p)).ToArray(),
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
private static IEnumerable<string> EnumerateBclAssemblyPaths()
|
||||
{
|
||||
// The .NET host advertises the resolved runtime-shared-framework + BCL DLL set
|
||||
|
||||
@@ -57,4 +57,18 @@ public sealed class ScriptSandboxBuildTests
|
||||
"""return (double)ctx.GetTag("X").Value;""");
|
||||
evaluator.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>#603: the BCL reference set is built once per process, so two Build calls share the same
|
||||
/// <c>MetadataReference</c> instances rather than re-reading ~170 DLLs per compile.</summary>
|
||||
[Fact]
|
||||
public void Build_reuses_metadata_reference_instances_across_calls()
|
||||
{
|
||||
var first = ScriptSandbox.Build(typeof(ScriptContext));
|
||||
var second = ScriptSandbox.Build(typeof(ScriptContext));
|
||||
|
||||
first.References.Count.ShouldBeGreaterThan(50);
|
||||
first.References.Count.ShouldBe(second.References.Count);
|
||||
for (var i = 0; i < first.References.Count; i++)
|
||||
ReferenceEquals(first.References[i], second.References[i]).ShouldBeTrue($"reference {i} was rebuilt");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user