이전 포스팅에서 Tess의 IIS 6.0 + ASP.Net 1.1 이하 버젼에서의 Hang Dump 분석과 관련된 정보를 올렸습니다.
비교적(겨우 2년 전이네요 :-) 최근에 Tess의 블로그에 ASP.Net 2.0 에서 같은 맥락으로 Hang Dump 분석시 참고가 되는 글이 올라와서 같이 번역(사실 거의 복사에 가깝습니다만) 해서 올립니다.
Tess의 글에서 언급하고 있습니다만 서버 프로세스가 처리를 하지 않거나 메모리 사용량이 늘어나거나 CPU 점유율이 높아지면서 Hang Dump를 수집하게 되면 고객들 께서는 "무슨 일을 하기에" 그런 일이 벌어지는지 문의를 하십니다.
바로 이전 포스팅과 본 포스팅은 "무슨일" 이 아니라 "나머지 무시할 수 있는 것"들에 대한 얘기 입니다. 즉, 사용자의 Request를 처리하지 않는 "Normal"한 (또는 Idle 상태의) Thread들은 어떻게 생겼는가 에 대해서 언급하고 있습니다.
일단 이전 포스팅에서 IIS 6.0의 W3WP 프로세스의 Thread들이 소개 되어 있기 때문에 간단하게 넘기고 여기서는 제목 처럼 .Net Framework 2.0(또는 ASP.Net 2.0)에서 이전의 .Net Framework 1.1과 달라진 부분에 집중 해 보겠습니다.
우선 놀고 있는 CLR Worker Thread를 보죠.
Idle CLR Worker Thread
혹시 이전 번젼과 차이가 느껴지시나요? 참고를 위해서 이전 버젼의 CLR Worker Thread의 Stack Trace를 비교해 보시죠.
이전 포스팅에서 잠시 언급을 했습니다만, .Net Framework 1.1은 GC Version이 서로 많이 다르고
서버 프로세스와 일반 클라이언트 프로세스에 로딩 되는 DLL 역시 바뀝니다.
ASP.Net 2.0에서는 서버 프로세스에서도 mscrowks (Workstation version)이 올라가고 있다는 뜻일까요? 그렇지는 않습니다.
이는 현재 구현상 이름이 남아 있는 것 뿐이 실행시 모양은 차이가 있습니다.
아래 GC Thread 부분을 참고하세요.
Idle CLR WorkerThread for ASP.Net 1.1 11 Id: fb8.268 Suspend: 0 Teb: 7ffaa000 UnfrozenChildEBP RetAddr Args to Child 0199fdf8 7c822124 77e6baa8 00000234 00000000 ntdll!KiFastSystemCallRet0199fdfc 77e6baa8 00000234 00000000 0199fe40 ntdll!NtWaitForSingleObject+0xc0199fe6c 77e6ba12 00000234 00009c40 00000000 kernel32!WaitForSingleObjectEx+0xac0199fe80 791d401f 00000234 00009c40 00000000 kernel32!WaitForSingleObject+0x120199fea4 791fdacc 00000000 00000000 80a56bcc mscorsvr!ThreadpoolMgr::WorkerThreadStart+0x3a0199ffb8 77e66063 000b6da8 00000000 00000000 mscorsvr!ThreadpoolMgr::intermediateThreadProc+0x440199ffec 00000000 791fda8b 000b6da8 00000000 kernel32!BaseThreadStart+0x34
Idle CLR WorkerThread for ASP.Net 1.1
11 Id: fb8.268 Suspend: 0 Teb: 7ffaa000 UnfrozenChildEBP RetAddr Args to Child 0199fdf8 7c822124 77e6baa8 00000234 00000000 ntdll!KiFastSystemCallRet0199fdfc 77e6baa8 00000234 00000000 0199fe40 ntdll!NtWaitForSingleObject+0xc0199fe6c 77e6ba12 00000234 00009c40 00000000 kernel32!WaitForSingleObjectEx+0xac0199fe80 791d401f 00000234 00009c40 00000000 kernel32!WaitForSingleObject+0x120199fea4 791fdacc 00000000 00000000 80a56bcc mscorsvr!ThreadpoolMgr::WorkerThreadStart+0x3a0199ffb8 77e66063 000b6da8 00000000 00000000 mscorsvr!ThreadpoolMgr::intermediateThreadProc+0x440199ffec 00000000 791fda8b 000b6da8 00000000 kernel32!BaseThreadStart+0x34
Idle CLR Completion Port/IO Thread
Idle CLR Gate Thread
위에서 언급했던 Server GC와 Workstation GC의 차이는 아래의 Stack Trace에서 차이가 납니다.
Idle GC Thread
18 Id: efc.12b8 Suspend: 1 Teb: fff82000 UnfrozenChildEBP RetAddr Args to Child 0257fe6c 7d4d8c56 000002dc 00000000 00000000 ntdll!ZwWaitForSingleObject+0x150257fedc 79e718fd 000002dc ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac0257ff20 79e718c6 000002dc ffffffff 00000000 mscorwks!PEImage::LoadImage+0x1990257ff70 79e7187c ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x1170257ff80 7a0d6f16 ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x170257ffa8 7a0d722e 00000000 fcb9fe5a 0257ffec mscorwks!SVR::gc_heap::gc_thread_function+0x2f0257ffb8 7d4dfff1 001b20e8 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b0257ffec 00000000 7a0d7192 001b20e8 00000000 kernel32!BaseThreadStart+0x34
아래의 Stack Trace는 Client Application 상에서 실행되고 있는 GC Thread의 Snapshot 입니다,.
특히, 붉게 표시된 부분은 GC가 위의 Thread와 달리 열심히 살아 있는 (즉, 사용되고 있는) 객체를 표시하고 있는 중이라는 것을 보실 수 있습니다.
위의 내용과 비교해 보시면 mscorwks!SVR이 아니네요.
ChildEBP RetAddr 0776fa8c 68d05dda mscorwks!WKS::gc_heap::mark_object_simple1+0x1480776fab8 68c9f515 mscorwks!WKS::gc_heap::mark_object_simple+0x19d0776faec 68c9efc1 mscorwks!WKS::gc_heap::c_drain_mark_list+0x950776fb18 68c9f9b3 mscorwks!WKS::gc_heap::c_mark_phase+0xaa0776fb34 68d0ed65 mscorwks!WKS::gc_heap::gc1+0x590776fb4c 68d0edb7 mscorwks!WKS::gc_heap::gc_thread_function+0x9f0776fb54 75e04911 mscorwks!WKS::gc_heap::gc_thread_stub+0x730776fb60 76e3e4b6 KERNEL32!BaseThreadInitThunk+0xe0776fba0 76e3e489 ntdll!__RtlUserThreadStart+0x230776fbb8 00000000 ntdll!_RtlUserThreadStart+0x1b
Idle Finalizer Thread
GC와 떨어져서 언급 할 수 없는 Finalizer의 경우도 역시 마찬가지 입니다.
Idle CLR Debugger Thread
Idle Timer Thread
Idle Appdomain Unload Thread
이전에는 없던 AppDomain Unload Thread라는 것이 생겼는데요. 관련 설명은 다음의 MSDN URL을 참고하세요.
AppDomain.Unload 메서드
http://msdn.microsoft.com/ko-kr/library/system.appdomain.unload.aspx
설명
.NET Framework 버전 2.0에는 응용 프로그램 도메인 언로드 전용 스레드가 있습니다. 이 스레드를 사용하면 특히 .NET Framework이 호스팅된 경우 안전성이 향상됩니다. 스레드가 Unload를 호출하면 대상 도메인이 언로드되도록 표시됩니다. 그러면 전용 스레드가 도메인의 언로드를 시도하고 도메인의 모든 스레드가 중단됩니다. 스레드가 비관리 코드를 실행하거나 finally 블록을 실행하고 있는 등의 이유로 중단되지 않는 경우 일정한 시간이 지난 후 원래 Unload를 호출한 스레드에서 CannotUnloadAppDomainException이 throw됩니다. 중단할 수 없는 스레드가 결국 종료되면 대상 도메인은 언로드되지 않습니다. 따라서 .NET Framework 버전 2.0에서는 실행 중인 스레드를 종료시키지 못할 수 있으므로 domain이 언로드되지 않을 수도 있습니다.