-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Error in Eval using a referenced assembly with unloading enabled. #355
Comments
This is a example project to reproduce the issue: https://github.com/MUN1Z/CsSrcipt.unload.issue |
This is a very interesting problem and it's not an easy task to solve...but possible. I have provided the code sample for that. Anyway, let's try. Though, in your first code snippet you are preparing for unloading the referenced assembly Next, if you remove Though with the very current API you cannot unload assembly if your eval-script does not return anything. Thus the API needs to change to allow that. I have done the change (af55aab) and it will be available in the very next release. For your convenience I have provided the extension method for that so you do not have to wait. Next, you need to be careful with eval type of scenarios. Every time an instance of the tipe from a give assembly typecasted to Note in the code sample below I had to call And below is the complete working sample that shows the use of the shared script form the unloadable eval-script: I do recommend avoid using eval and using LoadMethod instead. It's more manageable ( using System.Reflection;
using Microsoft.CodeAnalysis;
using CSScripting;
using CSScriptLib;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
var useDelegate = false;
for (int i = 0; i < 20; i++)
{
Console.WriteLine("Loaded assemblies count: " + AppDomain.CurrentDomain.GetAssemblies().Count());
call_UnloadAssembly(useDelegate);
GC.Collect();
}
}
static Assembly printer_asm;
static void call_UnloadAssembly(bool useDelegate)
{
var info = new CompileInfo { RootClass = "Printing", AssemblyFile = "Printer.dll" };
if (printer_asm == null)
printer_asm = CSScript.Evaluator
.CompileCode(@"using System;
using System.Diagnostics;
public class Printer
{
public static void Print() =>
Console.WriteLine(""Printing..."");
}", info);
if (useDelegate)
{
var script = CSScript.Evaluator
.With(eval => eval.IsAssemblyUnloadingEnabled = true)
.ReferenceAssembly(printer_asm)
.LoadMethod<ICalc>(@"public int Sum(int a, int b)
{
Printing.Printer.Print();
return a+b;
}");
script.Sum(1, 2);
script.GetType().Assembly.Unload();
}
else
{
var result = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true)
.ReferenceAssembly(printer_asm)
.EvalAndUnload("Printing.Printer.Print();");
}
}
}
public interface ICalc
{
int Sum(int a, int b);
}
}
static class Extension
{
public static object EvalAndUnload(this IEvaluator evaluator, string scriptText, bool unloadableDependency = true)
{
var info = new CompileInfo { CodeKind = SourceCodeKind.Script };
var asm = evaluator.CompileCode(scriptText, info);
// note that the leading dot is missing "css_Root" vs ".css_Root"; this is required for asms compiled with inloadable context
var entryPointType = asm.GetType($"{info.RootClass}", true, false);
var entryPointMethod = entryPointType?.GetTypeInfo().GetDeclaredMethod("<Factory>") ?? throw new InvalidOperationException("Script entry point method could be found.");
var submissionFactory = (Func<object[], Task<object>>)entryPointMethod.CreateDelegate(typeof(Func<object[], Task<object>>));
var result = submissionFactory.Invoke(new object[] { null, null }).Result;
asm.Unload();
return result;
}
} |
I understand, I'm not at home right now but in a little while I'll run the example you sent, thank you very much @oleg-shilo . Regarding the use of Eval, it was the closest I could get to simulating the behavior of lua scripts that I use on Tibia servers. I need the developer who will create the scripts to be free to create and call objects, variables, methods, create his own business rule in that script and override one or two delegates to in the end return the script instance to be stored and called when some player performs a certain action. but I will analyze the use of the load method calmly, it may be possible to change it if I change some behaviors in the scripts. I'm going to leave an example of the pattern that is used in lua and as I'm recreating it in eval, I want to make it very similar so I can convert these lua scripts to csx automatically in the future. Regarding reloading scripts, they will be executed when a player with permission needs to speak a specific command in the game, for example /reload scripts, or via terminal on the server, execute the same command. |
@oleg-shilo I tested it now, so the same problem still occurs, the problem occurs when you put "IsAssemblyUnloadingEnabled = true" in the printer assembly and reference the assembly in Eval. In my case I need to reload both the assemblies created from scripts and the scripts that only use those assembly. |
Correct. That's why I did not marked the shared assembly for unloading.
That was an architectural reason but it also served an additional purpose: Roslyn is not able to reference assemblies that are loaded with the "collectable" context. Thus you may want to consider not unloading the assemblies at all or not unloading the shared scripts/assemblies. After all it might not be such a pressure on your memory as it might seem. But it only you can answer this question. BTW, it's only CS-Script who is trying to do something about unloading. Roslyn completely expects you to do eval and forget about it. Completely ignoring the fact that a tiny assembly has been created. You can also consider an early days technique that CS-Script implemented when true unloading was not available. You cna group your eval jobs in to batches hosted in a dedicated AppDomain. And when the job is doen then just unload the domain with all these eval-assemblies in it. I have already captures some of those considerations here |
This is an unbearable bug, if we keep compiling dynamic methods (with |
Agree. Though most likely Roslyn team would reply to that "It's not a bug but a feature" :) Mind you, in late 2002, I lodged the feature request on MS Connect for unloading dynamically loaded assemblies. Interestingly enough the ticket handler (.NET ASP lead) agreed with my request but admitted... "it would be too hard to implement". But 17 years later they did it. So it came a long way... Anyway, indeed uncontrolled evals may lead to effective memory leaks. So it's your call. You can be OK with it as long as your set of dynamic assemblies is fixed. Or if you prefer, you can switch to the unloadable options as I mentioned (e.g. using interfaces, in many cases, it's a preferred option for architectural reasons). |
Hello @oleg-shilo , i am trying reload my script files. I have a assembly loaded called Libs, all my scripts uses this Libs with reference in Roslyn Eval method.
Could not load file or assembly 'ℛ*1240c384-fa14-4563-9208-e649e77e3a95#1-0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the specified file.
The Eval method do not find the referenced assembly before addition of "IsAssemblyUnloadingEnabled" flag in CompileCode method.
If i remove the flag, the Eval find the assembly and works fine, but i cannot unload the asm assembly:
The text was updated successfully, but these errors were encountered: