.NETGURU
AppDomains + remoting
Messages   Related Types
This message was discovered on microsoft.public.dotnet.framework.

Post a new message to this list...

Aaron
Hi,

I've implemented an elegant way to provide a plug-in, hierarchical,
architecture to our application with sub-application isolation -
AppDomains. These AppDomains are loaded and unloaded as neccessary
within the application. Both synchronous and asynchronous events that
cross AppDomain boundaries (and even machine bouindaries) are used
extensively throughout also. These events are often propogated from
child applications up the application hierarchy to the root
application eg: Log messages.

Recently, I noticed the handle count of the application to climb
steadily when AppDomains were instantiated, usually ~20/AppDomain. The
handle count does not drop when AppDomains are unloaded. My initial
thought was garbage collection. However, even explicit calls to
Collect() haven't halted this behaviour.

I call BeginInvoke when firing an event asynchronously without
providing a Async callback. Could this the evil in our code? I've
looked through the documentation and I can't find anything to indicate
that EndInvoke is neccessary. My concern about providing such a
callback is that if a child application fires an event and the
AppDomain in which the child existed is then unloaded then undesirable
behaviour may occur.

I've checked my code to ensure no resources are left open before the
unloading of a domain. Is there a known leak associated with the use
of AppDomains?

Help!

Aaron.
Reply to this message...
 
    
David Levine
[Original message clipped]

it depends on the object that you use it on. For objects derived from
Control it should not be necessary to call EndInvoke. For other objects,
especially those doing network IO work, there is a high possibility that a
memory leak can occur. if you search through the archives at this site you
should find a post discussing this issue.

http://blogs.msdn.com/cbrumme

[Original message clipped]

Not with AppDomains - an old post by Chris Brumme indicated that there is a
leak of about 16 bytes per appdomain (or some low number like this)...it
would take thousands of loads/unloads before that became a problem.

Reply to this message...
 
    
Aaron
Sijin & David,

Thanks for taking the time to have a look at this for me.

Here is a piece of code that leaks 4 handles per AppDomain:

using System;
using System.Runtime;
using System.Threading;

namespace AppDomainTest
{
public class AppDomainClass : MarshalByRefObject
{
public void Go()
{
Thread t = new Thread(new ThreadStart(ThreadProc));
t.Start();
}
private void ThreadProc() {    }
}

class AppDomainTest
{
[STAThread]
static void Main(string[] args)
{
for (int i = 0; i < 50; i++)
{            
    Console.WriteLine("Loading AppDomain...");
    AppDomain domain = AppDomain.CreateDomain("TempDomain");
    Console.WriteLine("Creating instance of AppDomainClass...");
    AppDomainClass adc =
(AppDomainClass)domain.CreateInstanceAndUnwrap("AppDomainTest",
"AppDomainTest.AppDomainClass");
    Console.WriteLine("Calling Go()...");
    adc.Go();
    Thread.Sleep(1000);
    Console.WriteLine("Unloading domain...");
    AppDomain.Unload(domain);
    Console.WriteLine("Domain unloaded.");                
}             
}
}
}

I have found that the only way to avoid this leak is to explicitly
call GC.Collect() from within the context of the AppDomain before
unloading it. However, even this method does not alleviate the handle
leak that occurs when registering an event callback that spans an
AppDomain boundary. Seems like a bug to me...

I guess this isn't a huge issue for alot of people as AppDomains are
not used in the way I am using them. Perhaps they were never
*intended* to be utilised like this?

Sijin - The handles created are listed as an event with description
"Thread synchronisation object" in SysInternals' Process Explorer.
This true for both threads and .net events.

Ideas??

Aaron.

"David Levine" <Click here to reveal e-mail address> wrote in message news:<#Click here to reveal e-mail address>...
[Original message clipped]

Reply to this message...
 
    
David Levine
My quick take on your example is that all the thread does it run and
immediately die as the thread proc returns immediately. When the appdomain
is unloaded all running threads that originated in the appdomain are unwound
via a ThreadAbort and the thread is terminated, but since they've already
terminated this should not be necessary. The thread that calls into the
appdomain is unwound to the boundary of the appdomain. You should not need
to call GC.Collect either because that is done for you by the runtime within
the context of the doomed appdomain.

