AppDomains + remoting

  • Thread starter Thread starter Aaron
  • Start date Start date
A

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.
 
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.
Using BeginInvoke without a corresponding EndInvoke may result in a leak -
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

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?

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.
 
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.
 
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 said:
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.



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.
Using BeginInvoke without a corresponding EndInvoke may result in a leak -
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

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?

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.
[/QUOTE]
 
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 said:
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 said:
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" <[email protected]> wrote in message
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.

Using BeginInvoke without a corresponding EndInvoke may result in a leak -
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


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?

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.
 
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 said:
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" <[email protected]> wrote in message
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 said:
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" <[email protected]> wrote in message
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.

Using BeginInvoke without a corresponding EndInvoke may result in a leak -
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


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?

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.
 
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 = new ApplicationInfo();
Children.Name = nai.Children.Name;
Children.State = nai.Children.State;
Children.Children = RebuildChildren(nai.Children);
}

// 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);

}
}
}
 
Aaron said:
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:

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.
 
Back
Top