AppDomains (“application domains”)


An
AppDomain is a light-weight process. 
Well, if you actually measure the costs associated with an AppDomain –
especially the first one you create, which has some additional costs that are
amortized over all subsequent ones – then “light-weight” deserves some
explanation:


size=2> 


A Win32
process is heavy-weight compared to a Unix process. style="mso-spacerun: yes">  A Win32 thread is heavy-weight compared
to a Unix thread, particularly if you are using a non-kernel user threads
package on Unix.  A good design for
Windows will create and destroy processes at a low rate, will have a small
number of processes, and will have a small number of threads in each
process.


size=2> 


Towards
the end of V1, we did some capacity testing using ASP.NET. style="mso-spacerun: yes">  At that time, we were able to squeeze
1000 very simple applications /
AppDomains into a single worker process. 
Presumably that process would have had 50-100 threads active in it, even
under heavy load.  If we had used OS
processes for each application, we would have 1000 CLRs with 1000 GC heaps. style="mso-spacerun: yes">  More disturbing, we would have at least
10,000 threads.  This would reserve
10 GB of VM just for their default 1 MB stacks (though it would only commit a
fraction of that memory).  All those
threads would completely swamp the OS scheduler.


size=2> 


Also, if
you execute a lot of processes, it’s key that those processes are filled with
shared pages (for example, the same code loaded at the same preferred addresses)
rather than private pages (like dynamically allocated data). style="mso-spacerun: yes">  Unfortunately, JITted code results in
private pages.  Our NGEN mechanism
can be used to create pre-JITted images that can be shared across
processes.  But NGEN is not a
panacea: NGEN images must be explicitly generated; if their dependencies change
through versioning, modifications to security policy, etc., then the loader will
reject the images as invalid and quietly fall back on JITting; NGEN images
improve load time, but they actually insert a small steady-state cost to some
operations, due to indirections; and NGEN can do a worse job of achieving
locality than JITting and dynamically loading types (at least in the absence of
a training scenario).


size=2> 


Over
time, I think you’ll see NGEN address many of these limitations and become a
core part of our execution story.


size=2> 


Of
course, I wouldn’t recommend that you actually run a process with 1000
AppDomains either.  For example,
address space is an increasingly scarce resource – particularly on servers. style="mso-spacerun: yes">  The version of the CLR we just shipped
now supports 3 GB of user address space, rather than the 2 GB that is normally
available.  (You need to boot the
system for this, and sacrifice OS buffer space, so don’t do it unless you really
need it).  64-bit systems, including
a 64-bit CLR, cannot come soon enough for certain scenarios.


size=2> 


Compared
to our goals, it still takes too long to create and destroy AppDomains. style="mso-spacerun: yes">  The VM and working set hits are too
high.  And the cost of crossing an
AppDomain boundary is embarrassing. 
But the general architecture is sound and you should see improvements in
all these areas in future releases.


size=2> 


It’s too
simplistic to say that AppDomains are just light-weight OS processes. style="mso-spacerun: yes">  There is more to say in several
dimensions:


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Security
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Instance lifetime
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Type identity
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Domain-neutrality
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Per-AppDomain state like static fields
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Instance-agility
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Configuration and assembly binding
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Unloading and other resource management
  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"
    > face=Tahoma size=2>Programming model

size=2> 


style="mso-bidi-font-weight: normal"> face=Tahoma>Security


Code
Access Security only works within an OS process. style="mso-spacerun: yes">  Threads freely call through AppDomain
boundaries, so the CLR must be able to crawl stacks across those boundaries to
evaluate permission demands.  In
fact, it can crawl compressed stacks that have been disassociated from their
threads, accurately evaluating permissions based on AppDomains that have already
been unloaded.


size=2> 


It’s
conceivable that one day we will have a sufficiently strong notion of
distributed trust that we can usefully propagate compressed stacks into other
processes.  However, I don’t expect
we’ll see that sort of distributed security for at least another couple of
releases.


size=2> 


It’s
possible to apply different security policy or different security evidence at
the granularity of an AppDomain. 
Any grants that would result based on AppDomain evidence and policy are
intersected with what would be granted by policy at other levels, like machine
or enterprise.  For example,
Internet Explorer attaches a different codebase to an AppDomain to indicate the
origin of the code that’s running in it. 
There are two ways for the host to control security at an AppDomain
granularity.  Unfortunately, both
techniques are somewhat flawed:


size=2> 


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l4 level1 lfo2; tab-stops: list .5in"> style="mso-fareast-font-family: Tahoma; mso-bidi-font-family: Tahoma"> style="mso-list: Ignore">1) style="FONT: 7pt 'Times New Roman'">     
The host can pre-load a set of
highly-trusted assemblies into an AppDomain. style="mso-spacerun: yes">  Then it can modify the security policy
to be more restrictive and start loading less-trusted application code. style="mso-spacerun: yes">  The new restricted policy will only
apply to these subsequent loads. 
This approach is flawed because it forces the host to form a closure of
the initial highly-trusted set of assemblies. style="mso-spacerun: yes">  Whatever technique the host uses here is
likely to be brittle, particularly in the face of versioning. style="mso-spacerun: yes">  Any dependent assemblies that are
forgotten in the initial load will be limited by the restricted policy. style="mso-spacerun: yes">  Furthermore, it is unnecessarily
expensive to eagerly load assemblies, just so they can escape a particular
security policy.


size=2> 


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l4 level1 lfo2; tab-stops: list .5in"> style="mso-fareast-font-family: Tahoma; mso-bidi-font-family: Tahoma"> style="mso-list: Ignore">2) style="FONT: 7pt 'Times New Roman'">     
The host can load the application
assemblies with extra evidence. 
When the security system evaluates the grant set for these assemblies,
this extra evidence can be considered and the application assemblies will get
reduced permissions.  This technique
allows the host to lazily load highly trusted assemblies into the same
AppDomain, since these won’t have the extra evidence attached to them. style="mso-spacerun: yes">  Unfortunately, this technique also has a
rough edge.  If an application
assembly has a dependency on a second application assembly, what is going to
attach extra evidence to the 2nd assembly? style="mso-spacerun: yes">  I suppose the host could get the
1st assembly’s dependencies and eagerly load them. style="mso-spacerun: yes">  But now we are back on a plan where
transitive closures must be eagerly loaded in order to remain secure. style="mso-spacerun: yes">  And, in future releases, we would like
to give each assembly a chance to run initialization code. style="mso-spacerun: yes">  There’s a risk that such initialization
code might run and fault in the dependencies before the host can explicitly load
them with extra evidence.


size=2> 


We need
to do better here in a future release.