So it looks to me like the test code is fine. I don't know why this
consumes 4 handles per appdomain (but I haven't verified this on my machine
either).

I use appdomains but I don't create/destroy them that frequently. I load the
app into a secondary appdomain, and then when the user logs out it is
unloaded. As you state, this may be a bug. Have you tried this out on the
Whidbey bits?

"Aaron" <Click here to reveal e-mail address> wrote in message
news:Click here to reveal e-mail address...
[Original message clipped]

Reply to this message...
 
    
Aaron
After *extensive* thrashing about it in the code I think I've finally
realised what's going on. In my "Application Manager" class I wire up
a message event. In this case the event spans app domain boundaries.
If I wire up this event the object is never garbage collected, even if
I unwire the event. This is where the handle leak was coming from as
mutexes consume 2 handles/mutex. Failing to connect the event results
in object collection, and subsequent resource deallocation.

Surely someone must know a way around this one??

Aaron.

"David Levine" <Click here to reveal e-mail address> wrote in message news:<Click here to reveal e-mail address>...
[Original message clipped]

Reply to this message...
 
    
David Levine
Why don't you unsubscribe when you want to unload the appdomain? You should
be able to release handles you allocated yourself.

Also, when you say the event spans appdomain boundaries it isn't clear
exactly what you mean. You cannot call directly between appdomains -
something must marshal the calls between them. Are you using remoting? Using
a cross-appdomain calling mechanism? A mailbox scheme between two threads?

"Aaron" <Click here to reveal e-mail address> wrote in message
news:Click here to reveal e-mail address...
[Original message clipped]

Reply to this message...
 
    
Aaron
Hi David,

I use AppDomain.CreateInstanceAndUnWrap to create my application, so
I'm using a transparent proxy to remote the object.

It might be time for some code (apologies for the length). Note: If I
register *any* of the events (MessageEvent, UnhandledException,
ProcessExit) the object is never queued for garbage collection, even
though I explicitly deregister the class as a listener prior to
releasing the reference to it. If I don't register these events then
the destructor is called. Weird. This is the offending
ApplicationManager code:

using System;
using APT.Common.Events;
using System.Threading;
using System.Runtime.Remoting;
using System.Security.Policy;
using System.Reflection;
using NetMAARS.Data;

namespace AppDomainTest
{    
    /// <summary>
    /// Manages the execution of an application
    /// </summary>
    /// <remarks>The class manages the loading and execution of an
application object. Applications are loaded into their own application
domain</remarks>
    /// <summary>
    /// Manages the execution of an application
    /// </summary>
    /// <remarks>The class manages the loading and execution of an
application object. Applications are loaded into their own application
domain</remarks>
    public class ApplicationManager : MessageFactory
    {    
        /// <summary>
        /// application
        /// </summary>
        protected Application Application;                

        /// <summary>
        /// The application domain in which the application is loaded and
executed
        /// </summary>
        protected AppDomain domain = null;
        
        /// <summary>
        /// state change mutex
        /// </summary>
        private Mutex StateMutex = new Mutex();            

        /// <summary>
        /// Ensure no one is acting on the domain at the same time
        /// </summary>
        private Mutex DomainMutex = new Mutex();
        
        /// <summary>
        /// Application state change delegate
        /// </summary>
        delegate void ApplicationStateDelegate();        

        /// <summary>
        /// Application state
        /// </summary>
        private ApplicationState State = ApplicationState.UNKNOWN;        

        /// <summary>
        /// Internal runstate flag
        /// </summary>
        private bool Running = false;        
    
        /// <summary>
        /// The name of the application
        /// </summary>
        public string ApplicationName
        {
            // get only
            get { return ApplicationMeta == null ? "<Unknown>" :
ApplicationMeta.Name; }
        }    

        /// <summary>
        /// The default start timeout
        /// </summary>
        public const int DefaultStartTimeout = 60000;

        /// <summary>
        /// The time to wait for starting
        /// </summary>
        public int StartTimeout = DefaultStartTimeout;

