C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상을 해결하기 위한 시도
지난 글에서 Thread.Suspend 호출 시 Resume까지의 시간 동안 메모리 할당이 발생하면 hang 현상이 발생할 수 있다고 설명했습니다.
C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상
; https://www.sysnet.pe.kr/2/0/11473
windbg - Thread.Suspend 호출 시 응용 프로그램 hang 현상에 대한 덤프 분석
; https://www.sysnet.pe.kr/2/0/11475
그러니까, 관건은 GC가 발생하는 동안에는 Thread.Suspend를 호출해서는 안 되는 것입니다. 그렇다면, 혹시 GC.WaitForFullGCApproach / GC.WaitForFullGCComplete을 이용해 GC가 임박했을 때 Thread.Suspend를 하지 않도록 만드는 것도 가능하지 않을까요?
How to receive a full GC notification with .NET 4.0
; https://learn.microsoft.com/en-us/archive/blogs/albulank/how-to-receive-a-full-gc-notification-with-net-4-0
GC.RegisterForFullGCNotification(Int32, Int32) Method
; https://learn.microsoft.com/en-us/dotnet/api/system.gc.registerforfullgcnotification
이를 바탕으로 다음과 같이 GC가 구동하려고 할 때 GC.WaitForFullGCApproach ~ GC.WaitForFullGCComplete 호출 구간에는 Thread.Suspend를 하지 못하도록 변경을 해봤습니다.
static volatile bool _safeStackTrace;
static void Main(string[] args)
{
GC.RegisterForFullGCNotification(10, 10);
_safeStackTrace = true;
Thread thWaitForFullGC = new Thread(GCNotifyProc);
thWaitForFullGC.IsBackground = true;
thWaitForFullGC.Start();
Program pg = new Program();
pg.Start();
Console.ReadLine();
}
static private void GCNotifyProc()
{
while (true)
{
var gcApproachStatus = GC.WaitForFullGCApproach();
if (gcApproachStatus == GCNotificationStatus.Succeeded)
{
_safeStackTrace = false;
GC.Collect();
}
else
{
return;
}
var gcCompleteStatus = GC.WaitForFullGCComplete();
if (gcCompleteStatus == GCNotificationStatus.Succeeded)
{
_safeStackTrace = true;
}
else
{
return;
}
}
}
private static string GetCallStack(Thread t)
{
string result = "";
try
{
// ...[생략]...
if (_safeStackTrace == true)
{
trace = new System.Diagnostics.StackTrace(t, false);
result = trace.ToString();
}
// ...[생략]...
} catch { }
return result;
}
하지만, 실제로 해보면 얼마 못 가서 ^^; 멈춥니다. 왜냐하면 GC.WaitForFullGCApproach 이후 _safeStackTrace = false를 설정하기 전, 다른 스레드가 이미 "new System.Diagnostics.StackTrace(t, false)" 코드로 진입해 힙 할당을 초래하는 코드를 실행할 수 있기 때문입니다. 어쩌면 이런 짧은 시간에 발생하는 경합 문제는 RegisterForFullGCNotification의 threshold 값을 10보다 높게 잡아 여유를 주거나, 테스트 자체의 부하를 조금 낮추면 확률을 줄일 수 있을 것입니다.
하지만 그런 시도를 하기가 좀 무의미한 면이 있는데요, 사실 별로 현실성이 없다는 생각이 들었습니다. 왜냐하면, GC.WaitForFullGCApproach, GC.WaitForFullGCComplete 메서드가 동작하도록 하려면 반드시 "Concurrent GC" 모드를 꺼야 하기 때문입니다. 즉, app.config 파일에 다음과 같은 항목을 포함시켜야만 합니다.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0"/>
</startup>
<runtime>
<gcServer enabled="true"/>
<gcConcurrent enabled="false"/>
</runtime>
</configuration>
독립 프로그램을 만드는 경우라면 저것도 방법일 수 있겠지만, 라이브러리 개발자들에게는 전혀 현실성이 없는 요구 사항입니다.
(
첨부 파일은 이 글의 테스트 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]