Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)

windbg 분석 사례 - 닷넷 프로파일러의 GC 콜백 부하

희한한 현상이 발생했습니다. 새로 개발한 닷넷 프로파일러가 설치되면 "Performance Monitor" MMC에서 "ASP.NET v4.0.30319" 범주의 "Requests Queued"에 큐잉 현상이 발생하는 것입니다.

현상이 발생하는 PC에서 Process Explorer를 통해 살펴보면 유독 다음과 같은 식의 콜 스택이 자주 보이는 것을 확인할 수 있었습니다.

ntdll.dll!ZwWaitForSingleObject+0xa
KERNELBASE.dll!WaitForSingleObjectEx+0x98
clr.dll!CoUninitializeEE+0x27eef
clr.dll!CoUninitializeEE+0x27ea3
clr.dll!CoUninitializeEE+0x27e64
clr.dll!GetMetaDataInternalInterface+0x327b9
clr.dll!ClrCreateManagedInstance+0x2e13
KERNEL32.DLL!BaseThreadInitThunk+0x22
ntdll.dll!RtlUserThreadStart+0x34

clr.dll!TranslateSecurityAttributes+0xa780c
clr.dll!ClrCreateManagedInstance+0x28a5e
clr.dll!TranslateSecurityAttributes+0xa6cee
clr.dll!TranslateSecurityAttributes+0x14e95b
clr.dll!TranslateSecurityAttributes+0x192985
clr.dll!ClrCreateManagedInstance+0xa13b3
clr.dll!GetMetaDataInternalInterface+0x326f2
clr.dll!ClrCreateManagedInstance+0x2e13
KERNEL32.DLL!BaseThreadInitThunk+0x22
ntdll.dll!RtlUserThreadStart+0x34

닷넷 프로파일러를 빼면 "ClrCreateManagedInstance" 메서드의 호출 발생이 현저하게 줄어듭니다. ClrCreateManagedInstance는 현재 deprecated 상태로 비-관리 코드에서 관리 코드를 호출할 때 사용하는 API입니다.

ClrCreateManagedInstance Function
; https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/clrcreatemanagedinstance-function

어쨌든 여기까지의 정보로는 뭔가 부족하군요. ^^




좀 더 자세한 정보가 필요하면 역시나 덤프뿐이 없습니다. 큐잉 현상이 나타나는 순간에 풀 덤프를 뜬 후 ClrCreateManagedInstance가 발생하는 스레드의 (sos를 이용한) 콜 스택을 확인했습니다.

0:012> !clrstack
OS Thread Id: 0x18bc (12)
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
Failed to start stack walk: 80070057

오호~~~ 해당 스레드가 ASP.NET 요청을 처리하는 스레드가 아니었군요. 그렇다면 네이티브 콜스택을 확인해 볼까요?

0:012> k
Child-SP          RetAddr           Call Site
00000043`a0d1f9d8 00007ff8`69219fb6 clr!CallbackShimElementPcData<&CMicrodomManifestWalker::Handler_assembly_description_pcdata>
00000043`a0d1f9e0 00007ff8`69282f8e clr!Object::GetGCSafeTypeHandleIfPossible+0x7a
00000043`a0d1fa20 00007ff8`6927a110 clr!SafeGetClassIDFromObject+0xe
00000043`a0d1fa50 00007ff8`692be145 clr!AllocByClassHelper+0x6d
00000043`a0d1faa0 00007ff8`68f7fb67 clr!SVR::gc_heap::walk_heap+0xb5
00000043`a0d1faf0 00007ff8`68e45882 clr!SVR::gc_heap::garbage_collect+0x4e5
00000043`a0d1fb80 00007ff8`68ee15c7 clr!SVR::gc_heap::gc_thread_function+0xdb
00000043`a0d1fbb0 00007ff8`70e113d2 clr!SVR::gc_heap::gc_thread_stub+0x94
00000043`a0d1fcf0 00007ff8`737a5454 kernel32!BaseThreadInitThunk+0x22
00000043`a0d1fd20 00000000`00000000 ntdll!RtlUserThreadStart+0x34

(덤프 뜨는 순간이 하필 달라서였을 수도 있지만) Process Explorer에서의 콜스택과 다른 것이 흥미롭습니다. 하지만 결정적으로, 문제가 되던 그 스레드는 바로 GC 스레드였던 것입니다. 여기까지 생각하니 감이 오는 것이 있었습니다. 바로 .NET Profiler에서 원하는 콜백을 받기 위해 설정하는 옵션 중에 GC 관련한 것이 있었다는 점입니다.

COR_PRF_MONITOR
; https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/cor-prf-monitor-enumeration

COR_PRF_MONITOR 열거 값에서 GarbageCollectionStarted, GarbageCollectionFinished, FinalizeableObjectQueued 콜백이 필요해서 COR_PRF_MONITOR_GC를 걸었었는데요. 사실, COR_PRF_MONITOR_GC 열거 값을 설정하면 다음의 콜백들도 함께 발생합니다.

  • MovedReferences
  • MovedReferences2
  • SurvivingReferences
  • SurvivingReferences2
  • ObjectReferences
  • ObjectsAllocatedByClass
  • RootReferences
  • RootReferences2
  • HandleCreated
  • HandleDestroyed