        /// <summary>
        /// Message event handler
        /// </summary>
        /// <param name="sender">The sending object</param>
        /// <param name="e">The message event parameters</param>
        public void OnMessageEvent(object sender, MessageEventArgs e)
        {
            // refire
            FireMessageEvent(e);
        }        

        /// <summary>
        /// Internal application meta
        /// </summary>
        protected IApplicationMeta ApplicationMeta;
        
        /// <summary>
        /// Initialise the application
        /// </summary>
        protected virtual void InitApplication()
        {
            Application.MessageEvent += new
MessageEventHandler(this.OnMessageEvent);
        }

        /// <summary>
        /// Fires a message event
        /// </summary>
        /// <param name="Message">The message</param>
        /// <param name="MessageType">The message type</param>
        /// <remarks>Caller is defaulted to
"ApplicationManager(APPLICATION_NAME)"</remarks>
        private void FireMessageEvent(string Message, MessageTypeEnum
MessageType)
        {
            // fire!
            FireMessageEvent(String.Format("ApplicationManager({0})",
ApplicationName), Message, MessageType);
        }

        /// <summary>
        /// Fires a message event
        /// </summary>
        /// <param name="Message">The message event</param>
        /// <remarks>The message type is defaulted to INFORMATION</remarks>
        private void FireMessageEvent(string Message)
        {
            // fire!
            FireMessageEvent(Message, MessageTypeEnum.INFORMATION);
        }

        /// <summary>
        /// Internal start application
        /// </summary>
        private void InternalStartApplication()
        {
            try
            {                
                // start it
                bool bSuccess;
                
                if (!Running)
                {                    
                    // start it
                    bSuccess = Application.Start();
                    //FireMessageEvent(String.Format("Application {0} started with
return code {1}", ApplicationName, bSuccess, bSuccess ?
MessageTypeEnum.INFORMATION : MessageTypeEnum.WARNING));
                    Running = bSuccess;
                }
                else
                {
                    // tell them
                    FireMessageEvent(String.Format("Application {0} is already
running", ApplicationName), MessageTypeEnum.WARNING);
                }
            }
            catch(ThreadAbortException)
            {
                // we timed out
                //FireMessageEvent(String.Format("{0} failed to start in an
acceptable amount of time", ApplicationName), MessageTypeEnum.ERROR);
            }
            catch(Exception ex)
            {
                // oh dear
                FireMessageEvent(String.Format("An exception has occurred while
attempting to start application {0}\n{1}", ApplicationName,
ex.ToString()), MessageTypeEnum.ERROR);
            }            
        }    

        private AppDomain LoadDomain()
        {
            AppDomain iad = null;

            try
            {
                // one at a time
                DomainMutex.WaitOne();

                AppDomainSetup ads = new AppDomainSetup();
                Evidence ade = new Evidence(AppDomain.CurrentDomain.Evidence);

                // set the base path
                ads.ApplicationBase = "file:///" + ApplicationMeta.BaseDirectory;
                ads.ConfigurationFile = ApplicationMeta.ConfigurationFile;
        
                // create the domain
                iad = AppDomain.CreateDomain(Guid.NewGuid().ToString(), ade, ads);

                // add our process exit handler
                iad.ProcessExit += new EventHandler(OnDefaultApplicationExit);
                iad.UnhandledException += new
UnhandledExceptionEventHandler(iad_UnhandledException);
            }
            catch(Exception ex)
            {
                // re throw
                throw new Exception(ex.ToString(), ex);
            }
            finally
            {
                // set it back
                DomainMutex.ReleaseMutex();
            }

            return iad;
        }

        /// <summary>
        /// Creates the application instance
        /// </summary>
        protected virtual void CreateApplication()
        {
            // create
            Application = (Application)domain.CreateInstanceAndUnwrap(ApplicationMeta.AssemblyName,
ApplicationMeta.ClassName);
        }
        
