Microsoft MVP성태의 닷넷 이야기
.NET Framework: 182. WCF의 InactivityTimeout [링크 복사], [링크+제목 복사],
조회: 28437
글쓴 사람
정성태 (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

... [106]  107  108  109  110  111  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11274정성태8/22/201719337.NET Framework: 674. Thread 타입의 Suspend/Resume/Join 사용 관련 예외 처리
11273정성태8/22/201721625오류 유형: 415. 윈도우 업데이트 에러 Error 0x80070643
11272정성태8/21/201724761VS.NET IDE: 120. 비주얼 스튜디오 2017 버전 15.3.1 - C# 7.1 공개 [2]
11271정성태8/19/201719190VS.NET IDE: 119. Visual Studio 2017에서 .NET Core 2.0 프로젝트 환경 구성하는 방법
11270정성태8/17/201730653.NET Framework: 673. C#에서 enum을 boxing 없이 int로 변환하기 [2]
11269정성태8/17/201721446디버깅 기술: 93. windbg - 풀 덤프에서 .NET 스레드의 상태를 알아내는 방법
11268정성태8/14/201721018디버깅 기술: 92. windbg - C# Monitor Lock을 획득하고 있는 스레드 찾는 방법
11267정성태8/10/201725088.NET Framework: 672. 모노 개발 환경
11266정성태8/10/201724893.NET Framework: 671. C# 6.0 이상의 소스 코드를 Visual Studio 설치 없이 명령행에서 컴파일하는 방법
11265정성태8/10/201753130기타: 66. 도서: 시작하세요! C# 7.1 프로그래밍: 기본 문법부터 실전 예제까지 [11]
11264정성태8/9/201724024오류 유형: 414. UWP app을 signtool.exe로 서명 시 0x8007000b 오류 발생
11263정성태8/9/201719505오류 유형: 413. The C# project "..." is targeting ".NETFramework, Version=v4.0", which is not installed on this machine. [3]
11262정성태8/5/201718213오류 유형: 412. windbg - SOS does not support the current target architecture. [3]
11261정성태8/4/201720799디버깅 기술: 91. windbg - 풀 덤프 파일로부터 강력한 이름의 어셈블리 추출 후 사용하는 방법
11260정성태8/3/201718887.NET Framework: 670. C# - 실행 파일로부터 공개키를 추출하는 방법
11259정성태8/2/201718130.NET Framework: 669. 지연 서명된 어셈블리를 sn.exe -Vr 등록 없이 사용하는 방법
11258정성태8/1/201718911.NET Framework: 668. 지연 서명된 DLL과 서명된 DLL의 차이점파일 다운로드1
11257정성태7/31/201719135.NET Framework: 667. bypassTrustedAppStrongNames 옵션 설명파일 다운로드1
11256정성태7/25/201720607디버깅 기술: 90. windbg의 lm 명령으로 보이지 않는 .NET 4.0 ClassLibrary를 명시적으로 로드하는 방법 [1]
11255정성태7/18/201723174디버깅 기술: 89. Win32 Debug CRT Heap Internals의 0xBAADF00D 표시 재현 [1]파일 다운로드3
11254정성태7/17/201719501개발 환경 구성: 322. "Visual Studio Emulator for Android" 에뮬레이터를 "Android Studio"와 함께 쓰는 방법
11253정성태7/17/201719801Math: 21. "Coding the Matrix" 문제 2.5.1 풀이 [1]파일 다운로드1
11252정성태7/13/201718421오류 유형: 411. RTVS 또는 PTVS 실행 시 Could not load type 'Microsoft.VisualStudio.InteractiveWindow.Shell.IVsInteractiveWindowFactory2'
11251정성태7/13/201717079디버깅 기술: 88. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 (2)
11250정성태7/13/201720666디버깅 기술: 87. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 [1]
11249정성태7/12/201718476오류 유형: 410. LoadLibrary("[...].dll") failed - The specified procedure could not be found.
... [106]  107  108  109  110  111  112  113  114  115  116  117  118  119  120  ...