Microsoft MVP성태의 닷넷 이야기
.NET Framework: 182. WCF의 InactivityTimeout [링크 복사], [링크+제목 복사],
조회: 22118
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)
WCF의 InactivityTimeout


"유수석의 WCF 바이블", 다들 읽어보셨죠?
WCF 실무에 뛰어드시기 전에 꼭 한번 읽어보시기를 바랍니다. (사실 저도 ^^; 아직 9장까지밖에 못 읽었습니다.)

어쨌든! 저 역시 위의 책을 읽으면서 많이 배우고 있습니다. 예를 들어 FaultException 예외를 사용하면 세션이 Faulted 상태로 빠지지 않는다는 사실을 몰랐었고, WCF가 지원하는 세션을 크게 "트랜스포트 세션", "보안 세션", "신뢰할 수 있는 세션"으로 나눈다는 것을 머릿속으로 정리해놓지는 못했었습니다.

그런데,,, 옥의 티가 하나 있는데요. 바로 "신뢰할 수 있는 세션"의 "InactivityTimeout" 설명이 완벽하지 못하다는 정도! (완벽?)

그 책의 636페이지에 보면 아래와 같은 설명이 있는데요.

"
신뢰할 수 있는 세션은 자체적으로 타임아웃을 InactivityTimeout 속성을 통해 설정할 수 있다. 이 타임아웃 시간 동안 클라이언트가 서비스를 호출하지 않으면 신뢰할 수 있는 세션은 종료된다.
... [생략] ...
ReceiveTimeout과 InactivityTimeout 속성이 모두 설정될 수 있으므로 신뢰할 수 있는 세션에 실제로 적용되는 타임아웃 시간은 두 속성값 중 작은 값으로 결정됨을 알아두자.
"



위의 설명에 의하면, "ReceiveTimeout 설정과 InactivityTimeout 속성은 동일한 기능을 하는데, 클라이언트가 서버에 대해 지정된 ReceiveTimeout 또는 InactivityTimeout 시간 동안 호출하지 않으면 세션이 종료된다."로 요약될 수 있습니다. 단지, "트랜스포트 세션"에서는 ReceiveTimeout 값이 사용되고, "신뢰할 수 있는 세션"에서는 부가적으로 InactivityTimeout 값이 하나 더 제공된다는 정도의 의미로 해석됩니다.

하지만, 실제로 테스트 해보면 이 설명이 사실과 다르다는 것을 알 수 있습니다.

예전에 제가 아래의 글에서 InactivityTimeout 값을 설명한 적이 있었는데요.

WCF : netTcpBinding에서의 각종 Timeout 값 설명
; https://www.sysnet.pe.kr/2/0/536

그 때는 말로만 설명하는 것으로 그쳤지만, 마침 질문해 주신 분도 있고 하니 실험적으로 알 수 있는 방법을 제공하도록 하겠습니다.




이를 테스트 해보기 위해, 세션 사용이 가능한 netTcpBinding 종단점을 제공하는 서비스를 만들고 config에 다음과 같이 ReceiveTimeout, InactivityTimeout을 설정합니다.

<netTcpBinding>
    <binding name="netTcpBindingConfig" receiveTimeout="00:01:30" >
        <security mode="None"/>
        <reliableSession enabled="true" inactivityTimeout="00:00:10" ordered="true" />
    </binding>
</netTcpBinding>

서비스의 메서드는 간단하게 Echo 메서드 하나만 정의하고 다음과 같이 시간 출력을 합니다.

public void Echo()
{
    Console.WriteLine("Echo: " + DateTime.Now.ToString("HH:mm:ss"));

    OperationContext.Current.Channel.Closed += new EventHandler(Channel_Closed);
    OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
}

void Channel_Faulted(object sender, EventArgs e)
{
    Console.WriteLine("Endpoint Faulted: " + DateTime.Now.ToString("HH:mm:ss"));
}

void Channel_Closed(object sender, EventArgs e)
{
    Console.WriteLine("Endpoint Closed: " + DateTime.Now.ToString("HH:mm:ss"));
}