size=2> 


Until
then, code injection remains a real concern. style="mso-spacerun: yes">  A host carefully prepares an AppDomain
and loads some partially trusted application code there for execution. style="mso-spacerun: yes">  If the application code can inject
itself into a different AppDomain (especially the default AppDomain, which is
presumably where the fully trusted host is executing), then it can escape the
policy and extra evidence that is constraining it. style="mso-spacerun: yes">  This is one reason that we don’t provide
AppDomain enumeration services to partially trusted code. style="mso-spacerun: yes">  If you can find an AppDomain, you can
perform an AppDomain.DoCallBack into it passing a delegate. style="mso-spacerun: yes">  This has the effect of marshaling the
delegate into that AppDomain and then dispatching to it there. style="mso-spacerun: yes">  The assemblies containing the delegate
and the target of the delegate will be created in the specified
AppDomain.


size=2> 


Today,
if a host exercises great care, it can use AppDomains as the basis of building a
secure environment.  In the future,
we would like to reduce the amount of care required of the host. style="mso-spacerun: yes">  One obvious way to do this is to involve
the host in any assembly loads that happen in any AppDomain. style="mso-spacerun: yes">  Unfortunately, that simple approach
makes it difficult to make wise decisions on loading assemblies as
domain-neutral, as we’ll see later.


size=2> 


style="mso-bidi-font-weight: normal">Instance
Lifetime


The CLR
contains a tracing GC which can accurately, though non-deterministically, detect
whether an object is still reachable. 
It is accurate because, unlike a conservative GC, it knows how to find
all the references.  It never leaves
objects alive just because it can’t distinguish an object reference from an
integer with the same coincidental set of bits. style="mso-spacerun: yes">  Our GC is non-deterministic because it
optimizes for efficient memory utilization. style="mso-spacerun: yes">  It collects portions of the GC heap that
it predicts will productively return memory to the heap, and only when it thinks
the returned memory warrants the effort it will expend.


size=2> 


If the
GC can see an orphaned cycle where A refers to B and B refers to A (but neither
A nor B are otherwise reachable), it will collect that cycle. style="mso-spacerun: yes">  However, you can create cycles that the
GC cannot trace through and which are therefore uncollectible. style="mso-spacerun: yes">  A simple way to do this is to have
object A refer to object B via a GCHandle rather than a normal object
reference.  All handles are
considered part of the root-set, so B (and thus A) is never
collected.


size=2> 


The GC
cannot trace through unmanaged memory either. style="mso-spacerun: yes">  Any cycles that involve COM objects will
be uncollectible.  It is the
application’s responsibility to explicitly break the cycle by nulling a
reference, or by calling ReleaseComObject, or by some other technique. style="mso-spacerun: yes">  Of course, this is standard practice in
the COM world anyway.


size=2> 


Nor can
the GC trace across processes. 
Instead, Managed Remoting uses a system of leases to achieve control over
distributed lifetime.  Calls on
remote objects automatically extend the lease the client holds. style="mso-spacerun: yes">  Leases can trivially be made infinite,
in which case the application is again responsible for breaking cycles so that
collection can proceed. 
Alternatively, the application can provide a sponsor which will be
notified before a remote object would be collected. style="mso-spacerun: yes">  This gives the application the
opportunity to extend leases “on demand”, which reduces network
traffic.


size=2> 


By
default, if you don’t access a remote object for about 6 minutes, your lease
will expire and your connection to that remote object is lost. style="mso-spacerun: yes">  You can try this yourself, with a remote
object in a 2nd process. 
But listen carefully:  you
can also try it with a remote object in a 2nd AppDomain. style="mso-spacerun: yes">  If you leave your desk for a cup of tea,
your cross-AppDomain references can actually timeout and disconnect!


size=2> 


Perhaps
one day we will build a distributed GC that is accurate and non-deterministic
across a group of processes or even machines. style="mso-spacerun: yes">  Frankly, I think it’s just as likely
that we’ll continue to rely on techniques like configurable leases for
cross-process or cross-machine lifetime management.


size=2> 


However,
there’s no good reason for using that same mechanism cross-AppDomain. style="mso-spacerun: yes"> There’s a relatively simple way for us to
trace object references across AppDomain boundaries – even in the presence of
AppDomain unloading.  This would be
much more efficient than what we do today, and would relieve developers of a big
source of problems.


size=2> 


We
should fix this.


size=2> 


style="mso-bidi-font-weight: normal">Type
Identity


Managed
objects can be marshaled across AppDomain boundaries according to one of several
different plans:


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Unmarshalable

size=2>This is the default for all types. 
If an object is not marked with the Serializable custom attribute, it
cannot be marshaled.  Any attempt to
pass such an object across an AppDomain boundary will result in an
exception.


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Marshal-by-value

size=2>This is the default for all types that are marked as Serializable, unless
they inherit from MarshalByRefObject. 
During a single marshal of a graph of objects, identity is
preserved.  But if the same object
is marshaled on two separate calls from AppDomain1 to AppDomain2, this will
result in two unrelated instances in AppDomain2.


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Marshal-by-reference

size=2>Any Serializable types that inherit from System.MarshalByRefObject will
marshal by reference.  This causes
an identity-preserving proxy to be created in the client’s AppDomain. style="mso-spacerun: yes">  Most calls and any field accesses on
this proxy will remote the operation back to the server’s AppDomain. style="mso-spacerun: yes">  There are a couple of calls, defined on
System.Object (like GetType), which might actually execute in the client’s
AppDomain.


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Marshal-by-bleed

size=2>Certain objects are allowed to bleed. style="mso-spacerun: yes">  For the most part, this bleeding is an
optional performance optimization. 
For example, if you pass a String object as an argument on a call to a
remoted MarshalByRefObject instance, the String is likely to bleed across the
AppDomain boundary.  But if you
create a value type with an Object[] field, put that same String into the
Object[], and pass the struct, the current marshaler might not bleed your
String.  Instead, it’s likely to be
marshaled by value.


size=2> 


In
other cases, we absolutely require that an instance marshal by bleed. style="mso-spacerun: yes">  System.Threading.Thread is a good
example of this.  The same managed
thread can freely call between AppDomains. 
Since the current marshaler cannot guarantee that an instance will always
bleed, we have made Thread unmarshalable by the marshaler for now. style="mso-spacerun: yes">  Then the CLR bleeds it without using the
marshaler when you call Thread.CurrentThread.


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Identity-preserving marshal-by-value

