Need to Solve Memory Leak with CodeDom/Need to find out how to use AppDomain to unload Asslembly

Hi,

I have the created the class shown in the code snippet from various sources around the net.   The code is supposed to give me a class that I can use to evaluate Javascript on the fly.

Sample usage is

private Evaluator eval = new Evaluator();
string x=eval.EvalJScript("(6*5)/3");

the variable x will get the value of 10.  as you can see, this is very useful for quite a number of things.   This code works very well to do what I need, however I am getting a problem.   Every Time I create a new Evaluator Class, its loading a new assembly into the app, and so after a while, a huge amount of memory is being leaked and wasted.  Considering I am using this from within an asp.net app, after a while the asp.net process uses too much memory and gets recycled.   What I need is to find a way to unload the assembly when I am finished with it.   I have been reading around the net and have discovered that you are supposed to create a seperate AppDomain, load the assembly created by CodeDom into this AppDomain, and then destroy this seperate AppDomain when finished to force the unload of the assembly.   My problem now is I have found no help on how to actually do this with my code below.   Can anyone help or give an example on how I may achieve this with my code.

Many Thanks,

Steed.
public class Evaluator
    {
        Type evaluatorType;
        object evaluator;
 
        public string EvalJScript(string JScript)
        {
            return evaluatorType.InvokeMember("Eval", BindingFlags.InvokeMethod, null, evaluator, new object[] { JScript }).ToString();
        }
 
 
        public Evaluator()  //class constructor
        {
            string JScriptSource =
            @"package Evaluator
            {
            class Evaluator
            {
            public function Eval(expr : String) : String
            {
            return eval(expr, ""unsafe"");
            }
            }
            }";
 
            CodeDomProvider compiler = CodeDomProvider.CreateProvider("JScript");
            CompilerParameters parameters = new CompilerParameters();
            parameters.CompilerOptions = "/t:library";
            parameters.GenerateInMemory = true;
            CompilerResults results = compiler.CompileAssemblyFromSource(parameters, JScriptSource);
            Assembly assembly = results.CompiledAssembly;
            evaluatorType = assembly.GetType("Evaluator.Evaluator");
            evaluator = Activator.CreateInstance(evaluatorType);
        }
 
    }

Open in new window

LVL 1
SteelsteedAsked:
Who is Participating?

Improve company productivity with a Business Account.Sign Up

x
 
wizrrConnect With a Mentor Commented:
I don't know how to do that through IIS. For any .NET assembly you can manage security through caspol tool (all assemblies must be signed):
http://msdn2.microsoft.com/en-us/library/cb6t8dtz(VS.80).aspx

But to verify that you have SecurityPermissionFlag.ControlAppDomain flag of  SecurityPermission class you need to apply this attribute to your assembly: [SecurityPermissionAttribute(SecurityAction.Demand, Unrestricted=true)]. If your assembly will not be loaded by IIS - that means your assembly have no SecurityPermissionFlag.ControlAppDomain and other flags, and cannot load\create AppDomains.

This is not solution, source of your problem can be something another.

As solution try also to add your IIS running user to administrator group (if you testing your site at home\work you can do that). After that you can know what is problem actually.
0
 
wizrrCommented:
Have you looked to MSDN sample for that?
http://msdn2.microsoft.com/en-us/library/system.appdomain.aspx

You can use sample, just derive your Evaluator from MarshalByRefObject and use it in CreateInstanceAndUnwrap.
0
 
