Welcome to MSDN Blogs Sign in | Join | Help

Issues caused by frequent application restarts are pretty common. If you are interested in what causes application restarts in general, and how you can monitor application restarts and what causes them you should read this post:

ASP.NET Case Study: Lost session variables and appdomain recycles

The most recent one was one where the ASP.NET site would respond slowly at regular intervals (read: it grinded to a halt every few hours). 

First shot at gathering data:

We started off by getting a memory dump with debug diag when the process was responding slowly.  Unfortunately at the time that the dump was taken no requests were executing (i.e. ~* e !clrstack showed no managed stacks and ~* kb showed all the threads in their idle states), so we didn't get much of a clue as to why the process was responding so slowly. 

If you are debugging a performance issue it is key to try to get dumps while there are requests executing since memory dumps are just snapshots of the process so other than what is on the heap you really don't get any history in a memory dump.

Luckily one of my colleagues (who was looking at the issue before me) had noticed that there were an extreme number of exceptions in the dump.  Perhaps I shouldn't call it luck really...  if we get memory dumps that at first sight seem to not be all that useful we will still try to poke around in them to see if we can gather any clues at all as to what is going on, in order to make a more targeted approach in the next round of collecting data.

Either case, running !dumpheap -type *Exception revealed that there had recently been a lot (~1000) of TypeLoadExceptions

Exception MethodTable: 791241c0
Exception object: 02d6c4bc
Exception type: System.TypeLoadException
Message: Could not load type 'System.Web.UI.WebControls.WebParts.Content' from assembly 'System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
InnerException: 
StackTrace (generated):
    SP       IP       Function
    00000000 00000001 System.Reflection.Assembly._GetType(System.String, Boolean, Boolean)
    1D0AE8D8 661680FB System.Web.UI.NamespaceTagNameToTypeMapper.System.Web.UI.ITagNameToTypeMapper.GetControlType(System.String, System.Collections.IDictionary)
	 

So the first working theory was that the exceptions themselves caused the poor performance, or at least had something to do with the poor performance.

Figuring out the cause of the TypeLoadExceptions

This exception tells us that someone is trying to create a System.Web.UI.WebControls.WebParts.Content object, but it can't because that type does not exist in System.Web.dll (the assembly that implements the System.Web.UI.WebControls.WebParts namespace). 

I looked up the System.Web.UI.WebControls.WebParts namespace on msdn and sure enough it didn't contain any Content class.  The only content class I could think of that was even remotely related was the one used for <asp:content> </asp:content> that you use with master pages.

I started playing around a bit with a simple application with master pages and found that if I browse to my application, and then add a new page with a master page and asp:content tags I will get this exception when this page compiles.

I know this is a bit of a mental leap, but I figured it probably had something to do with this tag and its namespace mappings so I ran !dumpheap -stat I found a type called System.Web.UI.TagNamespaceRegisterEntry.  I had 3 of them on the heap for my simple sample each with a tag prefix, namespace and assembly name

0:005> !dumpheap -type System.Web.UI.TagNamespaceRegisterEntry
Loading the heap objects into our cache.
------------------------------
Heap 0
   Address         MT     Size
02f04df8 663b6148       28    1 System.Web.UI.TagNamespaceRegisterEntry 
02f04e14 663b6148       28    1 System.Web.UI.TagNamespaceRegisterEntry 
02f04f3c 663b6148       28    1 System.Web.UI.TagNamespaceRegisterEntry 
total 3 objects
------------------------------
Heap 1
   Address         MT     Size
total 0 objects
------------------------------
total 3 objects
Statistics:
      MT    Count    TotalSize Class Name
663b6148        3           84 System.Web.UI.TagNamespaceRegisterEntry
Total 3 objects, Total size: 84
0:005> !do 02f04df8 Name: System.Web.UI.TagNamespaceRegisterEntry MethodTable: 663b6148 EEClass: 663b60c8 Size: 28(0x1c) bytes GC Generation: 1 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll) Fields: MT Field Offset Type VT Attr Value Name 790fd8c4 4002029 4 System.String 0 instance 00000000 _virtualPath 79102290 400202a 8 System.Int32 1 instance 0 _line 790fd8c4 400202b c System.String 0 instance 02f04bc8 _tagPrefix 790fd8c4 400202c 10 System.String 0 instance 02f04be0 _ns 790fd8c4 400202d 14 System.String 0 instance 02f04c24 _assemblyName

I dumped out all 3 of them and found the following mappings

asp -> System.Web.UI.WebControls.WebParts
asp -> System.Web.UI.WebControls
mobile -> System.Web.UI.MobileControls

So, the tagprefix asp: maps to both WebControls.WebParts which explains why it tries to look in the WebParts namespace for Content.  Since the process doesn't fail because of this, but rather just continues happily delivering the right Content object it also means that this exception is benign, i.e. it'll just try this first, if it fails it continues looking in the WebControls namespace.

However... we still haven't answered the most important questions

1. Why are there so many of them?  In my little sample I only get a new one if i modify a page so that it has to be recompiled.  If I compile normally I will only get one independently of how many pages I have.

2. Why would it be compiling these pages at regular intervals, especially a few hours into the life of the process, all pages should be compiled by then

The only thing that seemed to fit was if perhaps the appdomain was recycling as this would answer both these questions...     

Figuring out if the appdomain is recycling

I dumped out the System.Web.HttpRuntimes (!dumpheap -type System.Web.HttpRuntime).  There is one per application loaded in the process.

The HttpRuntime object has a member variable called _firstRequestStartTime and the expected start time should be some time around the start of the process.  If the _firstRequestStartTime is within the last few minutes, that means that the application has restarted very recently.

0:005> !do 0x6eb2d14
Name: System.Web.HttpRuntime
MethodTable: 6639a67c
EEClass: 6639a60c
Size: 152(0x98) bytes
GC Generation: 2
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
791132b4  40010f8        4 ...amedPermissionSet  0 instance 00000000 _namedPermissionSet
6639b12c  40010f9        8 ...ileChangesMonitor  0 instance 06eb3218 _fcm
6639e2c8  40010fa        c ...ing.CacheInternal  0 instance 06eb4d74 _cacheInternal
6639d878  40010fb       10 ...Web.Caching.Cache  0 instance 06eb4c8c _cachePublic
7910be50  40010fc       74       System.Boolean  1 instance        0 _isOnUNCShare
6639ad78  40010fd       14 ...Web.Util.Profiler  0 instance 06eb2dac _profiler
6639ae78  40010fe       18 ...estTimeoutManager  0 instance 06eb2dc8 _timeoutManager
663aebe4  40010ff       1c ....Web.RequestQueue  0 instance 02ed438c _requestQueue
7910be50  4001100       75       System.Boolean  1 instance        0 _apartmentThreading
7910be50  4001101       76       System.Boolean  1 instance        1 _processRequestInApplicationTrust
7910be50  4001102       77       System.Boolean  1 instance        0 _beforeFirstRequest
7910c878  4001103       84      System.DateTime  1 instance 06eb2d98 _firstRequestStartTime
7910be50  4001104       78       System.Boolean  1 instance        1 _firstRequestCompleted
7910be50  4001105       79       System.Boolean  1 instance        0 _userForcedShutdown
...

It's a bit tricky to convert the time (a value type) to an actual time stamp, but it goes like this...

dq <address of firstrequeststarttime>  (dump the content of the start time as a quad word to get the number of ticks.)
.formats <quad word>&&0x3FFFFFFFFFFFFFFF  (run .formats on the number of ticks with the 2 highest bits filtered out)

0:005> dq 06eb2d98
06eb2d98  48ca8304`65439a9d 00000000`00000000
06eb2da8  6639ad78`00000000 0000000a`02ed4c5c
06eb2db8  01000000`00000000 00000000`00000000
06eb2dc8  06eb2dec`6639ae78 00000000`06eb2f54
06eb2dd8  00000000`00000005 00000000`08f0d180
06eb2de8  7912d8f8`00000000 6639af40`0000000d
06eb2df8  06eb2e44`06eb2e30 06eb2e6c`06eb2e58
06eb2e08  06eb2e94`06eb2e80 06eb2ebc`06eb2ea8

0:005> .formats 48ca8304`65439a9d&0x3FFFFFFFFFFFFFFF 
Evaluate expression:
  Hex:     08ca8304`65439a9d
  Decimal: 633462752501013149
  Octal:   0043124060214520715235
  Binary:  00001000 11001010 10000011 00000100 01100101 01000011 10011010 10011101
  Chars:   ....eC..
  Time:    Tue May 13 13:34:10.101 3608 (GMT+2)
  Float:   low 5.77321e+022 high 1.21882e-033
  Double:  2.56941e-266