As
we’ve seen, objects which marshal by value only preserve identity in a single
marshaling operation, like a single remoted call. style="mso-spacerun: yes">  This means that, the more you call, the
more objects you create.  This is
unacceptable for certain objects, like certain instances of System.Type. style="mso-spacerun: yes">  Instead, we marshal the type specifier
from one AppDomain to another, effectively do a type load in the 2nd
AppDomain (finding any corresponding type that has already been loaded, of
course) and then treat that type as the result of the unmarshal.


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Custom marshaling

size=2>The Managed Remoting and serialization architectures are quite
flexible.  They contain sufficient
extensibility for you to define your own marshaling semantics. style="mso-spacerun: yes">  Some researchers at Microsoft tried to
build a system that transparently migrated objects to whatever client process
was currently using them.  I’m not
sure how far they got.


size=2> 


How does
all this relate to type identity? 
Well, instances of System.Type, and the metaobjects reachable from them
like MethodInfos and PropertyInfos, can be marshaled in two different ways. style="mso-spacerun: yes">  If the underlying assembly was loaded as
domain-neutral into the two AppDomains involved in a remote operation, then the
metaobjects from that assembly will be marshaled-by-bleed. style="mso-spacerun: yes">  If instead the underlying assembly was
loaded per-domain, then the metaobjects from that assembly will be
identity-preserving marshaled-by-value.


size=2> 


style="mso-bidi-font-weight: normal"> face=Tahoma>Domain-neutrality


So
what’s this domain-neutral vs. per-domain distinction? style="mso-spacerun: yes">  Remember when I said that a key to good
performance is to have lots of shared pages and to minimize private pages? style="mso-spacerun: yes">  At the time, I was talking about sharing
pages across processes.  But the
same is true of sharing pages across AppDomains. style="mso-spacerun: yes">  If all the AppDomains in a process can
use the same JITted code, MethodTables, MethodDescs and other runtime
structures, this will give us a dramatic performance boost when we create more
AppDomains in that process.


size=2> 


If an
assembly is loaded domain-neutral, we just mean that all these data structures
and code are available in all the different AppDomains. style="mso-spacerun: yes">  If that same assembly is loaded
per-domain, we have to duplicate all those structures between
AppDomains.


size=2> 


In V1
and V1.1 of the CLR, we offer the following policies for determining which
assemblies should be domain-neutral:


size=2> 


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l1 level1 lfo4; tab-stops: list .5in"> style="mso-fareast-font-family: Tahoma; mso-bidi-font-family: Tahoma"> style="mso-list: Ignore">1) style="FONT: 7pt 'Times New Roman'">     
Only share mscorlib.dll. style="mso-spacerun: yes">  This choice is the default. style="mso-spacerun: yes">  We must always share mscorlib, because
the operating system will only load one copy of mscorwks.dll (the CLR) into a
process.  And there are many 1:1
references backwards and forwards between mscorwks and mscorlib. style="mso-spacerun: yes">  For this reason, we need to be sure
there’s only a single mscorlib.dll, shared across all the different
AppDomains.


size=2> 


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l1 level1 lfo4; tab-stops: list .5in"> style="mso-fareast-font-family: Tahoma; mso-bidi-font-family: Tahoma"> style="mso-list: Ignore">2) style="FONT: 7pt 'Times New Roman'">     
Share all strongly-named
assemblies.  This is the choice made
by ASP.NET.  It’s a reasonable
choice for them because all ASP.NET infrastructure is strongly-named and happens
to be used in all AppDomains.  The
code from web pages is not strongly-named and tends to be used only from a
single AppDomain anyway.


size=2> 


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l1 level1 lfo4; tab-stops: list .5in"> style="mso-fareast-font-family: Tahoma; mso-bidi-font-family: Tahoma"> style="mso-list: Ignore">3) style="FONT: 7pt 'Times New Roman'">     
Share all assemblies. style="mso-spacerun: yes">  I’m not aware of any host or application
which uses this choice.


size=2> 


Wait a
second.  If sharing pages is such a
great idea, why isn’t everyone using “Share all assemblies”? style="mso-spacerun: yes">  That’s because domain-neutral code has a
couple of drawbacks.  First and most
importantly, domain-neutral code can never be unloaded. style="mso-spacerun: yes">  This is an unfortunate consequence of
our implementation, though fixing it will be quite hard. style="mso-spacerun: yes">  It may be several more releases before
we even try.


size=2> 


A second
drawback is that domain-neutral code introduces a few inefficiencies. style="mso-spacerun: yes">  Usually the working set benefits quickly
justify these inefficiencies, but there may be some scenarios (like
single-AppDomain processes!) where this isn’t true. style="mso-spacerun: yes">  These inefficiencies include a 1:M
lookup on all static field accesses and some high costs associated with deciding
when to execute class constructors. 
That’s because the code is shared across all AppDomains, yet each
AppDomain needs its own copy of static fields which are initialized through its
own local execution of a .cctor method. 
You can reduce the overhead associated with .cctors (whether in
domain-neutral code or not) by marking your .cctors with tdBeforeFieldInit. style="mso-spacerun: yes">  I’ve mentioned this in prior
blogs.


size=2> 


Finally,
in V1 & V1.1, we don’t allow you to combine NGEN with domain-neutral
code.  This may not be a concern for
you, given the other limitations associated with NGEN today. style="mso-spacerun: yes">  And I’m confident that we’ll remove this
particular restriction in a future release.


size=2> 


Okay,
but this still sucks.  Why are these
choices so limited?  Ideally a host
would specify a set of its own assemblies and some FX assemblies for
sharing.  Since these assemblies
would be intrinsic to the operation of the host, it wouldn’t matter that they
can never unload.  Then the
application assemblies would be loaded per-domain.


size=2> 


We can’t
support this because, if one assembly is loaded as domain-neutral, all the other
assemblies in its binding closure must also be loaded as domain-neutral. style="mso-spacerun: yes">  This requirement is trivially satisfied
by the first and third policies above. 
For the 2nd policy, we rely on the fact that strong-named
assemblies can only early-bind to other strong-named assemblies.


size=2> 


If we
didn’t require an entire binding closure to be domain-neutral, then references
from a domain-neutral assembly to a per-domain assembly would require a 1:M
lookup, similar to what we do for static field accesses. style="mso-spacerun: yes">  It’s easy to see how this sort of lookup
can work for static field access. 
But it’s much harder to see what kind of indirections would allow a
domain-neutral type to inherit from a per-domain one. style="mso-spacerun: yes">  All the instance field offsets, base
class methods, and VTable slots would need biasing via a 1:M lookup. style="mso-spacerun: yes">  Ouch.


size=2> 