SteelsteedAuthor Commented:
Hi there, thanks for that info, however I am still unsure of how to apply this to the code I have.  Lets rephrase my skill on the subject, I am actually a noob, so I hope you dont mind if I ask for a helping hand.
I am unsure of how to use the MarshalByRefObject to get something to use with CreateInstanceAndUnwrap.   CreateInstanceAndUnwrap seems to want an assembly as a file on disk .. but in the current code my assembly is in memory. ... am not sure what exactly to do to glue things together ....
0
Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

 
wizrrCommented:
Let's see.
//--
// Creates a new instance of the specified type. Parameters specify the assembly where the type is defined, and the name of the type.
//--
public Object AppDomain.CreateInstanceAndUnwrap (
      string assemblyName,
      string typeName
)
//--
//assemblyName
//The display name of the assembly. See Assembly.FullName.
//--
//typeName
//The fully qualified name of the requested type, including the namespace but not the assembly, as returned by the Type.FullName property.



    
    public static string EvalJScript(string JScript) {
        // Construct and initialize settings for a second AppDomain.
        AppDomainSetup ads = new AppDomainSetup();
        ads.ApplicationBase = 
            System.Environment.CurrentDirectory;
        ads.DisallowBindingRedirects = false;
        ads.DisallowCodeDownload = true;
        ads.ConfigurationFile = 
            AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
 
        // Create the second AppDomain.
        AppDomain ad2 = AppDomain.CreateDomain("AD #2", null, ads);
 
        // Create an instance of MarshalbyRefType in the second AppDomain. 
        // A proxy to the object is returned.
        Evaluator mbrt = 
            (Evaluator) ad2.CreateInstanceAndUnwrap(
                typeof(Evaluator).Assembly.FullName, 
                typeof(Evaluator).FullName
            );
 
        // Call a method on the object via the proxy, passing the 
        // default AppDomain's friendly name in as a parameter.
        return ( mbrt.EvalJScript(JScript) );
    }
 
    public class Evaluator : MarshalByRefObject
    {
        Type evaluatorType;
        object evaluator;
 
        public string EvalJScript(string JScript)
        {
            return evaluatorType.InvokeMember("Eval", BindingFlags.InvokeMethod, null, evaluator, new object[] { JScript }).ToString();
        }
 
 
        public Evaluator()  //class constructor
        {
            string JScriptSource =
            @"package Evaluator
            {
            class Evaluator
            {
            public function Eval(expr : String) : String
            {
            return eval(expr, ""unsafe"");
            }
            }
            }";
 
            CodeDomProvider compiler = CodeDomProvider.CreateProvider("JScript");
            CompilerParameters parameters = new CompilerParameters();
            parameters.CompilerOptions = "/t:library";
            parameters.GenerateInMemory = true;
            CompilerResults results = compiler.CompileAssemblyFromSource(parameters, JScriptSource);
            Assembly assembly = results.CompiledAssembly;
            evaluatorType = assembly.GetType("Evaluator.Evaluator");
            evaluator = Activator.CreateInstance(evaluatorType);
        }
 
    }

Open in new window

0
 
wizrrCommented:
Oh. Forgot this:

AppDomain.Unload(ad2);

to unload your new app domain and free all resources and libs.
0
 
SteelsteedAuthor Commented:
Hi There,

I have modded my code to look like the snippet below.  I changed things around a bit so as to make it fit in with the way I have been using the class in my project.
I am getting a problem however that the line
Eval mbrt =(Eval)ad2.CreateInstanceAndUnwrap(typeof(Eval).Assembly.FullName,typeof(Eval).FullName);

is making a System.IO.FileNotFoundException saying that the assembly cant be found.   Any ideas ?

Thanks,

