So, after doing a ton of research on how to sandbox a c# script compiler so that the assemblies being loaded are only loaded into the sandbox AppDomain and not my primary AppDomain, I have run into the problem where all of the dll's I created are unloaded upon unloading the sandbox AppDomain EXCEPT FOR ONE. That one is from a script that looks like:
return new Func<List<int>,int>
(
(list) =>
{
var total = 0;
foreach (int i in list)
{
total += i;
}
return total;
}
);
Now what happens with the returns from the scripts is that they are all eventually returned in a dictionary to the primary AppDomain. The rest of the scripts all return simple serializable objects or primitives and as I said all of their assemblies unload correctly and I am able to delete them with the primary domain still active. Is it possible that this particular return has to "pass" back the creating assembly to the primary AppDomain because its value is a Func? Is there no way around this?
The reason I have the sandbox in the first place is so that after a set of scripts runs I can dispose of the object that executed them and that dispose method unloads the sandbox domain and deletes all of the created assemblies. I want to be able to use this in a constantly running environment like a web server where a build up of assemblies is problematic and currently, every time a script set runs with a return of a Func, I am going to have a lingering assembly. I would rather not have an asterisk next to the documentation of using this library, so any ideas would be welcome.
For reference, here is my code that compiles the script:
var provider = new CSharpCodeProvider(new Dictionary<string, string>() { { CompilerOptionName, CompilerOptionVersion } });
var compilerParams = new CompilerParameters { GenerateExecutable = false, GenerateInMemory = false };
compilerParams.ReferencedAssemblies.AddRange(References);
compilerParams.TempFiles = new TempFileCollection(BinPath);
compilerParams.OutputAssembly = Path.Combine(BinPath,
Utilities.AssemblyPrefix + ProfigurationExe.Profiguration.ID + "_" + Action.ID + "_Script" + Utilities.DllExt);
// If no object is returned by user code, enter in a return null to satisfy returning an object for
// default code template. If they do have a return that will return first.
Code = Code + ReturnNull;
var parameterString = BuildParameterString();
parameterString = !String.IsNullOrEmpty(parameterString) ? ", " + parameterString : String.Empty;
// If someone simply imports namespaces separated by spaces, commas, or semicolons, create a proper using statement
if (!String.IsNullOrEmpty(Imports) && !IsUsingRegex.IsMatch(Imports))
{
Imports = String.Join("; ", Imports.Split(" ,;".ToCharArray()).Select(s => Using + " " + s)) + "; ";
}
FinalCode = String.Format(Imports + CodeTemplate,
new object[] {DefaultNamespace, ClassName, DefaultMethod, parameterString, Code});
// This just is a variable of the code that will be compiled, with all spaces and line breaks removed
var debugFriendlierFinalCode = U.CompressWhiteSpaceRegex.Replace(FinalCode.Replace("\r", String.Empty).Replace("\n", String.Empty), U.OneSpace);
// Note that we are adding the import fields to the beginning in case user wants to import namespaces (and not have to always use fully qualified class names)
var results = provider.CompileAssemblyFromSource(compilerParams, FinalCode);
Assembly = results.CompiledAssembly;
if (!results.Errors.HasErrors)
{
return Assembly;
}
// If there were compiler errors, aggregrate them into an exception.
var errors = new StringBuilder("Dynamic Code Compiler Errors :\r\n");
foreach (CompilerError error in results.Errors)
{
errors.AppendFormat("Line {0},{1}\t: {2}\n",
error.Line, error.Column, error.ErrorText);
}
throw new ProfigurationException(errors.ToString());
The Func
is serializable and is being copied like you intend. But it points to code that resides inside of the assembly you want to unload. That's why the assembly is being loaded in the parent AppDomain as well.
Not sure what to recommend. I hope it makes sense that you cannot unload code that is being used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With