The first request for this appdomain came in at 13:34:10, now we can compare this to the current time

0:005> .time
Debug session time: Tue May 13 13:35:47.210 2008 (GMT+2)
System Uptime: 33 days 11:04:15.423
Process Uptime: 0 days 5:42:08.328
  ...

And we find that the appdomain restarted less than a minute ago, so it is definitely a case of process restarts.

Why did the appdomain restart?

After adding logging per the "ASP.NET Case Study: Lost session variables and appdomain recycles" article the following showed up in the eventlog

_shutDownStack:    at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   at System.Environment.get_StackTrace()
   at System.Web.Hosting.HostingEnvironment.InitiateShutdownInternal()
   at System.Web.Hosting.HostingEnvironment.InitiateShutdown()
   at System.Web.HttpRuntime.ShutdownAppDomain(String stackTrace)
   at System.Web.HttpRuntime.OnCriticalDirectoryChange(Object sender, FileChangeEvent e)
   at System.Web.FileChangesMonitor.OnCriticaldirChange(Object sender, FileChangeEvent e)
   at System.Web.DirectoryMonitor.FireNotifications()
   at System.Web.Util.WorkItem.CallCallbackWithAssert(WorkItemCallback callback)
   at System.Web.Util.WorkItem.OnQueueUserWorkItemCompletion(Object state)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
_shutDownMessage: Change Notification for critical directories.
bin dir change or directory rename
HostingEnvironment initiated shutdown
HostingEnvironment caused shutdown
Change in C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\5fe0fce6\53087c60\hash\hash.web

The next step was to figure out who was touching the hash.web by monitoring the file with process monitor, and It turned out, it was antivirus scanning the file, which in turn triggered the filechangesmonitor to recycle the appdomain.

Solutions

Normally I would suggest looking into if it is possible to exclude the Temporary ASP.NET Files directory and subdirectories from automatic virus scanning as well as the web content directories. 

In 2.0 it is also possible to disable filechangesmonitoring through the HKLM\Software\Microsoft\ASP.NET\FCNMode key.  This KB article lists the different values that this can be set to,  a value of 1 will disable file changes monitoring.  The drawback of disabling this is that if you disable it, the application will not restart if you change web.config etc. so you need to carefully consider what you want to set this to.

I just came across this great post from Mike Volodarsky (a program manager in the IIS team) about breaking changes when you move 2.0 applications to IIS 7.

It is definitely worth a read before you make the switch so you can take appropriate actions to avoid running into problems.

Laters,

Tess

1 Comments
Filed under:

When you debug .net applications you will sometimes get error messages in windbg.  Here are a few of the ones I most commonly get questions around...

 

Failed to start stack walk

If you run the sos command !clrstack to display the .net stack on a thread, and this thread is a .net thread but it is not currently running any .net code, sos will spit out Failed to start stack walk: 80004005.  This does not mean that there is anything wrong with the process or with the debugger. It simply means that sos can't display the stack because there is none.

OS Thread Id: 0x1ec (12)
Failed to start stack walk: 80004005

 

Unable to walk the managed stack

If you run !clrstack on a native thread (i.e. a thread that has no corresponding System.Threading.Thread), sos will display the following message instead.

OS Thread Id: 0x554 (11)
Unable to walk the managed stack. The current thread is likely not a 
managed thread. You can run !threads to get a list of managed threads in
the process

Following frames may be wrong

If windbg can't resolve a symbol it will give an ERROR the first time it encounters this symbol telling you that the symbol file could not be found, and all subsequent times it encounters this symbols it will give you a WARNING, telling you that it can't unwind the stack properly.  When you see this message it means that anything you see below the WARNING note may be incorrect so you can't trust the stack from there on.  In this case for example we can see that the stack is waiting to enter a critical section, and that some method in DataLayer.dll is trying to enter a critical section, however because we don't have proper symbols for DataLayer.dll we can not say if it is the method DllUnregisterServer or not.  In fact most likely DllUnregisterServer is just the last exported symbol name, otherwise we would be at an offset of 0x43fb which means that this method would be very very long.

Not only do we not know if this is the right method name or not, but windbg may even loose stack frames when it doesn't have the proper symbols so you really shouldn't trust this stack at all...

For a discussion on symbols, how they work etc. see this post

0:018> kL
ChildEBP RetAddr  
020eea58 77f8f295 NTDLL!NtWaitForSingleObject+0xb
020eeacc 77f87f26 NTDLL!RtlpWaitForCriticalSection+0x9e
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for DataLayer.dll - 
020eead4 6010f8b2 NTDLL!RtlEnterCriticalSection+0x46
WARNING: Stack unwind information not available. Following frames may be wrong.
020eeae4 6012e72b DataLayer!DllUnregisterServer+0x43fb
...

Failed to load data access DLL, 0x80004005

This error means that a) you are loading the wrong version of sos.dll, i.e. in this case I loaded the 2.0 version in a 1.1 dump, or b) it can't find the proper version of mscordacwks.dll.  If it is option b, try turning on !sym noisy  and run .cordll -ve -u -l.  And of course, as the error message mentions,  make sure that you have a full dump rather than a mini dump.  Check out this post for some basics on memory dumps.

0:167> !clrstack
Doesn't work with 1.x
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
            2) the file mscordacwks.dll that matches your version of mscorwks.dll is 
                in the version directory
            3) or, if you are debugging a dump file, verify that the file 
                mscordacwks___.dll is on your symbol path.
            4) you are debugging on the same architecture as the dump file.
                For example, an IA64 dump file must be debugged on an IA64
                machine.

You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll.  .cordll -ve -u -l will do a verbose reload.
If that succeeds, the SOS command should work on retry.

If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.

 

Laters,

Tess

When you use authenticode signed assemblies in an application, the application needs to go out and check the certificate revocation lists (CRLs) to verify that the signature is still valid the first time it loads up the authenticode signed assembly. 

If the server, serving your asp.net application, doesn't have internet access or if the internet connection is slow this can lead to issues where you stall the process during startup or when the assembly in question first loads.

What you will typically see is a thread loading up an assembly with a callstack like this:

0:026> kL 200
ChildEBP RetAddr 
0e82c1b4 7c822124 ntdll!KiFastSystemCallRet
0e82c1b8 77e6bad8 ntdll!NtWaitForSingleObject+0xc
0e82c228 73ca64ec kernel32!WaitForSingleObjectEx+0xac
0e82c254 73ca6742 cryptnet!CryptRetrieveObjectByUrlWithTimeout+0x12f
0e82c280 73ca3253 cryptnet!CryptRetrieveObjectByUrlW+0x9b
0e82c2f8 73ca6b26 cryptnet!RetrieveObjectByUrlValidForSubject+0x5b
0e82c348 73ca3568 cryptnet!RetrieveTimeValidObjectByUrl+0xbc
0e82c3b0 73ca37d6 cryptnet!CTVOAgent::GetTimeValidObjectByUrl+0xc2
0e82c460 73ca3673 cryptnet!CTVOAgent::GetTimeValidObject+0x2f1
0e82c490 73ca3136 cryptnet!CrlFromCertGetTimeValidObject+0x2d
0e82c4d4 73ca4ab1 cryptnet!CryptGetTimeValidObject+0x58
0e82c530 73ca284b cryptnet!GetTimeValidCrl+0x1e0
0e82c574 73ca27a9 cryptnet!GetBaseCrl+0x34
0e82c614 761d2a7a cryptnet!MicrosoftCertDllVerifyRevocation+0x128
0e82c6a4 761d25e4 crypt32!VerifyDefaultRevocation+0x1d0
0e82c714 761d2ec0 crypt32!CertVerifyRevocation+0xb7
0e82c794 761d2769 crypt32!CChainPathObject::CalculateRevocationStatus+0x1f2
0e82c7dc 761cbac3 crypt32!CChainPathObject::CalculateAdditionalStatus+0x147
0e82c898 761ccfdd crypt32!CCertChainEngine::CreateChainContextFromPathGraph+0x227
0e82c8c8 761c235a crypt32!CCertChainEngine::GetChainContext+0x44
0e82c8f0 76bb80ff crypt32!CertGetCertificateChain+0x60
0e82c954 76bb66f3 wintrust!_WalkChain+0x1a8
0e82c990 76bb4de1 wintrust!WintrustCertificateTrust+0xb7
0e82ca84 76bb2f5e wintrust!_VerifyTrust+0x144
0e82caa8 64025c5c wintrust!WinVerifyTrust+0x4e
0e82cb4c 7a02a405 mscorsec!GetPublisher+0xe4
0e82cba4 79e9650a mscorwks!PEFile::CheckSecurity+0xaa
0e82cbd0 79e96453 mscorwks!PEAssembly::CheckSecurity+0x3a
0e82cbf8 79ea20f1 mscorwks!PEAssembly::PEAssembly+0x106
0e82ce94 79ea1fd7 mscorwks!PEAssembly::DoOpen+0x103
0e82cf28 79e9ff07 mscorwks!PEAssembly::Open+0x79
0e82d08c 79e93f1f mscorwks!AppDomain::BindAssemblySpec+0x247
0e82d124 79e93df8 mscorwks!PEFile::LoadAssembly+0x95
0e82d1c4 79f20d93 mscorwks!Module::LoadAssembly+0xee
0e82d200 79f1cdda mscorwks!Assembly::FindModuleByTypeRef+0x113
0e82d250 79e7b956 mscorwks!ClassLoader::LoadTypeDefOrRefThrowing+0xfc
0e82d354 79f13114 mscorwks!SigPointer::GetTypeHandleThrowing+0x297
0e82d3fc 79084899 mscorwks!CEEInfo::getFieldType+0x14a
0e82d9c8 790635c3 mscorjit!Compiler::impImportBlockCode+0x3303
0e82da4c 7906355b mscorjit!Compiler::impImportBlock+0x20c
0e82da64 79063494 mscorjit!Compiler::impImport+0xe5
0e82da70 79064e1c mscorjit!Compiler::fgImport+0x20
0e82da7c 790614e6 mscorjit!Compiler::compCompile+0xb
0e82dad4 79061236 mscorjit!Compiler::compCompile+0x2df
0e82db68 7906118c mscorjit!jitNativeCode+0xb8
0e82dba0 79f0f9cf mscorjit!CILJit::compileMethod+0x3d
0e82dc0c 79f0f945 mscorwks!invokeCompileMethodHelper+0x72
0e82dc50 79f0f8da mscorwks!invokeCompileMethod+0x31
0e82dca8 79f0ea33 mscorwks!CallCompileMethodWithSEHWrapper+0x84
0e82e060 79ecbc28 mscorwks!UnsafeJitFunction+0x230
0e82e0a0 79ecb252 mscorwks!MethodDesc::IsVerifiable+0x9f
0e82e0d4 79ecb216 mscorwks!MethodDesc::IsVerifiable+0x32
0e82e140 79061344 mscorwks!CEEInfo::isInstantiationOfVerifiedGeneric+0xf9
0e82e188 79061236 mscorjit!Compiler::compCompile+0xbd
0e82e21c 7906118c mscorjit!jitNativeCode+0xb8
0e82e254 79f0f9cf mscorjit!CILJit::compileMethod+0x3d
0e82e2c0 79f0f945 mscorwks!invokeCompileMethodHelper+0x72
0e82e304 79f0f8da mscorwks!invokeCompileMethod+0x31
0e82e35c 79f0ea33 mscorwks!CallCompileMethodWithSEHWrapper+0x84
0e82e714 79f0e795 mscorwks!UnsafeJitFunction+0x230
0e82e7b8 79e87f52 mscorwks!MethodDesc::MakeJitWorker+0x1c1
0e82e810 79e8809e mscorwks!MethodDesc::DoPrestub+0x486
0e82e860 01921f0e mscorwks!PreStubWorker+0xeb
...

And if you look at the .net stack with !clrstack you will see the authenticode signed assembly in question loading up, so you can identify which dll it was that required the sign verification.

This thread will call off to another thread that goes out to get the CRL:

0:026> kb 2000
ChildEBP RetAddr  Args to Child              
03c0e084 7c822124 71b23a09 000007c0 00000001 ntdll!KiFastSystemCallRet
03c0e088 71b23a09 000007c0 00000001 03c0e0b0 ntdll!NtWaitForSingleObject+0xc
03c0e0c4 71b23a52 000007c0 000007c8 00000000 mswsock!SockWaitForSingleObject+0x19d
03c0e1b4 71c0470c 00000001 00000000 03c0e22c mswsock!WSPSelect+0x380
03c0e204 4e7de746 00000001 00000000 03c0e22c ws2_32!select+0xb9
03c0ea44 4e7de8a5 000007c8 03c0ea68 4e7d976b winhttp!ICSocket::Connect_Start+0x3a8
03c0ea50 4e7d976b 03e42000 4e7c4b5c 00000000 winhttp!CFsm_SocketConnect::RunSM+0x42
03c0ea68 4e7d9805 03d61000 00000000 00000000 winhttp!CFsm::Run+0x20
03c0ea8c 4e7de99b 03e42000 03d80000 03c0eab0 winhttp!DoFsm+0x2a
03c0ea9c 4e7de9b8 0000ea60 00000005 00000020 winhttp!ICSocket::Connect+0x32
03c0eab0 4e7ebc6a 0000ea60 00000005 0000ea60 winhttp!ICSocket::Connect+0x13
03c0eaf8 4e7ebde7 03d67280 03c0eb1c 4e7d976b winhttp!HTTP_REQUEST_HANDLE_OBJECT::OpenConnection_Fsm+0x44a
03c0eb04 4e7d976b 03d67280 00000000 00000000 winhttp!CFsm_OpenConnection::RunSM+0x37
03c0eb1c 4e7d9805 03d61000 00000000 00000000 winhttp!CFsm::Run+0x20
03c0eb40 4e7ebe65 03d67280 03d80000 03c0eb78 winhttp!DoFsm+0x2a
03c0eb50 4e7edb71 00000000 00000000 03d61000 winhttp!HTTP_REQUEST_HANDLE_OBJECT::OpenConnection+0x2f
03c0eb78 4e7ede6d 03d671e0 03c0eb9c 4e7d976b winhttp!HTTP_REQUEST_HANDLE_OBJECT::MakeConnection_Fsm+0x9b
03c0eb84 4e7d976b 03d671e0 03d80000 00000000 winhttp!CFsm_MakeConnection::RunSM+0x37
03c0eb9c 4e7d9805 03d61000 00000000 00000000 winhttp!CFsm::Run+0x20
03c0ebc0 4e7ec68f 03d671e0 03d61000 03d67140 winhttp!DoFsm+0x2a
03c0ebf8 4e7ec8b9 03d67140 03c0ec1c 4e7d976b winhttp!HTTP_REQUEST_HANDLE_OBJECT::SendRequest_Fsm+0x8e
03c0ec04 4e7d976b 03d67140 03d80000 00000000 winhttp!CFsm_SendRequest::RunSM+0x37
03c0ec1c 4e7d9805 03d61000 00000000 00000000 winhttp!CFsm::Run+0x20
03c0ec40 4e7e779c 03d67140 03d61000 03e40000 winhttp!DoFsm+0x2a
03c0ec60 4e7e7d11 00000000 03c0ec84 4e7d976b winhttp!HTTP_REQUEST_HANDLE_OBJECT::HttpSendRequest_Start+0x2a4
03c0ec6c 4e7d976b 03e40000 00000001 00000000 winhttp!CFsm_HttpSendRequest::RunSM+0x4c
03c0ec84 4e7d9902 03d61000 00000000 00000000 winhttp!CFsm::Run+0x20
03c0eccc 4e7d0528 03e40000 03d80000 00000000 winhttp!StartFsmChain+0xd9
03c0ed10 4e7d087e 03d80000 00000000 00000000 winhttp!HttpWrapSendRequest+0x199
03c0ed94 73cac488 03d80000 00000000 00000000 winhttp!WinHttpSendRequest+0x1ee
03c0fe10 73cac916 03d64000 03d80000 0324bdc4 cryptnet!InetSendAuthenticatedRequestAndReceiveResponse+0x108
03c0feb4 73cacbb2 03d64000 0324bdc4 00002005 cryptnet!InetSendReceiveUrlRequest+0x1b0
03c0fee4 73ca3026 0324bdc4 00000002 00002005 cryptnet!CInetSynchronousRetriever::RetrieveObjectByUrl+0x59
03c0ff20 73ca2e08 0324bdc4 00000002 00002005 cryptnet!InetRetrieveEncodedObject+0x66
03c0ff74 73ca62a0 0324bdc4 00000002 00002005 cryptnet!CObjectRetrievalManager::RetrieveObjectByUrl+0xb1
03c0ffb8 77e66063 0324bd80 00000000 00000000 cryptnet!CryptRetrieveObjectByUrlWithTimeoutThreadProc+0x56
03c0ffec 00000000 73ca6207 0324bd80 00000000 kernel32!BaseThreadStart+0x34

