Solved

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

Posted on 2007-12-04
14
1,892 Views
Last Modified: 2013-12-16
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

0
Comment
Question by:Steelsteed
14 Comments
 
LVL 3

Expert Comment

by:wizrr
ID: 20409888
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
 
LVL 1

Author Comment

by:Steelsteed
ID: 20410528
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
 
LVL 3

Expert Comment

by:wizrr
ID: 20410613
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
Master Your Team's Linux and Cloud Stack

Come see why top tech companies like Mailchimp and Media Temple use Linux Academy to build their employee training programs.

 
LVL 3

Expert Comment

by:wizrr
ID: 20410625
Oh. Forgot this:

AppDomain.Unload(ad2);

to unload your new app domain and free all resources and libs.
0
 
LVL 1

Author Comment

by:Steelsteed
ID: 20410980
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
 
LVL 3

Expert Comment

by:wizrr
ID: 20411049
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
 
LVL 1

Author Comment

by:Steelsteed
ID: 20411796
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
 
LVL 1

Author Comment

by:Steelsteed
ID: 20412102
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
 
LVL 3

Expert Comment

by:wizrr
ID: 20413464
>> 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
 
LVL 1

Author Comment

by:Steelsteed
ID: 20419785
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
 
LVL 3

Accepted Solution

by:
wizrr earned 500 total points
ID: 20421014
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
 
LVL 1

Expert Comment

by:Computer101
ID: 20592692
Forced accept.

Computer101
EE Admin
0
 

Expert Comment

by:BinShao1987
ID: 39576648
good, use AppDomain can fix the MS Bug.
0

Featured Post

3 Use Cases for Connected Systems

Our Dev teams are like yours. They’re continually cranking out code for new features/bugs fixes, testing, deploying, testing some more, responding to production monitoring events and more. It’s complex. So, we thought you’d like to see what’s working for us.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The object model of .Net can be overwhelming at times – so overwhelming that quite trivial tasks often take hours of research. In this case, the task at hand was to populate the datagrid from SQL Server database in Visual Studio 2008 Windows applica…
This document covers how to connect to SQL Server and browse its contents.  It is meant for those new to Visual Studio and/or working with Microsoft SQL Server.  It is not a guide to building SQL Server database connections in your code.  This is mo…
Two types of users will appreciate AOMEI Backupper Pro: 1 - Those with PCIe drives (and haven't found cloning software that works on them). 2 - Those who want a fast clone of their boot drive (no re-boots needed) and it can clone your drive wh…
A short tutorial showing how to set up an email signature in Outlook on the Web (previously known as OWA). For free email signatures designs, visit https://www.mail-signatures.com/articles/signature-templates/?sts=6651 If you want to manage em…

770 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question