Skip to content

Commit

Permalink
Add host option to disable AssemblyLoadContext
Browse files Browse the repository at this point in the history
A host can disable the use of AssemblyLoadContext by returning
a value of "true" or "1" when GetHostOption is called with the
option name "DisableAssemblyLoadContext".

This is an escape hatch for hosts that have issues with the ALC
behavior, allowing them to opt out until the underlying issues
can be fixed.
  • Loading branch information
mhutch committed Jan 10, 2024
1 parent 6ec6c31 commit 79ec659
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Assembly LoadInAssemblyLoadContext (AssemblyLoadContext loadContext)
return loadContext.LoadFromStream (new MemoryStream (Assembly));
}
}
#else
#endif
public Assembly LoadInCurrentAppDomain ()
{
if (DebugSymbols != null) {
Expand All @@ -47,6 +47,5 @@ public Assembly LoadInCurrentAppDomain ()
return System.Reflection.Assembly.Load (Assembly);
}
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,51 @@ namespace Mono.TextTemplating;

partial class CompiledTemplate
{
[SuppressMessage ("Performance", "CA1822:Mark members as static", Justification = "Same API for ALC and AppDomain build variants of the class")]
sealed class TemplateAssemblyContext : IDisposable
/// <summary>
/// Abstracts over loading assemblies into an AssemblyLoadContext or AppDomain
/// and resolving assemblies from the host.
/// </summary>
abstract class TemplateAssemblyContext : IDisposable
{
public abstract Assembly LoadAssemblyFile (string assemblyPath);
public abstract Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData);
public virtual void Dispose () { }

[SuppressMessage ("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "Conditionally compiled version of this returns multiple concrete types")]

public static TemplateAssemblyContext Create (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles)
{
#if FEATURE_ASSEMBLY_LOAD_CONTEXT
if (!host.IsAssemblyLoadContextDisabled ()) {
return new AssemblyLoadContextTemplateAssemblyContext (host, referenceAssemblyFiles);
}
#endif
return new CurrentAppDomainTemplateAssemblyContext (host, referenceAssemblyFiles);
}
}

#if FEATURE_ASSEMBLY_LOAD_CONTEXT
sealed class AssemblyLoadContextTemplateAssemblyContext : TemplateAssemblyContext
{
readonly TemplateAssemblyLoadContext templateContext;
public TemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) => templateContext = new (referenceAssemblyFiles, host);
public Assembly LoadAssemblyFile (string assemblyPath) => templateContext.LoadFromAssemblyPath (assemblyPath);
public Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInAssemblyLoadContext (templateContext);
public void Dispose () { }
#else
readonly CurrentDomainAssemblyResolver assemblyResolver;
public TemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) => assemblyResolver = new (referenceAssemblyFiles, host.ResolveAssemblyReference);
public Assembly LoadAssemblyFile (string assemblyPath) => Assembly.LoadFile (assemblyPath);
public Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInCurrentAppDomain ();
public void Dispose () => assemblyResolver.Dispose ();
public AssemblyLoadContextTemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles)
=> templateContext = new (referenceAssemblyFiles, host);
public override Assembly LoadAssemblyFile (string assemblyPath) => templateContext.LoadFromAssemblyPath (assemblyPath);
public override Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInAssemblyLoadContext (templateContext);
}
#endif

sealed class CurrentAppDomainTemplateAssemblyContext : TemplateAssemblyContext
{
readonly CurrentDomainAssemblyResolver assemblyResolver;
public CurrentAppDomainTemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles)
=> assemblyResolver = new (referenceAssemblyFiles, host.ResolveAssemblyReference);
public override Assembly LoadAssemblyFile (string assemblyPath) => Assembly.LoadFile (assemblyPath);
public override Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInCurrentAppDomain ();
public override void Dispose ()
{
base.Dispose ();
assemblyResolver.Dispose ();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TemplateProcessor : MarshalByRefObject
[SuppressMessage ("Performance", "CA1822:Mark members as static", Justification = "Needs to be an instance for MarshalByRefObject")]
public string CreateAndProcess (ITextTemplatingEngineHost host, CompiledAssemblyData templateAssemblyData, string templateAssemblyFile, string fullName, CultureInfo culture, string[] referencedAssemblyFiles)
{
using var context = new TemplateAssemblyContext (host, referencedAssemblyFiles);
using var context = TemplateAssemblyContext.Create (host, referencedAssemblyFiles);

Assembly assembly = templateAssemblyData is not null
? context.LoadInMemoryAssembly (templateAssemblyData)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if !FEATURE_ASSEMBLY_LOAD_CONTEXT

using System;
using System.IO;
using System.Reflection;
Expand Down Expand Up @@ -49,5 +47,3 @@ public void Dispose ()
}
}
}

#endif
16 changes: 16 additions & 0 deletions Mono.TextTemplating/Mono.TextTemplating/HostOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Microsoft.VisualStudio.TextTemplating;

static class HostOptionExtensions
{
const string DisableAlcOptionName = "DisableAssemblyLoadContext";

static bool IsOptionTrue (this ITextTemplatingEngineHost host, string optionName) =>
host.GetHostOption(optionName) is string optionVal
&& (optionVal == "1" || optionVal.Equals("true", StringComparison.OrdinalIgnoreCase));

public static bool IsAssemblyLoadContextDisabled (this ITextTemplatingEngineHost host) => host.IsOptionTrue (DisableAlcOptionName);
}

0 comments on commit 79ec659

Please sign in to comment.