        /// <summary>
        /// Checks the application interface
        /// </summary>
        private void CheckApplicationInterface()
        {
            if (Application.GetType().GetInterface("NetMAARS.Data.IApplication")
== null)
                throw new Exception("The target type does not implement the
IApplication interface");
        }
        
        /// <summary>
        /// Starts the application
        /// </summary>
        public bool StartApplication()
        {    
            // success flag
            bool bSuccess = false;
            try
            {
                // one at a time please
                StateMutex.WaitOne();                    
                // load the domain
                domain = LoadDomain();
                // load the application into it
                CreateApplication();    
                // update the status
                State = ApplicationState.STOPPED;
                // ensure it derives from the appropriate class
                CheckApplicationInterface();
                // initialise the application
                InitApplication();
                // update the state
                State = ApplicationState.STARTING;
                // start it!
                ApplicationStateDelegate asd = new
ApplicationStateDelegate(InternalStartApplication);
                // invoke
                IAsyncResult ar = asd.BeginInvoke(null, null);
                if (!ar.AsyncWaitHandle.WaitOne(StartTimeout, true))
                {
                    FireMessageEvent(String.Format("Application failed to start in
the time allocated ({0}ms)", StartTimeout), MessageTypeEnum.ERROR);
                }
                // end it
                asd.EndInvoke(ar);        
                // set the state
                State = Running ? ApplicationState.ACTIVE :
ApplicationState.STOPPED;
            }
            catch(Exception ex)
            {                
                // oh dear
                FireMessageEvent(String.Format("An exception has occurred while
attempting to start application {0}\n{1}", ApplicationMeta.Name,
ex.ToString()), MessageTypeEnum.ERROR);
                // unload the domain
                UnloadDomain();
            }
            finally
            {
                // just set it to the run flag
                bSuccess = Running;
                // release the mutex
                StateMutex.ReleaseMutex();
            }
            // set it
            return bSuccess;
        }        
    
        /// <summary>
        /// unload the app domain in which the application is running
        /// </summary>
        protected void UnloadDomain()
        {            
            try
            {    
                DomainMutex.WaitOne();
                // set the state
                State = ApplicationState.UNKNOWN;
                // ensure its all good
                if (domain != null)
                {
                    // no more handlers pls
                    domain.ProcessExit -= new EventHandler(OnDefaultApplicationExit);
                    domain.UnhandledException -= new
UnhandledExceptionEventHandler(iad_UnhandledException);
                    // unload it
                    AppDomain.Unload(domain);                        
                }    
            }
            catch(ExecutionEngineException eee)
            {
                FireMessageEvent(String.Format("An engine execution exception has
occurred while attempting to unload domain for {0}:\n{1}",
ApplicationName, eee.ToString()), MessageTypeEnum.ERROR);
            }
            catch(Exception ex)
            {
                FireMessageEvent(String.Format("An exception has occurred while
attempting to unload domain for {0}:\n{1}", ApplicationName,
ex.ToString()), MessageTypeEnum.ERROR);
            }
            finally
            {
                // set it to null
                domain = null;
                // set it back
                DomainMutex.ReleaseMutex();
            }
        }        

        /// <summary>
        /// Perform any shutdown tasks
        /// </summary>
        protected virtual void ShutdownApplication()
        {
            // derived classes may ant to do stuff in here
        }

        /// <summary>
        /// Internal stop application worker method
        /// </summary>
        private void InternalStop()
        {
            try
            {    
                bool bSuccess;                                
                // get the success flag
                if (Running)
                {
                    // shut it down
                    ShutdownApplication();

                    // stop it                    
                    bSuccess = Application.Stop();                    
    
                    // tell everyone what happened
                    FireMessageEvent(String.Format("Application {0} has stopped
(result: {1})", ApplicationName, bSuccess),
MessageTypeEnum.INFORMATION);
                    // update running flag
                    Running = !bSuccess;
                }                            
            }
            catch(ThreadAbortException)
            {
                // we timed out
                FireMessageEvent(String.Format("Aborting effort to stop
application {0}", ApplicationName), MessageTypeEnum.INFORMATION);
            }
            catch(Exception ex)
            {
                // uh oh
                FireMessageEvent(String.Format("An exception has occurred while
attempting to stop application {0}:\n{1}", ApplicationName,
ex.ToString()), MessageTypeEnum.INFORMATION);
            }
            finally
            {                            
                // we're stopped
                State = ApplicationState.STOPPED;    
                
                if (Application != null)
                {
                    // deregister listener
                    Application.MessageEvent -= new
MessageEventHandler(OnMessageEvent);
                }
                // null it
                Application = null;
            }
        }

        /// <summary>
        /// Stops the application
        /// </summary>
        public bool StopApplication()
        {
            bool bSuccess = false;
            try
            {    
                // one at a time please
                StateMutex.WaitOne();                
                // state change
                State = ApplicationState.STOPPING;
                // start it!
                ApplicationStateDelegate asd = new
ApplicationStateDelegate(InternalStop);
                // invoke
                IAsyncResult ar = asd.BeginInvoke(null, null);
                if (!ar.AsyncWaitHandle.WaitOne(ApplicationMeta.MaxShutdownWait,
true))
                {
                    FireMessageEvent(String.Format("Application failed to stop in the
time allocated ({0}ms)", ApplicationMeta.MaxShutdownWait),
MessageTypeEnum.ERROR);
                }
                // end it
                asd.EndInvoke(ar);            
                // state up
                State = ApplicationState.STOPPED;
                // set it
                bSuccess = !Running;                
            }
            catch (Exception ex)
            {
                // uh oh
                FireMessageEvent(String.Format("An error has occurred while
stopping application {0}\n{1}", ApplicationName, ex.ToString()),
MessageTypeEnum.ERROR);
            }
            finally
            {                    
                // unload the domain
                UnloadDomain();                
                // let others have a go
                StateMutex.ReleaseMutex();                
            }
            // return it
            return bSuccess;
        }    
        
        /// <summary>
        /// Handles the event raised when the default application domain is
shut down
        /// </summary>
        /// <param name="sender">The sending object</param>
        /// <param name="e">Not used</param>
        public void OnDefaultApplicationExit(object sender, EventArgs e)
        {
            FireMessageEvent("The default application domain is exiting and
this application domain has not been shut down! Application is now
exiting!", MessageTypeEnum.ERROR);
            StopApplication();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="applicationmeta">The application meta</param>
        public ApplicationManager(IApplicationMeta applicationmeta)
        {
            State = ApplicationState.UNKNOWN;
            this.ApplicationMeta = applicationmeta;            
        }    

        ~ApplicationManager()
        {
            FireMessageEvent("Destructor called!!");
        }

        /// <summary>
        /// Create default application info
        /// </summary>
        /// <returns>Current application info</returns>
        private ApplicationInfo CreateApplicationInfo()
        {
            ApplicationInfo ai = new ApplicationInfo();

            // init
            ai.Name = ApplicationName;
            ai.State = State;

            // return
            return ai;
        }

        /// <summary>
        /// Rebuild children
        /// </summary>
        /// <param name="nai">Application info</param>
        /// <returns>Rebuilt application info</returns>
        private ApplicationInfo[] RebuildChildren(ApplicationInfo nai)
        {
            // any kids?
            if (nai.Children == null) return null;        

            // create the new array to hold the new kids
            ApplicationInfo[] Children = new
ApplicationInfo[nai.Children.Length];

            // rebuild
            for(int i = 0; i < nai.Children.Length; i++)
            {
                Children[i] = new ApplicationInfo();
                Children[i].Name = nai.Children[i].Name;
                Children[i].State = nai.Children[i].State;
                Children[i].Children = RebuildChildren(nai.Children[i]);
            }

            // return
            return Children;
        }

        /// <summary>
        /// Gets the application info
        /// </summary>
        /// <returns>Application state</returns>
        public virtual ApplicationInfo GetApplicationInfo()
        {
            ApplicationInfo ai = null;

            try
            {
                // lock it
                DomainMutex.WaitOne();

                // exist?
                if (domain != null)
                {                
                    //FireMessageEvent(String.Format("State = {0}",
Enum.GetName(typeof(ApplicationState), State)));
                    if (State != ApplicationState.STOPPED && State !=
ApplicationState.UNKNOWN)
                    {    
                        // get it
                        ai = Application.GetApplicationInfo();
                    }
                    else
                    {
                        // create the default info
                        ai = CreateApplicationInfo();
                    }
                }
            }
            catch(Exception ex)
            {
                // oh dear
                FireMessageEvent(String.Format("An exception occurred while
attempting to retrieve application info:\n{0}", ex.ToString()),
MessageTypeEnum.ERROR);
                ai = CreateApplicationInfo();
            }
            finally
            {
                // release
                DomainMutex.ReleaseMutex();
            }
            // return it
            return ai;
        }                

        /// <summary>
        /// Unhandled exception handler
        /// </summary>
        /// <param name="sender">The sending object</param>
        /// <param name="e">Params</param>
        public void iad_UnhandledException(object sender,
UnhandledExceptionEventArgs e)
        {
            // inform
            FireMessageEvent(String.Format("An unhandled exception has occurred
(CLR {0} terminating):\n{0}",
((Exception)e.ExceptionObject).ToString(), e.IsTerminating ? "is" :
"is not"), MessageTypeEnum.ERROR);

        }
    }
}
Reply to this message...
 
    
David Levine
"Aaron" <Click here to reveal e-mail address> wrote in message
news:Click here to reveal e-mail address...
[Original message clipped]

Couple of random observations...

You have code like this...

catch(Exception ex)
{
// re throw
throw new Exception(ex.ToString(), ex);
}

As written you'd be better off not catching this at all. It would be better
to add an error message that added some meaning to it. Simply using the
error message from the inner exception is not worth the effort since the
message is available to whatever higher level code that catches it.

Also, when you call EndInvoke it is possible for that to throw an exception.
That may not be a problem but it could skip over code you intend to
execute.

There is nothing in there that leaps out as being wrong, but to be honest
this code sample is too long to really get a good feel for, especially since
it has references to custom assemblies that only you have, so it cannot be
built as it is.

If you could post a simple but complete code sample that reproduces the
problem, without requiring external DLLs, then someone could be of more
assistance.

Reply to this message...
 
    
Sijin Joseph
You can use the Performance Monitor counters on your application to
check what are the actual handles that are being created. Thatmight help
you to troubleshoot your problem.

Sijin Joseph
http://www.indiangeek.net
http://weblogs.asp.net/sjoseph

Aaron wrote:
[Original message clipped]

Reply to this message...
 
 
System.AppDomain
System.AppDomainSetup
System.Console
System.Enum
System.EventArgs
System.EventHandler
System.Exception
System.ExecutionEngineException
System.GC
System.Guid
System.IAsyncResult
System.MarshalByRefObject
System.Messaging.MessageType
System.Security.Policy.Evidence
System.String
System.Threading.Mutex
System.Threading.Thread
System.Threading.ThreadAbortException
System.Threading.ThreadStart
System.UnhandledExceptionEventArgs
System.UnhandledExceptionEventHandler
System.Windows.Forms.Application




ExamGuru IT Solutions - .Net Guru is owned and operated by ExamGuru, Inc., the man behind .Net Guru. If you're in the market for bespoke software or software consultancy, why not get him and his highly trained team to help? - www.examguru.net/ITCertification
Ad


Need Dot Net Interview Questions?
Ask ExamGuru, Inc. for advice and help on Passing .Net Interviews
.Net Projects
Best-of-breed application framework for .NET projects, developed by ExamGuru, Inc. and ExamGuru IT
Free .net Help
Commission ExamGuru, Inc. and his team for your next bespoke software project
FogBUGZ
The only bug tracking system carefully crafted with one goal in mind: helping teams create great software.
Awesome Tools
If you don't know about these, you're missing out... IT Certification Questions
IT Interview Questions
Free Oracle 10g Training
MCSE Boortcamp
Cisco Study Guides
Cheap Study Guides
Exact Questions
Dot Net Interview Questions
Oracle OCP
Cheap Travel
Designer Perfumes - Wholesale Prices
Free Programming Tutorials
 
ExamGuru IT Solutions - .Net Guru is owned and operated by ExamGuru, Inc., the man behind .Net Guru. If you're in the market for bespoke software or software consultancy, why not get him and his highly trained team to help? - www.examguru.net/ITCertification
 Copyright © ExamGuru, Inc. 2001-2006
Contact Us - Terms of Use - Privacy Policy - www.dot-net-guru.com - www.examguru.net - www.oraclesource.net - www.itinterviews.net - www.examguru.net/ITCertification