그리고, 클라이언트에서는 바인딩 값에 아래와 같이 시간을 다르게 설정합니다.

netTcpBinding.ReliableSession.Enabled = true;
netTcpBinding.ReliableSession.InactivityTimeout = new TimeSpan(0, 0, 15);

이제, 서비스를 시작하고 클라이언트에서 접속해서 Echo 메서드를 호출하면 다음과 같이 출력되고,

D:\...\bin>ConsoleApplication1.exe
Press any key to exit...
Echo: 02:09:45

클라이언트에서 연결을 닫지 않은 체로 1분 30초가 지나면 서버 측의 ReceiveTimeout에서 지정한 데로 연결이 끊깁니다. 그래서 최종적으로 아래와 같은 출력 결과를 얻을 수 있습니다.

D:\...\ConsoleApplication1\bin>ConsoleApplication1.exe
D:\...\bin>ConsoleApplication1.exe
Press any key to exit...
Echo: 02:09:45
Endpoint Closed: 02:11:15

보시는 것처럼, 위와 같은 결과를 얻기 위해 정확하게 1분 30초가 소요되었습니다. 만약, 책의 내용대로라면 10초만에 연결이 끊겼어야 합니다.

자, 이제 그럼 InactivityTimeout 값을 테스트 해볼까요?

서버 측은 여전히 동일하게 실행하고, 클라이언트는 실행 후 연결을 닫지 않고 "Ctrl + C" 키를 누르거나 혹은 작업관리자를 통해서 강제 종료를 해봅니다. 이번에는 서버와 클라이언트에서 다음과 같이 동작하는 것을 확인할 수 있습니다.

wcf_inactivityTimeout_test_1.png

보시는 것처럼, 클라이언트는 2시 13분 07초에 프로그램을 (비정상) 종료했고 서버는 정확히 InactivityTimeout 값에 지정된 10초 후인 2시 13분 17초에 클라이언트와의 연결을 종료시켰습니다.

자, 그럼 이번엔 반대로 서버를 강제 종료해봅니다.

wcf_inactivityTimeout_test_2.png

서버가 (비정상) 종료한 시간은 2시 16분 03초이고, 클라이언트는 14초 걸려서 연결을 Faulted로 돌렸는데 이 값은 클라이언트 측의 코드를 통해 지정한 15초에 가까운 값입니다.

이 정도면, InactivityTimeout 값에 대해 알아볼 수 있는 실제적인 실험이었다고 볼 수 있겠지요.

테스트 한 프로젝트를 올려두었으니 각자 로컬에서 테스트 해보시고 의심스러운 부분이 있으면 질문 주시기 바랍니다.




제가 위에서 "완벽"하지 못하다고 말씀을 드렸는데요. 위의 테스트 결과대로라면 "완벽"이 아니라 아예 틀렸다고 볼 수 있는데, 왜 그런 단어를 사용했을까요? 궁금하지 않으세요? ^^

InactivityTimeout 값이 매우 헷갈리는 것이 하나 있는데요. 바로 서버 측의 InactivityTimeout 값을 10초 미만으로 조정하면 그 동작이 ReceiveTimeout과 동일하다는 이상한(?) 규칙을 가지고 있다는 점입니다. 즉, 위의 테스트에서 서버 측의 InactivityTimeout 값을 6초로 지정하면, 6초 내에 클라이언트로부터 호출이 없으면 연결이 끊기게 됩니다.

엄청 재미있는 사실은, 클라이언트 측의 InactivityTimeout 값을 10초 미만으로 지정하는 것은 무관하다는 것입니다. 즉 서버 측에 대해서만 10초 미만으로 지정한 경우 ReceiveTimeout처럼 동작합니다.

제 생각에는 아마도 이것은 버그가 아닐까 생각됩니다. 일관성을 위해서 InactivityTimeout 값을 10초 미만으로 지정해도 위의 테스트 결과대로 나와야 합니다. (버그가 아니라면, 아마도 상대편이 죽은 경우 곧바로 자원을 회수해야 하는 특별한 서비스를 위해서 10초 미만으로 지정되는 경우에 한해 그와 같은 동작을 일부러 하도록 한 것이 아닐런지...!)