The 2nd parameter to cryptnet!InetSendReceiveUrlRequest (0324bdc4) is the URL we are getting the CRL from in this case http://crl.microsoft.com/pki/crl/products/CodeSignPCA.crl

0:026> du 0324bdc4 
0324bdc4  "http://crl.microsoft.com/pki/crl"
0324be04  "/products/CodeSignPCA.crl"

It will attempt to make the webrequest for 60 seconds before it times out, so if there is something blocking this request the validation can be stalled for some time.

Another variation

Recently we had a case with this same issue, only it happened at shutdown so it caused the process not to shut down in a timely manner

Event Type:		Warning
Event Source:    	W3SVC
Event Category:		None
Event ID:		1013
Date:			13/05/2008
Time:                   10:02:49
User:			N/A
Computer:		MYMACHINE
Description:
A process serving application pool 'DefaultAppPool' exceeded time limits during shut down. The process id was '3368'. 

For more information, see Help and Support Center at .

What can you do about it?

Check if the IIS server has internet access and can browse to http://crl.microsoft.com/pki/crl/products/CodeSignPCA.crl, verify that the download is not blocked by a proxy or firewall.

If the IIS server doesn't have internet access, check if Authenticode signing is neccessary, or if strong naming would suffice for your security needs.

If authenticode signing is required you may consider catalog signing, where you sign a deployment package/cab file but the individual assemblies will be strong named.

.Net 2.0 Service Pack 1 contains a fix (KB 936707) that allows you disable the signature verification through the generatePublisherEvidence configuration element.

 

Additional information:

Similar issues can also occur in Exchange and SQL Server, have a look at these kbs for more details

KB915850 You may experience a 45-second delay when you run a full-text query in an instance of SQL Server 2005 that is running on a server without Internet access

KB944752 Exchange 2007 managed code services do not start after you install an update rollup for Exchange 2007

 

Laters,

Tess

Today I came across a really good article for bloggers, Scott Hanselmans post on "Blog Interesting - 32 Ways to Keep Your Blog from Sucking

(update: for some reason the link gives an xml error, should be http://www.hanselman.com/blog/BlogInteresting32WaysToKeepYourBlogFromSucking.aspx if the link above doesn't work)

I realize that most of you are not bloggers, and also that I am breaking rule #7 and #8 on the list here:) 

7. Don't post throwaways

  • I try to have a minimum length to a post. If you don't think about your blog post, likely no one else will either. If I want to save a link, rather than posting "I want to save this link, so I'm blogging it to remember" I use a service like http://del.icio.us/shanselman. Unless you're a link blogger, but then you'd batch them up.

8. Avoid "excessive quoting"

  • Some popular bloggers can get away with this, but I think that quotes make up more than 30% of a blog post (or, gasp, 70% or more) than you really have to ask yourself "am I providing value here?"

...but I really enjoyed his post, so I'm sharing it for you to enjoy as well...

And regarding #7, trying to have a minimum lenght to a post,  a lot of people would probably argue that instead of trying to have a minimum length, I should try to set a maximum length for my posts since they tend to get very wordy... especially the folks that manage the community server for blogs.msdn.com as i suspect that most my post make it on to the large object heap:)

Laters,

Tess

I very frequently get emails like the one I got this morning:

"Tess,

It sounds like the hotfix for kb946644 may resolve a problem we've been having for some time in .Net 2.0.
How, exactly, can we get this hotfix? Can you provide it?"

This particular hotfix was one i blogged about here, regarding a deadlock with the GC when using XmlSchemaSet.Add... but the answer will be the same for any hotfix.

Many new hotfixes in the Visual Studio or .net framework area are available for public download by the Visual Studio and .NET Framework Public Availability Program.

If the particular hotfix you are looking for (like this one) is not available there, you need to open a support case and the reason for this is that we want to keep track who gets certain hotfixes in case there is an issue that we need to inform the customers about.   If you open a support case to get a hotfix this case will of course be free of charge, however if the fix does not resolve the issue and further troubleshooting is neccessary you will be given the option to troubleshoot it further in a regular support case.

/Tess 

2 Comments
Filed under:

If I were to pick out ten keywords for my blog I would pick, in no particular order, ASP.net, windbg, sos, debugging, .net exceptions, memory leaks, performance issues, OutOfMemory exceptions, garbage collection, troubleshooting.  Those are the things that I think, but I might be wrong, that people who visit my blog are most interested in.

As with most other technical blogs (I think), about half of the traffic to my blog comes from people searching on various search engines, but what really surprised me was what the top search terms are that bring people here, since obviously my keywords are way off:) 

1. kifastsystemcallret
2. .net exception types
3. ntdll!kifastsystemcallret
4. 0x800703e9
5. 0xe0434f4d
6. Invalid viewstate
7. tess blog
8. tess fernandez
9. developerinstallation
10. unblock

The 0x800703e9 (exception code for StackOverflow) and 0xe0434f4d (exception code for .net exceptions) I can completely understand, just like invalid viewstate and ".net exception types" since I have written quite a bit about these subjects. 

Incidentally, if you got here through a search and you want to see my posts on the topic, check out the post index.

The search for Tess Fernandez is a bit funny, especially since my name is Ferrandez:)  so I'm not sure how this blog even comes up as the number 1 choice on live and google for that search term.

kifastsystemcallret

But... what surprised me the most was the overwhelming amount of searches for kifastsystemcallret and ntdll!kifastsystemcallret that brought people here.  I even had to stop and think for a moment to try to recall if I had ever written anything on the topic:)

kifastsystemcallret is a return function address for trap frames, similar to sharedUserData!SystemCallStub.  That sounds about as interesting as watching paint dry:)  but if you're interested in the details you can read more about it here.  What is interesting though is where you see it, and that is probably what gets people searching for it.

If you have a memory dump or break into a live process with windbg, these frames will be on top of the stack, for example in

  11  Id: fb8.268 Suspend: 0 Teb: 7ffaa000 Unfrozen
ChildEBP RetAddr  Args to Child              
0199fdf8 7c822124 77e6baa8 00000234 00000000 ntdll!KiFastSystemCallRet
0199fdfc 77e6baa8 00000234 00000000 0199fe40 ntdll!NtWaitForSingleObject+0xc
0199fe6c 77e6ba12 00000234 00009c40 00000000 kernel32!WaitForSingleObjectEx+0xac
0199fe80 791d401f 00000234 00009c40 00000000 kernel32!WaitForSingleObject+0x12
0199fea4 791fdacc 00000000 00000000 80a56bcc mscorsvr!ThreadpoolMgr::WorkerThreadStart+0x3a
0199ffb8 77e66063 000b6da8 00000000 00000000 mscorsvr!ThreadpoolMgr::intermediateThreadProc+0x44
0199ffec 00000000 791fda8b 000b6da8 00000000 kernel32!BaseThreadStart+0x34

So if you debug, this shows up a lot, and something that shows up a lot usually raises a red flag... but rest assured that this is one that you can safely ignore... what is interesting, if anything, is what is below the ntdll!KiFastSystemCallRet and SharedUserData!SystemCallStub.

If you want to know what else you can safely ignore, you might want to have a look at these two posts

Things to ignore when debugging an ASP.NET hang
Things to ignore when debugging an ASP.NET Hang - Update for .NET 2.0 

 

 

What I've learned from this is that I am clueless when it comes to search engines:)  but hopefully if you came here looking for answers on KiFastSystemCallRet you need to look no longer now:)

Laters,

Tess

I have written a few posts about stackoverflow exceptions, here, here, here and here.  The one I am going to talk about today is one of those unfortunate cases where you are trying to do the right thing and still shoot yourself in the foot.

Problem description:

Randomly when browsing the application we get the "Internet Explorer cannot display the webpage" page, and the following event is found in the system eventlog

Event Type:	Warning
Event Source:	W3SVC
Event Category:	None
Event ID:		1009
Date:		2008-05-06
Time:		10:04:26
User:		N/A
Computer:		MYMACHINE
Description:
A process serving application pool 'DefaultAppPool' terminated unexpectedly. The process id was '4436'. The process exit code was '0x800703e9'.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

0x800703e9 means "Recursion too deep; the stack overflowed" so we are dealing with a stackoverflow as the title of the post suggests, i.e. that we are exhausting the memory allocated for the stack which most often happens when you run into a situation where you have infinite recursion, i.e. methods calling themselves either directly or in a chain, and there is no stop condition.

Debugging the issue:

