스레드 비정상 종료로 발생하는 CLOSE_WAIT 소켓 상태
보통, 서버 측 소켓에서는 Accept 후 클라이언트 측 과의 통신을 별도의 스레드에 할당해서 처리합니다. 간략하게 코드를 만들어 보면 다음과 같은 식인데요.
static void Main(string[] args)
{
TcpListener listener = new TcpListener(6000);
listener.Start();
while (true)
{
TcpClient childSocket = listener.AcceptTcpClient();
Thread thread = new Thread(tcpClient_Connected);
thread.Start(childSocket); // 혹은 스레드 풀을 사용하거나.
}
}
static void tcpClient_Connected(object obj)
{
TcpClient socket = obj as TcpClient;
Thread.Sleep(1000 * 10); // 테스트를 위해 10초 동안 대기
socket.Close();
}
그런데, 여기서 만약 tcpClient_Connected 메서드를 처리하는 스레드가 비정상 종료하는 상황이 발생했다고 가정해 보겠습니다.
static void Main(string[] args)
{
... [생략] ...
Thread thread = new Thread(tcpClient_Connected);
thread.Start(childSocket);
ThreadPool.QueueUserWorkItem(abortThread, thread);
... [생략] ...
}
static void tcpClient_Connected(object obj)
{
... [생략] ...
}
static void abortThread(object obj)
{
Thread thread = obj as Thread;
Thread.Sleep(5000); // 5초 후에 스레드 강제 종료
thread.Abort();
}
이렇게 되면, 당연히 서버 측 대응 소켓은 Close 메서드를 호출하지 못하게 되고 클라이언트 측 소켓이 Close를 하는 시점에 CLOSE_WAIT으로 빠지게 됩니다.
CLOSE_WAIT 상태에 대해서는 지난번 글에서 자세히 설명했으므로 여기서는 넘어가겠습니다.
코드로 재현하는 소켓 상태(FIN_WAIT1, FIN_WAIT2, TIME_WAIT, CLOSE_WAIT, LAST_WAIT)
; https://www.sysnet.pe.kr/2/0/1334
이런 상황을 개선하려면 tcpClient_Connected 메서드 내에서 예외 처리를 추가해 줄 수 있습니다.
static void tcpClient_Connected(object obj)
{
TcpClient socket = obj as TcpClient;
try
{
Thread.Sleep(1000 * 10); // 테스트를 위해 10초 동안 대기
}
catch { }
finally
{
socket.Close();
}
}
finally에 의해서 소켓은 닫히게 되도록 했지만, 그래도 뭔가 미심쩍습니다. 왜냐하면 스레드 비정상 종료 현상은 이후의 어떤 코드도 실행하지 못할 가능성이 더 크기 때문에 저 정도의 처리로는 부족하기 때문입니다.
바로 이럴 때 적합하게 사용할 수 있는 것이 CER(constrained execution region)입니다.
RuntimeHelpers.PrepareConstrainedRegions Method
; https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions
예외처리 블록을 CER로 지정해 주면 해당 코드는 반드시 실행되도록 JIT 컴파일까지 미리 시켜 놓기 때문에 안전하게 Close 메서드를 호출할 수 있게 됩니다. 방법도 그다지 어렵지 않은데, 단순히 try 블록이 시작되기 전에 RuntimeHelpers.PrepareConstrainedRegions 메서드만 불러주면 됩니다.
그리하여, 스레드 비정상 종료에도 안전하게 소켓을 해제하는 코드는 다음의 형태를 갖게 됩니다.
static void tcpClient_Connected(object obj)
{
TcpClient socket = obj as TcpClient;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
Thread.Sleep(5000 * 60);
}
catch
{ }
finally
{
socket.Close();
}
}
단순한 TCP 서버에서는 위와 같은 처리를 안해도 무리는 없지만, 24시간/365일 구동되는 시스템이라면 꼭 필요한 장치일 수 있겠지요. ^^
(
첨부파일은 위의 상황을 테스트 할 수 있는 간략한 예제입니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]