아무튼, 이런 이상한 현상으로 인해 10초 미만에 대해서는 책에서 설명한 내용이 맞기 때문에 "완벽"하지 않다고 표현한 것입니다.

참고로, .NET 3.5/4.0에서 모두 위와 같이 10초 현상을 겪고 있습니다.




좀 더 부가적인 설명을 드리자면, 위의 InactivityTimeout 실험 데이터는 아주 이상적인 경우에 맞아 떨어진 수치입니다. 왜냐하면, InactivityTimeout 값을 판단하기 위한 서버와 클라이언트의 heart-beat 체크가 정확히 1초마다 이뤄지는 것은 아니기 때문입니다. 확인을 위해 소켓 API를 가로채기 해보면 다음과 같은 결과를 얻을 수 있습니다.

[Srv] Send: 03시 01분 19초
[Cln] Send: 03시 01분 19초
[Srv] Send: 03시 01분 26초
[Cln] Send: 03시 01분 26초
[Srv] Send: 03시 01분 34초
[Cln] Send: 03시 01분 34초
[Srv] Send: 03시 01분 41초
[Cln] Send: 03시 01분 41초

서버(Srv)와 클라이언트(Cln) 모두 서로를 대략 7초 간격으로 확인하는 것을 볼 수 있는데요. 7초 간격은 클라이언트 측의 15초에서 절반으로 나뉜 시간입니다. 만약 18초로 설정하면 9초 간격으로 서로 체크하는 것이 확인됩니다.

테스트에 의하면, heart-beat을 체크하는 주기는 "서버와 클라이언트 중 큰 시간의 절반"과 "서버와 클라이언트 중 작은 값"을 비교해서 낮은 값으로 설정하게 됩니다.

예를 들어, 서버는 15초, 클라이언트는 20초라고 설정되어 있다면 "20 / 2 = 10초 < 15초"이기 때문에 10초로 선택되어 클라이언트, 서버 양측 모두 10초 단위로 서로 살아 있는지를 확인하게 됩니다.

(물론, 이 실험값은 공식적으로 문서화된 것이 아니기 때문에 향후 닷넷 프레임워크에서는 달라질 수 있겠고!)

그래서, 정확하게 상대편에서 종료한 시점부터 7초가 아니고, 체크하는 바로 그 시점에 상대편이 살아있어야 합니다. 즉, 위의 소켓 가로채기에서 보면 클라이언트가 03시 01분 37초에 종료했다면 그로부터 7초 후에 서버가 연결을 끊는 것이 아니고, 03시 01분 41초 - 체크를 하는 그 시점에 확인 후 곧바로 연결을 종료하게 됩니다.

한 가지 더, 위의 heart-beat 주기는 ReceiveTimeout에도 적용됩니다. 이 때문에, ReceiveTimeout 값이 1분 30초이고, InactivityTimeout 설정의 영향으로 heart-beat이 10초로 설정되어 있다면 최대 1분 40초까지 ReceiveTimeout이 적용될 수 있습니다. 즉, 03시 01분 10초에 마지막으로 클라이언트로부터 호출을 받았던 서비스인 경우 heart-beat 확인이 03시 02분 39초에 되었다면 그 시점에는 1분 30초가 지난 것이 아니기 때문에 연결이 끊기지 않다가 다음번 heart-beat 확인이 되는 03시 02분 49초가 되어서야 연결이 끊기게 됩니다.



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

[연관 글]






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

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

비밀번호

댓글 작성자
 



2010-07-31 12시28분
참... 이런 의미에서, 책의 790페이지에 있는 "리스트 9-17"의 코드가 변경되어야 합니다.

CommuicationState 상태는 클라이언트가 비정상 종료(또는 네트워크가 끊겼다거나)했을 때는 알아낼 수 없기 때문입니다. 즉, InactivityTimeout으로 인해 또는 ReceiveTimeout으로 인해 클라이언트 연결이 정리되기 이전에 CommuicationState 상태를 알아내려고 시도하면 Opened로 나오기 때문에 그런 상황에서는 callback.OnTick 메서드 호출이 예외가 발생할 수 있습니다.