If you want more detail on how to debug these types of issues in general you might want to check out some of the posts I mentioned earlier.  To save some space I will go straight to the core and get a memory dump when the stackoverflow occurrs, using the config file unknown.cfg that you can find attached to this post which happens to be a hands-on lab on stackoverflow exceptions.

I gather a memory dump of the asp.net process (w3wp.exe), when the stackoverflow occurrs, with the following command:

adplus -pn w3wp.exe -c unknown.cfg

Then i open up the 1st_Chance_Unknown_Exception dump in windbg and load sos.dll using .loadby sos mscorwks. At this point, since the unknown exception was what triggered the dump, I will be situated on the thread that caused the unknown exception, i.e. the stackoverflow exception.

If we look at the stack it's tempting to assume that the culprit is the call to GetResourceFromDefault since this is at the top of the stack.  In reality this is just the drop of water that make the cup run over.  Our real issue is what made it so full to begin with.  It's the same thing when you debug   

0:024> !clrstack
OS Thread Id: 0x14ac (24)
ESP       EIP     
0febaca0 7d4e2366 [HelperMethodFrame_2OBJ: 0febaca0] System.Environment.GetResourceFromDefault(System.String)
0febad00 793f7575 System.NullReferenceException..ctor()
0febaf34 79e7c74b [GCFrame: 0febaf34] 
0febb050 79e7c74b [GCFrame: 0febb050] 
0febb0b4 79e7c74b [GCFrame: 0febb0b4] 
0febb6f4 0fe1076b ErrorPage.Page_Load(System.Object, System.EventArgs)
0febb728 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0febb738 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0febb748 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0febb758 6613cb50 System.Web.UI.Control.LoadRecursive()
0febb76c 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0febb968 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0febb9a0 6614d80f System.Web.UI.Page.ProcessRequest()
0febb9d8 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0febb9e0 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0febb9f4 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0febb9f8 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
0febbabc 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0febbb1c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0febbb30 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0febbb3c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0febc878 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0febc888 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0febc898 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0febc8a8 6613cb50 System.Web.UI.Control.LoadRecursive()
0febc8bc 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0febcab8 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0febcaf0 6614d80f System.Web.UI.Page.ProcessRequest()
0febcb28 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0febcb30 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0febcb44 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext)
0febcb48 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
0febcc0c 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean)
0febcc6c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean)
0febcc80 660082e5 System.Web.HttpServerUtility.Transfer(System.String)
0febcc8c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs)
0febd9c8 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
...<cut to save space/>
fecf25c 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean) 0fecf2bc 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean) 0fecf2d0 660082e5 System.Web.HttpServerUtility.Transfer(System.String) 0fecf2dc 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs) 0fed0018 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs) 0fed0028 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs) 0fed0038 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs) 0fed0048 6613cb50 System.Web.UI.Control.LoadRecursive() 0fed005c 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean) 0fed0258 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean) 0fed0290 6614d80f System.Web.UI.Page.ProcessRequest() 0fed02c8 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext) 0fed02d0 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext) 0fed02e4 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext) 0fed02e8 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String) 0fed03ac 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean) 0fed040c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean) 0fed0420 660082e5 System.Web.HttpServerUtility.Transfer(System.String) 0fed042c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs) 0fed1168 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs) 0fed1178 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs) 0fed1188 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs) 0fed1198 6613cb50 System.Web.UI.Control.LoadRecursive() 0fed11ac 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean) 0fed13a8 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean) 0fed13e0 6614d80f System.Web.UI.Page.ProcessRequest() 0fed1418 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext) 0fed1420 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext) 0fed1434 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext) 0fed1438 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String) 0fed14fc 66007a6c System.Web.HttpServerUtility.Execute(System.String, System.IO.TextWriter, Boolean) 0fed155c 660082a1 System.Web.HttpServerUtility.Transfer(System.String, Boolean) 0fed1570 660082e5 System.Web.HttpServerUtility.Transfer(System.String) 0fed157c 0fe1082b ErrorPage.Page_Load(System.Object, System.EventArgs) 0fed22b8 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs) 0fed22c8 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs) 0fed22d8 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs) 0fed22e8 6613cb50 System.Web.UI.Control.LoadRecursive() 0fed22fc 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean) 0fed24f8 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean) 0fed2530 6614d80f System.Web.UI.Page.ProcessRequest() 0fed2568 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext) 0fed2570 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext) 0fed2584 0fe10535 ASP.errorpage_aspx.ProcessRequest(System.Web.HttpContext) 0fed2588 660080c9 System.Web.HttpServerUtility.ExecuteInternal(System.Web.IHttpHandler, System.IO.TextWriter, Boolean, Boolean, System.Web.VirtualPath, System.Web.VirtualPath, System.String, System.Exception, System.String)
...

In this case we can see that the stack starts off with a call to ErrorPage.aspx, so we are probably handling some type of error condition,  then it calls into Server.Transfer, and this again starts processing errorpage.aspx and so it continues.  If you look closely the section marked in maroon keeps repeating over and over.  In fact I cut out part of the stack to save some space so in reality this pattern repeated itself probably 20 times before we finally filled up the stack.