In fact,
long term we’re not trying to find some more flexible policies for a host to
specify which assemblies can be loaded domain-neutral. style="mso-spacerun: yes">  It’s evil to have knobs that an
application must set.  We really
want to reach a world where the CLR makes sensible decisions on the most
appropriate way to execute any application. style="mso-spacerun: yes">  To get there, we would like to remove
the inefficiencies and differing semantics associated with domain-neutral code
and make such assemblies unloadable. 
Then we would like to train our loader to notice those AppDomains which
will necessarily make identical binding decisions (more on this later). style="mso-spacerun: yes">  This will result in maximum automatic
sharing.


size=2> 


It’s not
yet clear whether/when we can achieve this ideal.


size=2> 


style="mso-bidi-font-weight: normal"> face=Tahoma>Per-AppDomain state like static
fields


As
stated above, domain-neutrality would ideally be a transparent optimization that
the system applies on behalf of your application. style="mso-spacerun: yes">  There should be no observable semantics
associated with this decision, other than performance.


size=2> 


Whether
types are domain-neutral or not, each AppDomain must get its own copy of static
fields.  And a class constructor
must run in each of those AppDomains, to ensure that these static fields are
properly initialized.


size=2> 


style="mso-bidi-font-weight: normal"> face=Tahoma>Instance-agility


We just
discussed how domain-neutrality refers to assemblies and how they are shared
between AppDomains. 
Instance-agility refers to object instances and how they are allowed to
flow between AppDomains.


size=2> 


An agile
instance must necessarily be of a type we loaded as domain-neutral. style="mso-spacerun: yes">  However, the converse is not true. style="mso-spacerun: yes">  The vast majority of domain-neutral
types do not have agile instances.


size=2> 


If an
instance marshals-by-bleed or if it performs identity-preserving
marshal-by-value, then by definition it is agile. style="mso-spacerun: yes">  The effect is the same in both cases:
it’s possible to have direct references to the same instance from multiple
AppDomains.


size=2> 


This is
in contrast to normal non-agile instances which are created, live and die in a
single AppDomain.  We don’t bother
to track which AppDomain these instances belong to, because we can infer
this.  If a thread is accessing an
instance, then the instance is clearly in the same AppDomain that the thread is
currently executing in.  If we find
references to an instance further back on a thread’s stack, then we can use the
AppDomain transitions which are recorded on that stack to determine the correct
AppDomain.  And – for per-domain
types – the type itself can tell us which AppDomain the instance belongs
to.


size=2> 


Although
we don’t normally track the AppDomain which contains an instance, there are some
exceptions.  For example, a
Finalizable object must be finalized in the AppDomain it lives in. style="mso-spacerun: yes">  So when an instance is registered for
finalization, we always record the current AppDomain at that time. style="mso-spacerun: yes">  And the finalizer thread(s) take care to
batch up instances in the same AppDomain to minimize transitions.


size=2> 


For an
instance to be agile, it must satisfy these rules:


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>It must be of a type that was loaded as
    domain-neutral.  (Today, we
    restrict ourselves to types in mscorlib.dll, which is always
    domain-neutral).
  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>The type must not be unloaded until the last instance has
    died.  (Today, we never unload
    these types).
  • style="MARGIN: 0in 0in 0pt; mso-list: l3 level1 lfo3; tab-stops: list .5in"
    > face=Tahoma size=2>Instances must not have references to any other instances
    that are not themselves agile.

size=2> 


Based on
these rules, it’s actually possible for the loader to identify some types as
having legally agile instances. 
System.String is a good example, because it is sealed and has no
references to other instances. 
However, this automatic detection would be inadequate for our
purposes.  We need some additional
objects like System.Threading.Thread to be agile. style="mso-spacerun: yes">  Since Thread can contain references to
many objects that are clearly not agile (like managed thread local storage,
which contains arbitrary application objects), we have to be very careful
here.


size=2> 


In this
case, being careful means that we partition some of the Thread’s state in a
per-AppDomain manner.


size=2> 


If
you’ve read my earlier blogs, you know that static fields can be per-AppDomain,
per-Thread, per-Context, or per-process (RVA-based statics). style="mso-spacerun: yes">  Now you know why the per-Thread and
per-Context statics are still partitioned by AppDomain. style="mso-spacerun: yes">  And you understand why the per-process
statics are restricted from containing arbitrary object references. style="mso-spacerun: yes">  They can only contain scalars, Strings
(agile instances!) and value types that are themselves similarly
constrained.


size=2> 


If
you’ve done much debugging with AppDomains and exceptions, you’ve probably
noticed that the first pass of exception handling is always terminated at an
AppDomain boundary.  It’s annoying:
if the exception goes unhandled and you take your last chance as a trap to the
debugger, you’ve lost the original context of the exception. style="mso-spacerun: yes">  But now it’s clear why this
happens.  If an exception instance
isn’t agile, it must be marshaled from one AppDomain to the next as the dispatch
occurs.  (We make a special
exception for an AppDomain-agile OutOfMemoryException that we pre-create, so
that it’s available when we don’t have enough memory to make a per-AppDomain
instance).


size=2> 


In fact,
there’s a lot of complexity involved in ensuring that instances are only
accessible from one AppDomain, or that they follow the discipline necessary for
agility.  You may be wondering why
we care.  We care because AppDomain
isolation is a fundamental guarantee of the managed environment, on which many
other guarantees can be built.  In
this sense, it is like separate address spaces for OS processes. style="mso-spacerun: yes">  Because of AppDomain isolation, we can
build certain security guarantees and we can reclaim resources correctly when
AppDomains are unloaded.


size=2> 


style="mso-bidi-font-weight: normal"> face=Tahoma>Configuration and Assembly Binding


Since
each AppDomain is expected to execute a different application, each AppDomain
can have its own private paths for binding to its assemblies, its own security
policy, and in general its own configuration. style="mso-spacerun: yes">  Even worse, a host can listen to the
AssemblyResolveEvent and dynamically affect binding decisions in each
AppDomain.  And the application can
modify configuration information like the AppDomain’s private path – even as it
runs.  This sets up terrible data
races, which rely on unfortunate side effects like the degree of inlining the
JIT is performing and how lazy or aggressive the loader is in resolving
dependent assemblies.  Applications
that rely on this sort of thing are very fragile from one release of the CLR to
the next.


size=2> 


This
also makes it very difficult for the loader to make sensible and efficient
decisions about what assemblies can be shared. style="mso-spacerun: yes">  To do a perfect job, the loader would
have to eagerly resolve entire binding closures in each AppDomain, to be sure
that those AppDomains can share a single domain-neutral assembly.


size=2> 


Frankly,
we gave the host and the application a lot of rope to hang themselves. style="mso-spacerun: yes">  In retrospect, we screwed up.