Steed.
    public class Evaluator
    {
        public string EvalJScript(string JScript)
        {
            // Construct and initialize settings for a second AppDomain.
            AppDomainSetup ads = new AppDomainSetup();
            ads.ApplicationBase =Environment.CurrentDirectory;
            ads.DisallowBindingRedirects = false;
            ads.DisallowCodeDownload = true;
            ads.ConfigurationFile =AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            
            // Create the second AppDomain.
            AppDomain ad2 = AppDomain.CreateDomain("AD #2", null, ads);
 
            // Create an instance of MarshalbyRefType in the second AppDomain.  A proxy to the object is returned.
            Eval mbrt =(Eval)ad2.CreateInstanceAndUnwrap(typeof(Eval).Assembly.FullName,typeof(Eval).FullName);
 
            // Call a method on the object via the proxy, passing the 
            // default AppDomain's friendly name in as a parameter.
            string res = mbrt.EvalJScript(JScript);
 
            AppDomain.Unload(ad2);
            
 
            return res;
        }
    }
 
    public class Eval : MarshalByRefObject
    {
        Type evaluatorType;
        object evaluator;
 
        public string EvalJScript(string JScript)
        {
            return evaluatorType.InvokeMember("Eval", BindingFlags.InvokeMethod, null, evaluator, new object[] { JScript }).ToString();
        }
 
 
        public Eval()  //class constructor
        {
            string JScriptSource =
            @"package Evaluator
            {
            class Evaluator
            {
            public function Eval(expr : String) : String
            {
            return eval(expr, ""unsafe"");
            }
            }
            }";
 
            CodeDomProvider compiler = CodeDomProvider.CreateProvider("JScript");
            CompilerParameters parameters = new CompilerParameters();
            parameters.CompilerOptions = "/t:library";
            parameters.GenerateInMemory = true;
            CompilerResults results = compiler.CompileAssemblyFromSource(parameters, JScriptSource);
            Assembly assembly = results.CompiledAssembly;
            evaluatorType = assembly.GetType("Evaluator.Evaluator");        //this refers to the package and class in the jscript
            evaluator = Activator.CreateInstance(evaluatorType);
        }
 
    }

Open in new window

0
 
wizrrCommented:
Strange. Hmm. You can sign your assembly with key and register in GAC. This allows as reference and load it without depending to location of assembly.

Another way maybe, is to set

ads.ApplicationBase = ...;//Environment.CurrentDirectory;

to directory where your assembly (assembly where Eval is defined) is located. If that not helps - i don't know(
0
 
SteelsteedAuthor Commented:
Hiya,

It seems the monsterous code is getting no simpler.   Let me explain what seems to be happening now.   This class I am using in inside a Class Library that is being used from my main asp.net app.  Now it seems that Environment.CurrentDirectory was actually bringing back the system32 folder and so the file was not being found.  I have set this to the folder where  the dll for this class lib is in.  Now instead of the System.IO.FileNotFoundException error I am getting a System.IO.FileLoadException, which seems to be because of an access denied.  The full error msg is

Could not load file or assembly 'hmsModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Access is denied.

hmsModel is the assembly name of the class library that this code is within.   So it seems that I cant load it again because its already in use ?   Any ideas ?   I would need to be able to load and unload multiple instances as far as I am aware.    From what I am reading I would actually need a different AppDomain for each and every assembly instance I load.  Is this right ?
0
 
SteelsteedAuthor Commented:
it seems to be something to do with the fact that I am referencing the library in the project already.   I have tried moving the code to a separate library/assembly, however I still have to reference the lib in order do do the casting to the type Eval in the line

Eval mbrt =(Eval)ad2.CreateInstanceAndUnwrap(typeof(Eval).Assembly.FullName,typeof(Eval).FullName);

I am unsure if this is the real problem though

0
 
wizrrCommented:
>> So it seems that I cant load it again because its already in use ?
No. You just have limited access in your code. I think your site must be full trust, without restrictions. I think this is hosting problem. This problem also can occur if you have no NTFS access 'execute' rights or something.
>> I would need to be able to load and unload multiple instances as far as I am aware.    From what I am reading I would actually need a different AppDomain for each and every assembly instance I load.  Is this right ?
Yes. This is solution.
>> it seems to be something to do with the fact that I am referencing the library in the project already.
No. Maybe you need SecurityPermissionFlag.ControlAppDomain permission to create your own AppDomain.
0
 
SteelsteedAuthor Commented:
So where and how would I tweak these security settings if that is the problem ?
I am running this off my XP development box, with the project setup in IIS 5.1
Remember that the library has to have execute rights for it to run the code inside it to begin with, and that is working.  Its just trying to load a new instance of the assembly into a different AppDomain that is failing.   If I were to somehow implement this SecurityPermissionFlag.ControlAppDomain permission, where would I code that ?  The NTFS permissions might be wrong, but I dont think so as the code is able to use this library/assembly quite fine by just referencing it.
0
 
Computer101Commented:
Forced accept.

Computer101
EE Admin
0
 
BinShao1987Commented:
good, use AppDomain can fix the MS Bug.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.