This is the code for ErrorPage.Page_Load simplified.  It follows a pretty common pattern of try/catch and transfering to an error page in the case we can't handle the exception. 

    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            Response.Write(((Exception)Session["CurrentException"]).Message.ToString() + "
"); Response.Write("occurred on page " + Request.UrlReferrer.AbsoluteUri.ToString()); Session["CurrentException"] = null; } catch (Exception ex) { Server.Transfer(Application["ErrorPage"].ToString()); } }

The problem here is that if an exception occurrs in ErrorPage.aspx we will transfer to ourselves, and chances are big that when executing the next time the issue will not have gone away so we simply keep transfering to ourselves until we finally run out of stackspace.

Same pattern in login.aspx 

Another common place where we see these autotransfers is in login.aspx, i.e. when you have a setup where if you are not logged in you will be transfered to login.aspx which is perfectly fine, but then when you try to transfer back to the referring page once you are logged in you can run into this issue if you are not careful, if the user has logged on to the page directly.

Resolution

The solution in this case is thankfully very simple.  If you get an error on the error page you should not transfer to the errorpage.  This may seem pretty logical when you figure out what the issue is, but it's pretty easy to end up with code like this, especially when the pattern is not as straight forward as this one.

While I am on the subject, my personal recommendation would be to not use an aspx page for the error page in case the issue is more general, like an out of memory exception for example.  In a case like that an aspx error page would fail just like any other aspx page so for a more stable error handling routine, a static page like an html page is usually best for the error page.

Laters,

Tess

This question that I got from a reader is something that I get asked pretty frequently...

"I met a small problem during my dump analysis, our application is using .net 2.0. when I load 2.0 version of sos.dll, some of useful extension commands are lost, but sos.dll which is under clr10 debugging tool has.

Do you know any way to use those extension command in 1.1 under 2.0 sos.dll?"

After you have gotten used to the wealth of commands and functions in sos for .net framework 1.1 it can get a bit tedious to have to do these things manually when debugging 2.0 applications.  The differences are not extreme but commands such as !aspxpages etc. are missing in 2.0, and the reason is basically that the debugging architecture in 2.0 is very different from 1.1 so the commands are ported continously and sent out with hotfixes and service packs, but some just havent made it yet.

You can not use the 1.1 version on 2.0 applications by loading it in an alternate way unfortunately, for most commands like !aspxpages you can script the command like I did here, but that too is a bit tedious.  If there are some commands that you are missing or some that never existed that you would have liked to have, you can comment on this post by Tom so that he can incorporate it in future versions.

Laters,

Tess

From time to time I get questions like "our process spawns a lot of threads, how do we know who created them?" or "can I tell how many times we call this method?".  You can answer both of these questions by setting breakpoints, and below is a simple sample of how this is done in windbg with sos...

My demo application (MyApplication.exe) has a class MyThreadClass that has a method CreateAThread, this creates a thread and starts running some method on that thread.  What I am going to show here is how you can set a breakpoint when a thread is started to identify that the method CreateAThread started it, and automate it so that you don't have to click go after each time you hit the breakpoint...

1. Attach to the process with Windbg (File/Attach to process)

2. load sos (.loadby sos mscorwks)

3. Set the breakpoint on System.Threading.Thread..ctor in mscorlib.dll (the constructor for Thread)

0:005> !bpmd mscorlib.dll System.Threading.Thread..ctor
Found 4 methods...
MethodDesc = 79256ac8
Setting breakpoint: bp 793B01CC [System.Threading.Thread..ctor(System.Threading.ThreadStart)]
MethodDesc = 79256ad0
Setting breakpoint: bp 794064C4 [System.Threading.Thread..ctor(System.Threading.ThreadStart, Int32)]
MethodDesc = 79256ad8
Setting breakpoint: bp 794064F4 [System.Threading.Thread..ctor(System.Threading.ParameterizedThreadStart)]
MethodDesc = 79256ae0
Setting breakpoint: bp 79406510 [System.Threading.Thread..ctor(System.Threading.ParameterizedThreadStart, Int32)]

This sets up 4 different breakpoints, one for each overload of the Thread constructor.  Note that what it actually does is set up a native breakpoint at the addresses where these constructors are JITted eg. bp 793B01CC.

At this point our list of breakpoints looks like this

0:000> bl
0 e 793b01cc 0001 (0001) 0:**** mscorlib_ni+0x2f01cc
1 e 794064c4 0001 (0001) 0:**** mscorlib_ni+0x3464c4
2 e 794064f4 0001 (0001) 0:**** mscorlib_ni+0x3464f4
3 e 79406510 0001 (0001) 0:**** mscorlib_ni+0x346510

So far so good, this works well since these methods are already JITted so we know the address of the code.

4. Set a breakpoint on MyApplication.MyThreadClass.CreateAThread  (not used yet, therefore not JITted)

0:005> !bpmd MyApplication.exe MyApplication.MyThreadClass.CreateAThread
Found 1 methods...
Adding pending breakpoints...

In this case since the method is not JITted yet sos will wait until the method is JITted before it actually sets the breakpoint.

5. Hit G to get the debugger to go and call the CreateAThread method

0:005> g
(1cc8.14c4): CLR notification exception - code e0444143 (first chance)
JITTED MyApplication!MyApplication.MyThreadClass.CreateAThread()
Setting breakpoint: bp 023F04E8 [MyApplication.MyThreadClass.CreateAThread()]
Breakpoint 4 hit
eax=01d66b48 ebx=02721f60 ecx=02722fc4 edx=002216e8 esi=02722fc4 edi=02722fc4
eip=023f04e8 esp=0012efc8 ebp=0012f024 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
023f04e8 57 push edi

Once this method is hit, sos will set a breakpoint with bp, and our list of breakpoints now looks like this

0:000> bl
0 e 793b01cc 0001 (0001) 0:**** mscorlib_ni+0x2f01cc
1 e 794064c4 0001 (0001) 0:**** mscorlib_ni+0x3464c4
2 e 794064f4 0001 (0001) 0:**** mscorlib_ni+0x3464f4
3 e 79406510 0001 (0001) 0:**** mscorlib_ni+0x346510
4 e 023f04e8 0001 (0001) 0:****

If we continue hitting g we will stop everytime either CreateAThread is called or everytime a new thread is created.  If this is something that happens frequently this soon gets very tiring so we can automate it instead to have it print something everytime it hits CreateAThread and for example run !clrstack to show the stack everytime it creates a new thread

6. Customize the breakpoints using bp, in the command string that follows bp <address> you can put any old commands you like to execute (separated by semicolon), and you can even make the breakpoint conditional so that you do certain things if a condition is true and others if it is not, or only activate it for a few passes. The helpfiles for windbg has some good examples around this.  Notice in this case that apart from echoing some text and running !clrstack I am adding an extra g to have the debugger continue after it hit the breakpoint.

0:000> bp 793b01cc ".echo -->Created a new Thread;!clrstack;g"
breakpoint 0 redefined
0:000> bp 023f04e8 ".echo -->Hit breakpoint CreateAThread;g"
breakpoint 4 redefined

7. Run the application and watch the results scroll by...  you might want to run .logopen /d to create a logfile before you start running so that all the output gets logged

0:000> g
--> Hit breakpoint CreateAThread
--> Created a new Thread
OS Thread Id: 0x14c4 (0)
ESP EIP
0012efb4 793b01cc System.Threading.Thread..ctor(System.Threading.ThreadStart)
0012efb8 023f0530 MyApplication.MyThreadClass.CreateAThread()
0012efcc 023f049d MyApplication.Form1.btnThreads_Click(System.Object, System.EventArgs)
0012efe4 7b062c9a System.Windows.Forms.Control.OnClick(System.EventArgs)
0012eff8 7b11cb29 System.Windows.Forms.Button.OnClick(System.EventArgs)
0012f004 7b11cc38 System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)
0012f02c 7b0e5ae3 System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
0012f09c 7b070246 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
0012f0a0 7b07e46f [InlinedCallFrame: 0012f0a0]
0012f140 7b07e393 System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
0012f148 7b06fd0d System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
0012f14c 7b06fce6 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
0012f15c 7b06fb8a System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
0012f318 004720d4 [NDirectMethodFrameStandalone: 0012f318] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0012f328 7b0835e5 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
0012f3c8 7b0831a5 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0012f440 7b082fe3 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0012f470 7b0692c2 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
0012f480 023f00a8 MyApplication.Program.Main()
0012f69c 79e7c74b [GCFrame: 0012f69c]

And thats all folks,

Tess

I got a question the other day around calling GC.Collect and GC.WaitForPendingFinalizers. 

The issue revolved around a problem where the finalizer was blocked and where the application would call GC.Collect() and GC.WaitForPendingFinalizers() which then caused the thread that called GC.WaitForPendingFinalizers() to hang, and the question was

If we interrupt the thread that calls GC.WaitForPendingFinalizers() will this also interrupt the finalizer thread, i.e. unblocking the finalizer?

I would be very surprised if it did, but I can see where the question came from as the msdn documentation for GC.WaitForPendingFinalizers is a bit ambigous.

When the garbage collector finds objects that can be reclaimed, it checks each object to determine the object's finalization requirements. If an object implements a finalizer and has not disabled finalization by calling SuppressFinalize, the object is placed in a list of objects that are marked as ready for finalization. The garbage collector calls the Finalize methods for the objects in this list and removes the entries from the list. This method blocks until all finalizers have run to completion.

The thread on which finalizers are run is unspecified, so there is no guarantee that this method will terminate. However, this thread can be interrupted by another thread while the WaitForPendingFinalizers method is in progress. For example, you can start another thread that waits for a period of time and then interrupts this thread if this thread is still suspended.

Just to make sure, I decided to test this in a small sample asp.net page with 3 buttons

Button 1. Creates a bunch of objects called BadFinalizer() that sleeps for 5000 ms in the ~BadFinalizer() method
Button 2. Starts a new thread that calls GC.Collect(); GC.WaitForPendingFinalizers();
Button 3. Aborts this thread

Attaching windbg I could confirm my suspicion...

After clicking on button1 and button2 I had two threads

The blocked finalizer...

OS Thread Id: 0x153c (20)
ESP EIP
02a0f8fc 7d61cca8 [HelperMethodFrame: 02a0f8fc] System.Threading.Thread.SleepInternal(Int32)
02a0f950 0f7d0740 BadFinalizer.Finalize()
02a0fc1c 79fbcca7 [ContextTransitionFrame: 02a0fc1c]
02a0fcec 79fbcca7 [GCFrame: 02a0fcec]

and the thread that waits in GC.WaitForPendingFinalizers()

OS Thread Id: 0x1bec (1)
ESP EIP
0097ef1c 7d61d051 [HelperMethodFrame: 0097ef1c] System.GC.WaitForPendingFinalizers()
0097ef6c 0f7d0707 TestPage.WaitForFinalization()
0097ef70 793b0d1f System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
0097ef78 793740ab System.Threading.ExecutionContext.runTryCode(System.Object)
0097f39c 79e7c74b [HelperMethodFrame_PROTECTOBJ: 0097f39c] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
0097f404 79373ff7 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0097f41c 79373ede System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0097f434 793b0c68 System.Threading.ThreadHelper.ThreadStart()
0097f660 79e7c74b [GCFrame: 0097f660]
0097f950 79e7c74b [ContextTransitionFrame: 0097f950]

And after clicking button 3, the thread sitting in GC.WaitForPendingFinalizers() was gone, while the finalizer was still tugging away...

So, the answer is no, if you abort the thread that called WaitForPendingFinalizers() you will just abort that thread, the finalizer will keep going.

Laters,

Tess

From time to time I get questions about how to change settings in windbg like this one...

The default color scheme for the command window is really annoying, because it is always black and white. It is really hard to figure out something in the screen after you have executed some commands that generates lots of output, especially for the command you entered.

Then I tried to configure the colors through view -> options -> colors, unfortunately I failed because of the items listed there which was not documented well. After google for some time, I still did not get something interesting.