size=2> 


I
suspect that in future versions we will try to dictate some reasonable
limitations on what the host and the AppDomain’s configuration can do, at least
in those cases where they want efficient and implicit sharing of domain-neutral
assemblies to happen.


size=2> 


style="mso-bidi-font-weight: normal"> face=Tahoma>Unloading


A host or
other sufficiently privileged code can explicitly unload any AppDomain it has a
reference to, except for the default AppDomain which is not unloadable. style="mso-spacerun: yes">  The default AppDomain is the one that is
created on your behalf when the process starts. style="mso-spacerun: yes">  This is the AppDomain a host typically
chooses for its own execution.


size=2> 


The
steps involved in an unload operation are generally as follows. style="mso-spacerun: yes">  As in many of these blogs, I’m
describing implementation details and I’m doing so without reading any source
code.  Hopefully the reader can
distinguish the model from the implementation details to understand which parts
of the description can change arbitrarily over time.


size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>Since the thread that calls AppDomain.Unload may itself
    have stack in the doomed AppDomain, a special helper thread is created to
    perform the unload attempt.  This
    thread is cached, so every Unload doesn’t imply creation of a new thread. style="mso-spacerun: yes">  If we had a notion of task priorities
    in our ThreadPool, we would be using a ThreadPool thread here.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>The unload thread sends a DomainUnload event to any
    interested listeners.  Nothing bad
    has happened yet, when you receive this event.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>The unload thread freezes the runtime. style="mso-spacerun: yes">  This is similar to the freeze that
    happens during (portions of) a garbage collection. style="mso-spacerun: yes">  It results in a barrier that prevents
    all managed execution.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>While the barrier is in place for all managed execution,
    the unload thread erects a finer-grained barrier which prevents entry into the
    doomed AppDomain.  Any attempt to
    call in will be rejected with a DomainUnloaded exception. style="mso-spacerun: yes">  The unload thread also examines the
    stacks of all managed threads to decide which ones must be unwound. style="mso-spacerun: yes">  Any thread with stack in the doomed
    AppDomain – even if it is currently executing in a different AppDomain – must
    be unwound.  Some threads might
    have multiple disjoint regions of stack in the doomed AppDomain. style="mso-spacerun: yes">  When this is the case, we determine
    the base-most frame that must be unwound before this thread is no longer
    implicated in the doomed AppDomain.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>The unload thread unfreezes the runtime. style="mso-spacerun: yes">  Of course, the finer-grained barrier
    remains in place to prevent any new threads from entering the doomed
    AppDomain.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>The unload thread goes to work on unwinding the threads
    that it has identified.  This is
    done by injecting ThreadAbortExceptions into those threads. style="mso-spacerun: yes">  Today we do this in a more
    heavy-weight but more scalable fashion than by calling Thread.Abort() on each
    thread, but the effect is largely the same. style="mso-spacerun: yes">  As with Thread.Abort, we are unable to
    take control of threads that are in unmanaged code. style="mso-spacerun: yes">  If such threads are stubborn and never
    return to the CLR, we have no choice but to timeout the Unload attempt, undo
    our partial work, and return failure to the calling thread. style="mso-spacerun: yes">  Therefore, we are careful to unwind
    the thread that called Unload only after all the others have unwound. style="mso-spacerun: yes">  We want to be sure we have a thread to
    return our failure to, if a timeout occurs!

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>When threads unwind with a ThreadAbortException, the Abort
    is propagated in the normal undeniable fashion. style="mso-spacerun: yes">  If a thread attempts to catch such an
    exception, we automatically re-raise the exception at the end of the catch
    clause.  However, when the
    exception reaches that base-most frame we identified above, we convert the
    undeniable ThreadAbortException to a normal DomainUnloaded
    exception.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>No threads can execute in the doomed AppDomain – except for
    a Finalizer thread which is now given a special privilege. style="mso-spacerun: yes">  We tell the Finalizer thread to scan
    its queue of ready-to-run finalizable objects and finalize all the ones in
    this AppDomain.  We also tell it
    to scan its queue of finalizable but still reachable objects (not ready to
    run, under normal circumstances) and execute them, too. style="mso-spacerun: yes">  In other words, we are finalizing
    reachable / rooted objects if they are inside the doomed AppDomain. style="mso-spacerun: yes">  This is similar to what we do during a
    normal process shutdown. 
    Obviously the act of finalization can create more finalizable
    objects.  We keep going until they
    have all been eliminated.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>During finalization, we are careful to skip over any agile
    reachable instances like Thread instances that were created in this
    AppDomain.  They effectively
    escape from this AppDomain in a lazy fashion at this time. style="mso-spacerun: yes">  When these instances are eventually
    collected, they will be finalized in the default AppDomain, which is as good
    as anywhere else.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>If we have any managed objects that were exposed to COM via
    CCWs, their lifetimes are partially controlled via COM reference counting
    rules.  If the managed objects are
    to agile instances, we remove them from their AppDomain’s wrapper cache and
    install them in the default AppDomain’s wrapper cache. style="mso-spacerun: yes">  Like other agile objects, they have
    lazily survived the death of the AppDomain they were created
    in.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>For all the non-agile CCWs (the vast majority), the managed
    objects are about to disappear. 
    So we bash all the wrappers so that they continue to support AddRef and
    Release properly.  All other calls
    return the appropriate HRESULT for DomainUnloadedException. style="mso-spacerun: yes">  The trick here, of course, is to
    retain enough metadata to balance the caller’s stack properly. style="mso-spacerun: yes">  When the caller drives the refcount to
    0 on each wrapper, it will be cleaned up.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>Now we stop reporting all the handles, if they refer to the
    doomed AppDomain, and we trigger a full GC. style="mso-spacerun: yes">  This should collect all the objects
    that live in this AppDomain.  If
    it fails to do so, we have a corrupted GC heap and the process will soon die a
    terrible death.

size=2> 



  • style="MARGIN: 0in 0in 0pt; mso-list: l2 level1 lfo5; tab-stops: list .5in"
    > face=Tahoma size=2>Once this full GC has finished, we are free to unmap all
    the memory containing JITted code, MethodTables, MethodDescs, and all the
    other constructs.  We also unload
    all the DLLs that we loaded specifically for this AppDomain.

size=2> 


In a
perfect world, that last step returns all the memory associated with the
AppDomain.  During V1, we had a leak
detection test that tried to verify this. 
Once we reached a steady-state in the test cycle, after unloading the
first few AppDomains, we got pretty close to our ideal. style="mso-spacerun: yes">  It’s harder to measure than you might
imagine, due to things like delayed coalescing of OS heap structures. style="mso-spacerun: yes">  According to our measurements, we were
leaking 12 bytes per unloaded AppDomain – of which 4 bytes was almost by
design.  (It was the ID of the
unloaded AppDomain).  I have no idea
how well we are doing these days.