즉, 그 부분도 try/catch로 감싸야 한다는!
kevin25

1  2  3  4  5  6  [7]  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13459정성태11/26/20232347닷넷: 2171. .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법파일 다운로드1
13458정성태11/26/20232369닷넷: 2170. .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp)
13457정성태11/25/20232300VS.NET IDE: 187. Visual Studio - 16.9 버전부터 추가된 "Display inline type hints" 옵션
13456정성태11/25/20232615닷넷: 2169. C# - OpenAI를 사용해 PDF 데이터를 대상으로 OpenAI 챗봇 작성 [1]파일 다운로드1
13455정성태11/25/20232499닷넷: 2168. C# - Azure.AI.OpenAI 패키지로 OpenAI 사용파일 다운로드1
13454정성태11/23/20232858닷넷: 2167. C# - Qdrant Vector DB를 이용한 Embedding 벡터 값 보관/조회 (Azure OpenAI) [1]파일 다운로드1
13453정성태11/23/20232342오류 유형: 879. docker desktop 설치 시 "Invalid JSON string. (Exception from HRESULT: 0x83750007)"
13452정성태11/22/20232447닷넷: 2166. C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법파일 다운로드1
13451정성태11/21/20232577닷넷: 2165. C# - Azure OpenAI API를 이용해 ChatGPT처럼 동작하는 콘솔 응용 프로그램 제작파일 다운로드1
13450정성태11/21/20232384닷넷: 2164. C# - Octokit을 이용한 GitHub Issue 검색파일 다운로드1
13449정성태11/21/20232491개발 환경 구성: 688. Azure OpenAI 서비스 신청 방법
13448정성태11/20/20232784닷넷: 2163. .NET 8 - Dynamic PGO를 결합한 성능 향상파일 다운로드1
13447정성태11/16/20232657닷넷: 2162. ASP.NET Core 웹 사이트의 SSL 설정을 코드로 하는 방법
13446정성태11/16/20232608닷넷: 2161. .NET Conf 2023 - Day 1 Blazor 개요 정리
13445정성태11/15/20232919Linux: 62. 리눅스/WSL에서 CA 인증서를 저장하는 방법
13444정성태11/15/20232668닷넷: 2160. C# 12 - Experimental 특성 지원
13443정성태11/14/20232713개발 환경 구성: 687. OpenSSL로 생성한 사용자 인증서를 ASP.NET Core 웹 사이트에 적용하는 방법
13442정성태11/13/20232506개발 환경 구성: 686. 비주얼 스튜디오로 실행한 ASP.NET Core 사이트를 WSL 2 인스턴스에서 https로 접속하는 방법
13441정성태11/12/20232821닷넷: 2159. C# - ASP.NET Core 프로젝트에서 서버 Socket을 직접 생성하는 방법파일 다운로드1
13440정성태11/11/20232442Windows: 253. 소켓 Listen 시 방화벽의 Public/Private 제어 기능이 비활성화된 경우
13439정성태11/10/20233010닷넷: 2158. C# - 소켓 포트를 미리 시스템에 등록/예약해 사용하는 방법(Port Exclusion Ranges)파일 다운로드1
13438정성태11/9/20232597닷넷: 2157. C# - WinRT 기능을 이용해 윈도우에서 실행 중인 Media App 제어
13437정성태11/8/20232777닷넷: 2156. .NET 7 이상의 콘솔 프로그램을 (dockerfile 없이) 로컬 docker에 배포하는 방법
13436정성태11/7/20232998닷넷: 2155. C# - .NET 8 런타임부터 (Reflection 없이) 특성을 이용해 public이 아닌 멤버 호출 가능
13435정성태11/6/20232934닷넷: 2154. C# - 네이티브 자원을 포함한 관리 개체(예: 스레드)의 GC 정리
13434정성태11/1/20232722스크립트: 62. 파이썬 - class의 정적 함수를 동적으로 교체
1  2  3  4  5  6  [7]  8  9  10  11  12  13  14  15  ...