I am absolutely horrible with colors and the combination of colors.  I usually wear all black clothes with bright fuschia sneakers:) so before I answer the question above, let me just say that I am not going to advice on color combinations for windbg, I'll just show you how I have set up my windbg environment so that you can set up your own the way you like it.

Windbg Workspaces

Windbg uses workspaces to store the information about your debugging session setup, i.e. what colors you use, what windows will be opened and where, what your symbol and source path is etc.  The Workspace Contents portion of the windbg help file tells you more about what it stores.

There are a couple of different types of workspaces that get loaded

  • The base workspace is used when WinDbg is in a dormant state.
  • The default user-mode workspace is used when you are attaching to a user-mode process (by using the -p command-line option or by using the File | Attach to a Process command).
  • The remote default workspace is used when you are connecting to a debugging server.
  • The default kernel-mode workspace is used when WinDbg begins a kernel-mode debugging session.
  • The processor-specific workspace is used during kernel-mode debugging after WinDbg attaches to the target computer. There are separate processor-specific workspaces for x86-based, Itanium-based, and x64-based processors.
  • Named (user defined) workspaces that get loaded on demand (through File/Open Workspace)

When you start up windbg it starts up with the base workspace, and then when you load up a memory dump you will either stay in that workspace or if you have choose to "save workspace" the previous time you looked at this dump it will load up that workspace.  When you do "save workspace" when looking at a dump it will create a specific workspace for that dump.

Themes

Windbg also has a set of predefined Themes that are stored in the debugging tools for windows\themes folder, and you can use these to jump-start your own custom workspace.

My Debugging Environment step-by-step

This is my debugging environment for memory dumps.  Apart from changing the colors I have also added a few windows that are useful when debugging such as

  • Two command browser windows (currently used for !eeheap -gc and !dumpheap -stat).  Command browser windows allow you to execute a command and leave the results in the window so you dont have to scroll up and down to see the results.  For example here i use it for !dumpheap -stat and i can copy and paste MTs from the output to use in commands in the regular command window.   While debugging I can change the command here if I want to type .browse ~* e !clrstack for example in the command window.
  • The Calls window shows the callstack of the current thread which is nice if you want to jump between frames in the stack to view locals etc.
  • The Scratch Pad is probably my favourite feature.  It's like notepad in the debugger so if you find some interesting stuff you can just add notes here, and if you right click on it you can associate it with a file so that your notes get stored to disk.
  • Watch, Locals and Registers are pretty self explanatory
  • The Disassembly window shows the disassembly of the method you are executing on the current thread
  • The command window is where all the fun stuff happens, i.e. where your commands are executed

To get this look I started off by reading the Themes.doc in the debugging tools for windows\themes directory and followed the instructions to start a new workspace.  I chose the Standard.reg registry file to start setting things up.

Next I opened windbg without opening a dump file and removed some of the windows that I didnt like, including a placeholder and added the windows above from the View menu  (note: the !eeheap -gc and !dumpheap -stat windows are Command Browser windows), and finally i positioned them as shown above.

The colors are a bit tricky to understand so I'll try to give an explanation of what they all mean along with what colors I used for each setting.  The colors are changed under view/options (the ones in bold are the ones that i have changed)

Color name My bg - text choice Description
Background black Background for all windows (including source and disassembly windows)
Text lime All text that is not described elsewhere
Current line black on lime for the current line in the source and disassembly windows
Breakpoint current line white on fuschia if the current line is an active breakpoint
Enabled breakpoint white on red active breakpoint that is not on the current line
Disabled breakpoint white on yellow disabled breakpoint
Changed data text red (on black) data that has changed in the registers, locals and watch windows
Source ... various colors for numeric constants, character constants etc. in the source window.
I have changed all black to lime and all others to lighter colors so that they stand out on the black background.
Disabled window dark grey shown when a window is disabled, for example. the registers windows while you are not stopped at a breakpoint.
Normal level command window lime on black command output
Error level command window red on black error messages in command output
Warning level command window orange on black warnings (ex. symbol warnings) in command output
Verbose level command window blue on black additional info only displayed when you have view|verbose output turned on. For example module load info etc.
Prompt level command window black on lime command prompt and commands displayed in the command window. eg. !eeheap -gc above. 
It is nice to have this in a different color than the command output so that you can see where the command output starts.
Prompt registers level command window lime on black register output

For the rest of the colors I chose lime on black wherever it was black on whit except for for warnings or errors in which case I chose orange on black and red on black.

Apart from this I have also associated dump files with windbg as described in this article to setup the symbol path and load extensions automatically, but since then I have changed it to load up the 2.0 version of sos instead.

If you want to you can even set up different command files for different scenarios, for example for memory issues you may want to add .browse !dumpheap -stat or similar to have this information ready as soon as you open the dump, or .browse .time;.echo "***";!runaway for high CPU hangs. 

Finally, when all this was set up I closed windbg and asnwered yes to "save information for workspace?" for the workspace 'base' and now whenever I open a dump I already have all the windows that I have, the way I want them.  

Have fun,

Tess

During our ASP.NET debugging chat there were many questions around the GC and the different generations. In this post I will try to explain the basics of how the GC works and what you should think about when developing .net applications in relation to the GC.

First off, there is already a lot written about the .net Garbage Collector. One of the best resources regarding how the GC works and how to program efficiently for the GC is Maoni’s blog. She hasn’t written anything since May 2007 but all the posts on her blog are still very relevant since the GC hasn’t really changed enough that it makes a difference as far as .net developers should be concerned.

Maoni had a presentation at the 2005 PDC about the GC and unfortunately her link to the presentation points to an invalid location so if you are interested in looking at it I have attached it to this post. Most of what I will discuss in this article is a mixture of her presentation along with things I have learned along the way, and some of the pictures in the post are taken straight from her presentation.

Table of Contents

What are segments and heaps? How much is allocated for the GC?
What are generations and why do we use a generational GC?
When and how does a collection occur?
What are roots? What keeps an object alive?
What is the Large Object Heap? And why does it exist?
Which GC Flavor fits my application best?
What is the cost of a garbage collection? How can I keep this cost at a minimum?
Additional Resources

What are segments and heaps? How much is allocated for the GC?

When you first start up a .net application the GC will allocate memory to store your .net objects.

How much it will allocate is depends on what framework version you use (including service packs or hotfixes), if you are running on x64 or x86 and what GC flavor the application is using (workstation or server)

Here is an example of how the heaps and segments look for 2.0.50727.1433 (2.0 SP1), on a dual proc running ASP.NET (the server flavor of the GC).

We have two heaps (one per logical processor since we are running the server GC) and each heap initially has one “small object” segment and one large object segment.

The initial allocation size here is 192 MB because the GC reserved 64 MB for each small object segment and 32 MB for each large object segment.

0:000> !eeheap -gc
Number of GC Heaps: 2
------------------------------
Heap 0 (001c3a88)
generation 0 starts at 0x0310d288
generation 1 starts at 0x030ee154
generation 2 starts at 0x03030038
ephemeral segment allocation context: none
segment   begin    allocated size                reserved
001c92f0  7a733370 7a754b98  0x00021828(137,256) 00004000
001c5428  790d8620 790f7d8c  0x0001f76c(128,876) 00004000

03030000 03030038 03115294 0x000e525c(938,588) 03d3f000
Large object heap starts at 0x0b030038
segment   begin     allocated  size                    reserved
0b030000 0b030038 0b4d5aa8 0x004a5a70(4,872,816) 01af8000
Heap Size 0x5cbc60(6,077,536)
------------------------------
Heap 1 (001c4a48)
generation 0 starts at 0x0712614c
generation 1 starts at 0x071014ac
generation 2 starts at 0x07030038
ephemeral segment allocation context: none
segment   begin     allocated  size                    reserved
07030000 07030038 07134158 0x00104120(1,065,248) 03d2f000
Large object heap starts at 0x0d030038
segment    begin    allocated  size                  reserved
0d030000 0d030038 0d0f3588 0x000c3550(800,080) 01f3c000
Heap Size 0x1c7670(1,865,328)
------------------------------
GC Heap Size 0x7932d0(7,942,864)

If you want to know how much the GC has reserved and committed for your particular version/flavor of the GC you can look at the performance counters # Total Committed Bytes and # Total Reserved Bytes under .net CLR memory.

You can also use !address to calculate your segment size from a dump. For example, this small object heap segment starting at 03030000…

03030000 03030038 03115294 0x000e525c(938,588) 03d3f000