size=2> 


In a
scenario where lots of unloads are happening, it’s unfortunate that we do a full
GC for each one.  For those cases,
we would like to defer the full GC and the reclamation of resources until the
next time that the GC is actually scheduled. style="mso-spacerun: yes">  …One day.


size=2> 


There’s
so much more I had intended to write about. style="mso-spacerun: yes">  For example, some ambiguities exist when
unmanaged (process-wide) code calls into Managed C++ and has to select a target
AppDomain.  This can be controlled
by flags in the VTFixup entries that are used by the IJW thunks. style="mso-spacerun: yes">  And customers often ask us for
alternatives to AppDomain unloading, like unloading individual methods,
unloading individual assemblies, or unloading unreferenced domain-neutral
assemblies.  There are many
interesting programming model issues, like the reason why we have a
CreateInstanceAndUnwrap method on
AppDomain.


size=2> 


But even
I think this blog is getting way too long.

Comments (50)

  1. Anonymous says:

    Asynchronous I/O

    Can you please talk about how .NET framework takes advantage of I/O completion port [or not ] mechanism on
    Windows ?

    I am specially interested in high capacity server implementations. In particular, I want to know about equivalent
    Win32 mechanisms for AcceptEx, ConnectEx and TransmitPackets (with TF_REUSE flags).

    Also, How come DACLs and security tokens are not modelled in the .NET framework ?

    Thanks
    Ricky

  2. Anonymous says:

    Could you maybe clarify what you mean by Marshal-By-Bleed? I suspect the meaning is supposed to be self-evident by the use of the word bleed, but there was a conspicuous lack of cartoon light bulbs above my head when I read that paragraph…

    By the way, its not possible for the blog to be too long when then posts have such fabulous technical content and clarity…

  3. Anonymous says:

    The use of the word "bleed" is supposed to imply that the objects bleed across the AppDomain boundary, rather than being properly contained inside the AppDomain they were created in. This is similar to the way colors might bleed from one garment to another in the laundry, though I don’t have much direct experience with washing clothes.

    The effect is that you can have direct references to the same object instance in many AppDomains simultaneously.

  4. Anonymous says:

    Ricky,

    The DACLs and security tokens are not yet modeled in the .NET framework, only because we didn’t get around to it yet. I’m sure you will see support for them at some point.

    As for IOCompletionPort support, have a look at ThreadPool.BindHandle which allows you to associate a handle (like a file handle) with the IOCompletion port that the ThreadPool controls. Also have a look at the System.Threading.Overlapped class, which allows you to receive callbacks via that IOCompletionPort, for any overlapped operations against that handle.

    I think you are asking whether the FX classes for e.g. Sockets are using this mechanism. That’s a better question for the authors of the appropriate FX classes. (I suspect they are using this mechanism).

  5. Anonymous says:

    Having a kind of in-memory created (dynamic) assemblies that are implicitly "unloaded" when they and their types are no longer reacheable would be a nice feature. Many dynamic languages impl. over the JVM depends on class unloading through GC (there is based on classloaders/their classes unreachability). I think , because of type identity rules and only-once guarantees for static initializers, this can be done only for some kind of only in-memory created assemblies.
    At the moment in-memory created assemblies are not GCed indipendently of the AppDomain.

    right? e.g.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondetailsofregularexpressionbehavior.asp

    regards.

  6. Anonymous says:

    These weblogs are among the very few that I’ve found that are actually worth reading. Thanks, Chris. Keep up the great work (and plug those Framework holes!)

    Cosmo

  7. Anonymous says:

    Pedronis,

    Everyone asks us for automatic implicit unloading of assemblies based on reachability. It’s a feature that is extremely popular, but very expensive for us to build. The decision of when/if to deliver this feature is more of a scheduling exercise. Other features keep getting in the way.

  8. Anonymous says:

    Chris,

    I’ve been plagued with an "Attempted to access an unloaded AppDomain" for several months now. We have a .NET Email Component such that for some ASP.NET customers that user our DLL will get this error occasionally. Our DLL is strongly named, so I am confused as to how this could be happening. It is a mixed-mode DLL implemented in managed C++ such that a thin managed C++ wrapper surrounds the unmanaged C++ implementation. I’ve gone over every piece of documentation and information on the Net that I can find about creating managed wrappers around unmanaged types, and as far as I can see I am doing everything correctly. To give you a flavor of what happens, here is an example snippet of a customer’s code:

               Chilkat.Email email = new Chilkat.Email();
               email.From = FromAddress;
               email.AddTo("", Address);
               email.Subject = Subject;
               email.Body = Body;
    

    The exception occurs when I set a value to the Body. The other statements execute without issue. Also, when I dump "email" in the Command Window, I get this:

    {Chilkat.Email}
    System.Object: {Chilkat.Email}
    Body: <error: an exception of type: {System.AppDomainUnloadedException} occurred>
    Decrypted: false
    EmailDate: {7/9/2003}
    EncryptedBy: ""
    FileDistList: ""
    From: ""
    FromAddress: ""
    FromName: ""
    HtmlCharset: "us-ascii"
    LastError: ""
    LocalDate: {7/9/2003}
    m_email: 84812680
    Mailer: "Chilkat Software Inc (http://www.chilkatsoft.com)"
    NumAlternatives: 0
    NumAttachedMessages: 0
    NumAttachments: 0
    NumBcc: 0
    NumCC: 0
    NumRelatedItems: 0
    NumReplacePatterns: 0
    NumTo: 0
    OutlookDistList: ""
    PlainTextCharset: "us-ascii"
    RawHeader: "MIME-Version: 1.0rnDate: Wed, 09 Jul 2003 10:55:07 -0400rnMessage-ID: <CHILKAT-MID-b18bc301-2589-4599-be87-d83d647ca053@blah>rnContent-Type: text/plain; charset="us-ascii"rnContent-Transfer-Encoding: 7bitrnX-Mailer: Chilkat Software Inc (http://www.chilkatsoft.com)rnX-Priority: 3 (Normal)"
    ReceivedEncrypted: false
    ReceivedSigned: false
    ReplyTo: ""
    ReturnReceipt: false
    SendEncrypted: false
    SendSigned: false
    SignaturesValid: false
    SignedBy: ""
    Size: <error: an exception of type: {System.AppDomainUnloadedException} occurred>
    Subject: ""
    Uidl: ""

    Note that Size and Body both return exceptions. Can you think of anything that could possibly cause this? Any help would be much appreciated.

    Best Regards,
    Matt Fausey
    Chilkat Software, Inc.

  9. Anonymous says:

    Matt,

    Feel free to contact me directly on this matter (if you can decode the mangling of my Microsoft email alias!). My first question is whether you built this for V1 or V1.1 of the CLR. If you built it for V1 and you are using MC++ with ASP.NET, then there’s a high likelihood that I know the problem. If you built it with V1.1, it depends what compiler switches you used. Let’s resolve the issue off-line from this blog, and then I can post the resolution back here — for anyone else with this problem — if it’s what I suspect.

    The problem seems to be mentioned at http://support.microsoft.com/default.aspx?scid=kb;en-us;309694. However, I don’t see an updated article with the 1.1 solution which is more palatable than the one described in the 309694 knowledge base article. Starting with 1.1, there’s a Managed C++ compiler switch that allows you to control this situation.

  10. Anonymous says:

    Chris,

    What is the Managed C++ compiler switch?

    Rocco

  11. Anonymous says:

    I am having trouble Unloading an AppDomain that has been set up to host remoting. It give the thread cannot be unwound exception. WHat can I do to make sure it unloads?

  12. Anonymous says:

    Rocco, the managed C++ compiler switch is /clr:initialAppDomain.

  13. Anonymous says:

    Kent, the first thing you should do is get a stack trace of the "reluctant" thread. Most likely it is performing unmanaged blocking rather than managed blocking. Less likely, it is executing CPU-intensive unmanaged code. In either case, the CLR cannot safely take control of the thread. If it is performing unmanaged blocking, consider switching it over to managed blocking instead. (See my post on Managed Blocking, or the most recent post on Apartments and Pumping for additional detail).

  14. Anonymous says:

    I have a question which may betray a fundamental misconception about what AppDomains are supposed to do, or may have a trivially simple answer.

    When you talk about domain-neutral assemblies above, you then state that even domain-neutral assemblies need per-AppDomain state for their static variables. You state this as a foregone conclusion.

    However, the problem domain that I’m trying to tackle (for which I hope that AppDomains may be the answer) specifically needs all state (including static variables if there are any) in one assembly to be shared across all the AppDomains that it loads.

    My scenario is sort of like a plugin system: Host application loads several assemblies that it wants to isolate from itself and each other and set limited permissions on, and then runs code inside those assemblies (which implement a predefined interface eg IPlugin that’s defined in the host application), passing them objects through which they can manipulate the host environment in certain well-defined ways.

    So the host application state is truly global, and when one of the plugins invokes a method which accesses static variables in the host app, it really should be accessing a single copy of that static variable, not a separate one per plugin.

    Am I trying to abuse AppDomains by wanting to do this, or is it a valid use of them, and if so, what would I need to do in the host application to get the semantics I want?

    (Currently, my code ignores the isolation and security issues and just does the following (apologies for syntax problems, I’m typing this from memory), which are the semantics I’m trying to preserve:

    Assembly asm = Assembly.LoadFrom(path);

    // get some custom attributes on the assembly to find the name of a type that implements IPlugin

    Type type = asm.GetType(typeName);

    ConstructorInfo cons = type.GetConstructor(new Type[] {})

    IPlugin plugin = (IPlugin) cons.Invoke(new object[] {});

    plugin.PluginMethod(pluginAPIObject);

    )

  15. Anonymous says:

    Hi guys. Since you seem to know so much about appdomains, maybe you could find the time to help me out here. See, I have a windows service that keeps calling a web service. Problem is, after a while (12-13 hours) I get this error "AppdomainUnloadedException" apparently in the middle of the processing of the data by the web service. Until that happens the calls to the web service have executed in the same iis session.

    Is there some web.config or iis setting that eludes me or can I stop this by catching this exception in global.asax? The application in the iis does not exit.

    I add the call stack for whatever good it will do:

    [AppDomainUnloadedException]: Attempted to access an unloaded AppDomain.

    at System.Threading.Thread.SetCompressedStackInternal(IntPtr unmanagedCompressedStack)

    at System.Threading.Thread.SetCompressedStack(CompressedStack stack)

    at System.Xml.XmlTextReader.CreateScanner()

    at System.Xml.XmlTextReader.Init()

    at System.Xml.XmlTextReader.Read()

    at System.Xml.XmlReader.MoveToContent()

    at System.Web.Configuration.XmlUtil.OpenXmlTextReader()

    at System.Web.Configuration.HttpConfigurationRecord..ctor(String filename, HttpConfigurationRecord parent, Boolean inheritable, String path)

    [ConfigurationException]: The XML file c:winntmicrosoft.netframeworkv1.1.4322Configmachine.config could not be loaded. Attempted to access an unloaded AppDomain. (c:winntmicrosoft.netframeworkv1.1.4322Configmachine.config)

    at System.Web.Configuration.HttpConfigurationSystem.CacheLookup(String vpath)

    at System.Web.Configuration.HttpConfigurationSystem.ComposeConfig(String reqPath, IHttpMapPath configmap)

    at System.Web.HttpContext.GetCompleteConfigRecord(String reqpath, IHttpMapPath configmap)

    at System.Web.HttpContext.GetCompleteConfig(String path)

    at System.Web.HttpContext.GetAppLKGConfig(String name)

    –>

    –.

    at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream)

    at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)

    at mywindowsservice.myWebService.datapush.readXmlBytes(Byte[] data, String parameter)

    Any suggestions will be appreciated.

    /Martin

  16. Anonymous says:

    Stuart,

    The vast majority of static fields are cloned for each AppDomain. A few others are cloned for each thread for each AppDomain or for each Context for each AppDomain. In other words, they are scoped at an even finer granularity than per AppDomain. We also support RVA-based static fields, which are process global. However, such statics can only contain primitive values. This is to avoid the potential of bleeding instances across an AppDomain boundary, but they may be sufficient for your needs. (All of the above is in my very first blog, entitled "Static Fields").

    Any objects must be marshaled somehow across the AppDomain boundary. This is intentional. Think of this like the address space separation of unmanaged processes. Our equivalent of memory mapping could be considered either agile objects or RVA-based statics.

  17. Anonymous says:

    Martin,

    It sounds like you might be running in ASP.NET. If this is the case, then ASP.NET will unload AppDomains for a variety of reasons. This includes changes to the application running in the AppDomain (they use file change notifications to monitor your aspx files, config files, etc.), and it includes high resource usage. I don’t know whether there are other circumstances that cause this — like perhaps they age out old applications after a long period of inactivity.

    If you are not running in ASP.NET and you don’t believe AppDomains should be unloaded, I would recommend setting a breakpoint on AppDomain.Unload and see who the caller is.

  18. Anonymous says:

    Chris:

    Yes, I’m running this in asp.net. I think I’ll take your advice and check out the unload method to try to find out if I can stop it from unloading. A posting on another site suggested I look at the asp(x) script timeout, maybe I hadn’t set it high enough after all. Thanks for your help.

    /Martin

  19. Anonymous says:

    In reference to:

    There’s so much more I had intended to write about. For example, some ambiguities exist when unmanaged (process-wide) code calls into Managed C++ and has to select a target AppDomain. This can be controlled by flags in the VTFixup entries that are used by the IJW thunks.

    This is a topic I am trying to get a better understanding of. We are having problems with a Managed C++ DLL that has a non-default appdomain and does LOTS of unmanaged calls (both P/Invoke and COM Interop). Also, the managed code is frequently called back by the unmanaged code.

    The code is compiled using VS .NET 2003 and we are running on 1.1 of the CLR. Interestingly, using the "initialAppDomain" flag SEEMS to make the problem go away, but we do not want to use this flag since we support unloading our app domain.

    On http://www.msdnaa.net, they say that:

    With version 1.1 of the .NET Framework, the vtable includes a flag that indicates to the runtime that if the call comes into the assembly from native code, the application domain that’s used will be that of the last application domain used to call into native code.

    But won’t this cause problems if you have managed/unmanaged transitions from multiple domains? What if I just called into unmanaged code from Domain A, and then receive a callback that really should be directed to Domain B? How can you ever reliably receive callbacks if you call unmanaged code from multiple domains?

  20. Anonymous says:

    Well, through lots of experimentation we were able to fully understand the behavior with and without the "/clr:initialAppDomain" flag.

    This is a topic that needs more detailed documentation from Microsoft, so that others don’t go through what we went through.

    For example, the docs seem to imply that the only reason you would use this flag is if you want to run on CLR 1.0. However, there are some cases where the 1.0 behavior makes more sense.

    Also, on MSDNAA they describe the 1.0 behavior as:

    "In the first version of the Framework, the runtime was consistent and ensured that the callback into managed code would go to the first application domain."

    I originally took "first application domain" to mean the first app domain loaded into the runtime. However, I think they really mean "the first application domain this assembly is loaded into". Is that correct?

  21. Anonymous says:

    Chris:

    I am new to AppDomains and do not quite understand "a given thread is not confined to a single application domain", also "multiple disjoint regions of stack" and how threads "stack in".

    Could you please further explain or advise where I can get more detail.

    thank you

  22. Anonymous says:

    Chris:

    I am new to AppDomains and do not quite understand "a given thread is not confined to a single application domain", also "multiple disjoint regions of stack" and how threads "stack in".

    Could you please further explain or advise where I can get more detail.

    thank you

  23. Anonymous says:

    Mark,

    You are correct that ‘initialAppDomain’ means the first AppDomain that the assembly is loaded into, rather than the first AppDomain of the process. And I agree with you that there are scenarios where initialAppDomain is preferred — though this position has been disputed by some of the folks over here.

    I think you will probably see some improvements in this area in Whidbey (hopefully with some documentation improvements, too). However, I don’t think those improvements made the first Whidbey Beta.

  24. Anonymous says:

    Lyon,

    AppDomains are boundaries around groups of objects within a process. Threads live within a process, rather than within any one of these AppDomain boundaries. When a thread calls from one AppDomain to another, it passes through the boundary and leaves a special marker frame on the stack. When the thread returns from the second AppDomain, the marker frame is popped off the stack during the reverse transition.

    If you look at the stack of a thread, you can imagine segments of stack separated by the marker frames. These segments between the marker frames are what I meant by "multiple disjoint regions of stack."

  25. Anonymous says:

    Hi,

    I’m newbie in this tasks related to application domains and I would like a rapid and a simple advice for using them correctly:

    1. Should I manage myself application domains, or should I let the CLR manage them? In what cases does this personal management deserve? Does alter performance of the application?

    2. Does CLR perform a good management of applicacion domains by default, without managing nothing myself?

    3. By default, a .NET application (Winforms, ASP.NET, etc.) maps to one (and only one) application domain? 1 process –> 1 application domain –> a .NET application?.

  26. Anonymous says:

    The CLR (meaning the runtime engine) doesn’t manage application domains itself. Instead, the application or some application framework is responsible for creating them, executing code in them, and destroying them.

    In the case of ASP.NET, this is handled by the application framework. It does a great job and there is no reason for you to interfere.

    In the case of WinForms, a WinForms app typically has a single application domain. For most GUI applications, this is appropriate. If you have an add-in model based on WinForms where it makes sense to use different AppDomains for each add-in (for all the usual reasons like isolation of binding, ability to unload, ability to have a distinct security policy, etc.) then your add-in model should create and manage the appdomains.

  27. Anonymous says:

    [href=//www.dmoz.net.cn/ title="wangzhidaquang"]

    [href=//www.86dmoz.com/ title="jingpingwangzhi"]

    [href=//www.kamun.com/ title="mianfeidianying"]

    [href=//www.kamun.com/ title="dianyingxiazai"]

    [href=//www.kamun.com/ title="MP3 free download"]

    [href=//www.pc530.net/ title="diannaoaihaozhe"]

    [href=//www.5icc.com/ title="duangxingcaixingxiazha"]

    [href=//www.dianyingxiazai.com/ title="dianyingxiazai"]

    [href=//www.yinyuexiazai.com/ title="yinyuexiazai"]

  28. Anonymous says:

    Question: How many threads does a typical managed process have when it just starts to run?&amp;nbsp;&amp;nbsp;…

  29. Anonymous says:

    Both Word and Excel OMs have APIs that allow closing documents programmatically. I suppose there are…

  30. Anonymous says:

    Question: How many threads does a typical managed process have when it just starts to run?&amp;nbsp;&amp;nbsp;…

  31. Anonymous says:

    Both Word and Excel OMs have APIs that allow closing documents programmatically. I suppose there are…

  32. Anonymous says:

    How to create and work with applications in AppDomains

  33. Anonymous says:

    I stumbled across an interesting issue today that I thought might be worth sharing.&#160; The design

  34. Anonymous says:

    Things have gone a bit crazy lately, we&#8217;ve been under a huge workload and the time left for blogging

  35. Anonymous says:

    Using Overlapped I/O from Managed Code

  36. Anonymous says:

    Multithreading adds the overhead of making sure data is accessed in a thread safe way. Win32 API uses