재미있는 것은, 제가 저 메서드들의 콜백에 대해 E_NOTIMPL을 반환값으로 했기 때문에 한번 호출된 이후로는 더 이상 안 불릴 거라고 가정했다는 점입니다.

STDMETHOD(MovedReferences2)(ULONG cMovedObjectIDRanges, ObjectID oldObjectIDRangeStart[], ObjectID newObjectIDRangeStart[], SIZE_T cObjectIDRangeLength[])
{
    UNREFERENCED_PARAMETER(cObjectIDRangeLength);
    return E_NOTIMPL;
}

그런데... E_NOTIMPL 반환값에 상관없이 필요할 때마다 매번 불렸습니다. ^^; 이 점이 상황을 악화시켰는데요, 가령 MovedReferences2 같은 경우에는, GC 발생 시 살아남은 객체 중 이동이 있으면 매번 발생하는 것이기 때문에 이런 것들이 합쳐져 은근히 부하를 주었던 것입니다. (하지만... 그래도 이상하긴 합니다. 아무리 콜백이 불려지는 것이지만 단순히 return 코드만 있는 것 뿐인데!)

정리해 보면, (디버깅 목적으로 제외하고) 실제 운영될 서버에 COR_PRF_MONITOR_GC 옵션이 붙은 닷넷 프로파일러를 넣는 것은 사용자 응답 시간이 길어진다는 단점으로 인해 사용해서는 안됩니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/10/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  [123]  124  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10880정성태12/16/201521334오류 유형: 315. 닷넷 프로파일러의 오류 코드 정보
10879정성태12/16/201523215오류 유형: 314. Error : DEP0700 : Registration of the app failed. error 0x80070005
10878정성태12/9/201526455디버깅 기술: 75. UWP(유니버설 윈도우 플랫폼) 앱에서 global::System.Diagnostics.Debugger.Break 예외 발생 시 대응 방법
10877정성태12/9/201530716VC++: 93. std::thread 사용 시 R6010 오류 [2]
10876정성태11/26/201526622.NET Framework: 541. SignedXml을 이용한 ds:Signature만드는 방법 [3]파일 다운로드1
10875정성태11/26/201531813개발 환경 구성: 279. signtool.exe의 다중 서명 기능 [2]
10874정성태11/26/201527531개발 환경 구성: 278. 인증서와 인증서를 이용한 코드 사인의 해시 구분
10873정성태11/25/201526224.NET Framework: 540. C# - 부동 소수 계산 왜 이렇게 나오죠? (2) [3]파일 다운로드1
10872정성태11/24/201534262.NET Framework: 539. C# - 부동 소수 계산 왜 이렇게 나오죠? (1) [1]
10871정성태11/23/201528710오류 유형: 313. SignTool Error: No certificates were found that met all the given criteria.
10870정성태11/23/201529879오류 유형: 312. 윈도우 10 TH2 (버전 1511) 업데이트가 안되는 경우 [1]
10869정성태11/23/201526045오류 유형: 311. certutil 실행 오류 - 0x80070057 [1]
10868정성태11/20/201525474제니퍼 .NET: 25. 제니퍼 닷넷 적용 사례 (5) - RestSharp 라이브러리의 CPU High 현상파일 다운로드1
10867정성태10/18/201528649.NET Framework: 538. Thread.Abort로 인해 프로세스가 종료되는 현상
10866정성태10/14/201523905.NET Framework: 537. C# - Reflection의 박싱 없이 값 형식을 다루는 방법파일 다운로드1
10865정성태10/13/201524194.NET Framework: 536. Thread.Abort의 스레드 종료 지연파일 다운로드1
10864정성태10/12/201521904.NET Framework: 535. aspnet.config 파일의 설정을 읽는 방법
10863정성태10/9/201527443.NET Framework: 534. ASP.NET 응용 프로그램이 예외로 프로세스가 종료된다면?
10862정성태10/9/201525685오류 유형: 310. 비주얼 스튜디오 - Unspecified error (Exception from HRESULT: 0x80004005 (E_FAIL))
10861정성태10/9/201530358기타: 54. 도서: 시작하세요! C# 6.0 프로그래밍: 기본 문법부터 실전 예제까지 (2)
10860정성태10/5/201528071개발 환경 구성: 277. IIS AppPool의 시작/중단에 대한 이벤트 로그 확인 방법
10859정성태10/5/201528500.NET Framework: 533. C#에서 string 형식이 primitive일까요? [6]
10858정성태10/2/201524981VS.NET IDE: 105. Visual Studio의 단위 테스트 작성 시 Fakes를 이용한 메서드 재정의 방법 [1]파일 다운로드1
10857정성태10/1/201521664VS.NET IDE: 104. Visual C++ 프로젝트의 빌드 이벤트에서 환경 변수 사용하는 방법
10856정성태9/30/201533116.NET Framework: 532. WPF DataGrid의 데이터 바인딩 시 리플렉션의 부하는 어느 정도일까요?파일 다운로드1
10855정성태9/30/201522377.NET Framework: 531. C# - XSLT 내의 javascript에 전달한 XML 노드의 타입은?
... 121  122  [123]  124  125  126  127  128  129  130  131  132  133  134  135  ...