…has 0x002c1000 bytes committed and an additional 0x03d3f000 bytes reserved, so the small object heap segment size for this version and GC flavor is 0x002c1000+0x03d3f000 bytes = 64 MB

0:000> !address 03030000
03030000 : 03030000 - 002c1000
Type     00020000 MEM_PRIVATE
Protect  00000004 PAGE_READWRITE
State   00001000 MEM_COMMIT
Usage    RegionUsageIsVAD

0:000> !address 03030000+002c1000
03030000 : 032f1000 - 03d3f000
Type     00020000 MEM_PRIVATE
State   00002000 MEM_RESERVE
Usage    RegionUsageIsVAD

Since this is a number that is subject to change in any hotfix or service pack you shouldn’t rely on it, but if you are wondering how much you are allocating, that is the answer.

For example 2.0 SP1 (2.0.50727.1433) has a segment size of 512 MB for the small object segments and 128 MB for the large object segments so the initial allocation size is a lot bigger on 64 bit which causes generation 2 collections to occur much more seldom.  

  Server GC Workstation GC Workstation GC+Concurrent
# of heaps 1 per logical processor 1 1

 

Once a segment is full a new segment is created within the same heap, so a heap can have many small object heap segments and many large object heap segments but the number of .net heaps will not change during the life of the process. The memory within the segments is committed and decommitted as needed and the segments are deleted when they are no longer needed.

The two small segments at the beginning of heap 1 are used to store string constants and you can simply ignore them as they won’t really affect your application in any real sense.

 

What are generations and why do we use a generational GC?

In a generational GC objects are created in Gen 0 and if they are still alive when a collection happens they get promoted to Gen 1. If they are still alive when a Gen 1 collection happens they are promoted to Gen 2 etc. until they finally end up in their final resting place in the highest generation.

The idea behind a generational GC is that most objects are very temporary, like locals, parameters etc. i.e. they go out of scope while in generation 1. If we can keep collecting just these objects without having to go through all the memory we will save a lot of time and CPU power when cleaning up the objects.

The longer an object has been alive, the more likely it is that the object will be around for a very long time. Think about it, most objects that survive a couple of collections are objects that are stored in cache, session scope or in other long term storage like static variables. If we know that this is the case then we don’t have to bother constantly searching through them all.

In the .net GC there are 3 generations (0, 1, and 2) and then there are the large objects (objects over 85000) that end up in a separate segment. The LOH objects are different in the sense that even if they survive a collection they are not promoted since they exist outside of Gen 0, 1, and 2.

There are a few other benefits to a generational garbage collector because of how allocations are done.

If we look at our first heap:

Heap 0 (001c3a88)
generation 0 starts at 0x0310d288
generation 1 starts at 0x030ee154
generation 2 starts at 0x03030038
ephemeral segment allocation context: none
segment begin allocated size reserved

03030000 03030038 03115294 0x000e525c(938,588) 03d3f000

The small object segment would look like this, where the green part is Gen 2, blue is Gen 1 and Orange is Gen 0

 

When a new object is allocated it will be allocated right after the last object on the heap (in gen 0) at address 0x03115294 and it will continue like that, growing until Gen 0 has reached its budget at which point a garbage collection will occur.

Since objects are allocated sequentially in the segment the cost of allocation is extremely small. It consists of taking a cheap lock (on single proc), moving a pointer forward, clearing the memory for a new object and register the new object with the finalize queue if it has a finalizer/destructor. The fact that they are allocated sequentially also gives a few other benefits such as locality of time and reference which means that objects that are allocated in the same method at the same time are stored close together. Since they are allocated at the same time/place they are likely to be used together and accessing them will be very quick.

Generation 1 and 0 live in something called the ephemeral segment (the first small object segment in each heap) and the size of Gen 1 and Gen 0 can never exceed the size of a segment. If a new segment is created that will become the new ephemeral segment. Gen 2 on the other hand can grow indefinitely (or until you run out of memory) so if you have high memory consumption a large amount of your objects will live in Gen 2.

The budgets for generation 1 and 0 vary over time based on the allocation pattern of the process and how much is actually collected during each collection. You can see what the current budget for Gen 0 is by looking at the .net CLR memory/Gen 0 heap size.

When and how does a collection occur?

A collection occurs

· When you allocate a new object and the generation 0 budget is reached, i.e. if the new object would cause it to go over budget.
· When someone calls GC.Collect (Induced GC)
· Based on memory pressure

Contrary to popular belief collections don’t happen at certain time intervals etc. so this means that if your application is not allocating any memory and is not calling GC.Collect, no collections will occur.
It is also important to understand that collections of the higher generations will only occur once their budgets are reached, and in the case of 64-bit processes gen 2 collections occur very seldom which means that a lot of memory may be sitting around even though it is not in use, just because it made its way into gen 2.

If you have a process (64 or 32 bit) that does not use a lot of .net objects, but does use a lot of native resources like threads, connections etc. you may end up in a situation, if you are not properly cleaning up the threads, connections etc. where you run out of native resources and handles because the objects have not been collected. Therefore it is absolutely crucial that you do dispose/close all objects that have native resources right after you are finished using them.

A garbage collection simplified goes through the following sequence

1. Suspend all threads that are making .net calls (i.e. could be allocating objects or modifying the objects on the heap). Threads making native calls are suspended on their return to managed code.

2. Determine which objects in the current generation can be garbage collected. This is done by asking the JIT, the EE Stack Walker, the Handle table and the finalize queue which objects are still accessible/in use. See maonis post “I am a happy janitor” for more info on this.

3. Delete all marked objects or add the empty spaces to a free list if the heap is not compacted.

4. Compact/Move the leftover objects to the backend of the heap (this is the most expensive part)

5. Resume all threads

Here is a collection, step by step (with pictures from Maonis presentation)

Allocate new objects at the end of the heap in Gen 0

Determine which objects are still accessible

 

Sweep the garbage and add the free blocks to the free-list to store new objects there if non-compacting

Compact the heap

Move the start of generation 0 to the end of the objects that survived. The survivors are now in generation 1

New objects are allocated in generation 0

A gen 1 collection can’t occur without a Gen 0 collection so any time a Gen 1 collection occurs it will be a Gen 1 + Gen 0, and same of course for Gen 2.

What are roots? What keeps an object alive?

If your object is rooted, that means that an object (a root) has a reference (directly or indirectly) to your object, and that that root object is either on a stack as a parameter or local, or it is a static variable, or it is on the finalizer queue, meaning that it needs to be finalized before it can be released. See this post for a discussion of different types of roots and what they mean.

An object is also considered alive if it is referenced by an object in an older generation, until that older object is collected of course.

What is the Large Object Heap? And why does it exist?

The large object heap is a special segment (or multiple segments) in a heap, specifically meant for objects over 85000 bytes. As I have mentioned many times before in my posts this 85000 refers to the size of the object itself, not the size of the object and all its children.

The example I always use is a large dataset. The dataset itself is merely a collection of a few links to different arrays, so the dataset object will never grow independently of the number of rows or columns it has, it will consistently stay at 80 bytes or 120 bytes etc. (different in different framework versions). In other words the dataset will never make it to the large object heap.

The objects that will be stored on the large object heap are usually strings and arrays of different kinds since a string is stored in one contiguous chunk rather than a linked list of the different characters. Same thing with an array, but again, here it is important to understand that it is just the size/length of the array that determines if it is a large object or not, not the total size of the objects that it references.

When you create a large object, for example a large string, it immediately goes on the large object heap segment so it is never even allocated in gen 0. As mentioned before the large object heap segment is not generational, if an object in the LOH is alive during a collection, it simply stays on the LOH.

The reasoning behind having a special heap for large objects is that it is very expensive to move them around, and particularly for arrays for example, it is very expensive to update all the references etc. Therefore the LOH is not compacted, instead any space that is left between objects when a garbage collections occur are put on a free-list so that if a new object is allocated it can be allocated in that free space. If multiple collections occur causing two or more free spaces after each other these are coalesced into one larger free space.

The large object heap is collected when a gen 2 collection occurs.

Which GC Flavor fits my application best?

At present there are three different versions / flavors of the GC, each optimized for different types of applications.

Server GC

The server GC is optimized for high throughput and high scalability in server applications where there is a consistent load and requests are allocating and deallocating memory at a high rate.
The server GC uses one heap and one GC thread per processor and tries to balance the heaps as much as possible. At the time of a garbage collection, the GC threads work on their respective threads and rendez-vous at certain points. Since they all work on their own heaps, minimal locking etc. is needed which makes it very efficient in this type of situation.

The Server GC is only available on multi processor machi