Microsoft MVP성태의 닷넷 이야기
닷넷: 2274. IIS - (프로세스 종료 없는) AppDomain Recycle [링크 복사], [링크+제목 복사],
조회: 7340
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 7개 있습니다.)
.NET Framework: 174. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법
; https://www.sysnet.pe.kr/2/0/841

.NET Framework: 447. w3wp.exe AppPool 재생(recycle)하는 방법 정리
; https://www.sysnet.pe.kr/2/0/1704

개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
; https://www.sysnet.pe.kr/2/0/1774

.NET Framework: 643. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/11145

개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
; https://www.sysnet.pe.kr/2/0/13514

닷넷: 2196. IIS - AppPool의 "Disable Overlapped Recycle" 옵션의 부작용
; https://www.sysnet.pe.kr/2/0/13516

닷넷: 2274. IIS - (프로세스 종료 없는) AppDomain Recycle
; https://www.sysnet.pe.kr/2/0/13672




IIS - (프로세스 종료 없는) AppDomain Recycle

보통 IIS Recycling은 특정 조건을 만족하면 프로세스(EXE) 자체가 재실행되는 방식으로 이뤄집니다. 그런데, 상황에 따라서는 AppDomain 단위로도 수행이 되는데요,

Application Pool vs Application Domain Recycling in Asp.net?
; https://stackoverflow.com/questions/60105130/application-pool-vs-application-domain-recycling-in-asp-net

대략 다음의 조건에서 그렇다고 하니,

  1. Modification to web.config or Global.asax
  2. Change to the contents of the application's bin directory
  3. Change to the physical path of the virtual directory
  4. Deletion of the subdirectory of the application
  5. The number of re-compilations (aspx, ascx or asax) exceeds the limit specified by the setting in machine.config or web.config (default of 15)

실제로 테스트를 해볼까요? ^^

이를 위해 .NET Framework의 기본 Web Application 프로젝트를 하나 만들고 Global.asax.cs 파일에 다음과 같은 코드를 추가합니다.

using System;
using System.Diagnostics;
using System.Web;
using System.Web.Optimization;
using System.Web.Routing;

namespace WebApplication1
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("===== Global.asax - Application_Start =====");
            // Code that runs on application startup
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }

        void Application_End(object sender, EventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("===== Global.asax - Application_End =====");
        }
    }
}

이후, IIS 10이 설치된 Windows Server 2022에서 호스팅하면 최초 페이지 방문 시 다음과 같은 로그가 찍히는 것을 (DebugView를 이용해) 확인할 수 있습니다.

[7696] ===== Global.asax - Application_Start ===== 

그 상태에서 IIS Recycle을 하면 End 메시지가 뜨고,

[7696] ===== Global.asax - Application_End ===== 

다시 웹 페이지를 방문하면 Process ID가 바뀐 Start 로그가 나옵니다.

[7932] ===== Global.asax - Application_Start ===== 




자, 그럼 첫 번째 항목(Modification to web.config or Global.asax)을 테스트해 볼까요? ^^

우선 web.config에서 적당한 내용을 수정해 볼 텐데요, 음... Antlr3.Runtime의 oldVersion을 0.0.0.0에서 1.0.0.0으로 바꾸고 저장해 봅니다.

...[생략]...
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />
        <bindingRedirect oldVersion="1.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
      </dependentAssembly>
      ...[생략]...
    </assemblyBinding>
  </runtime>
...[생략]...

저장하는 동시에, 디버그 화면에는 End 메시지가 뜨고, 웹 페이지를 방문하면 Process ID가 바뀌지 않은 채로 Start 메시지가 찍힙니다.

[7932] ===== Global.asax - Application_End ===== 
[7932] ===== Global.asax - Application_Start ===== 

마찬가지로 Global.asax 파일 역시, 마땅히 수정할 것이 없으니 그냥 공백 문자 하나 추가한 다음 저장하면 역시나 Process ID가 바뀌지 않는 Recycle이 발생합니다.

[7932] ===== Global.asax - Application_End ===== 
[7932] ===== Global.asax - Application_Start ===== 




그다음 조건은 /bin 디렉터리의 변경입니다. 일례로 "test.txt" 파일을 만들면 그와 동시에 End가 뜨고 웹 페이지를 방문하면 Start가 나옵니다. 역시나 이번에도 Process ID는 그대로입니다.

[7932] ===== Global.asax - Application_End ===== 
[7932] ===== Global.asax - Application_Start ===== 

3번째 조건인 virtual directory의 경로를 바꾸는 것은, 당연히 recycle이 될 테니 넘어가고...

4번째 조건인 subdirectory 삭제는, 현재 Visual Studio에서 빌드한 그대로 IIS에 올렸으니, /obj/Debug 디렉터리를 삭제해 보면 될 텐데요, 음... Recycle 메시지가 안 나옵니다. /obj 디렉터리를 삭제해도 마찬가지고, 임의로 /test 디렉터를 만들고 삭제해도 아무런 변화가 없습니다.

사용하지 않는 App_Data, App_Start, Properties 디렉터리를 삭제해도 마찬가지인데, 각종 css, map 파일을 가지고 있던 "Content" 디렉터리나 "Scripts\WebForms\MSAjax" 디렉터리를 삭제했더니 Recycle이 발생했습니다. 아마도, 사용 중인 자원을 담은 디렉터리가 없어지면 Recycle이 발생하는 것으로 보입니다.

마지막 조건의 경우, numRecompilesBeforeAppRestart를 초과하는 aspx, ascx, asax 파일에 대한 수정으로 (기본 값이 15회라고 알려진) 재컴파일 횟수가 넘어가면 Recycle이 발생한다는 건데요, 테스트를 위해 "d.aspx"라는 파일을 추가한 다음, 메모장으로 열어 아무 내용이나 넣고 15번을 수정하면 Recycle이 발생합니다.

이때 중요한 것은, 내용만 바꿔서 저장하는 것뿐만 아니라, 그 바뀐 내용을 ASP.NET 엔진이 컴파일할 수 있도록 요청해야 한다는 점입니다. 그랬을 때에만 14번째 요청 후, 15번째 파일 내용을 변경해 저장하는 순간 Recycle이 발생합니다.




참고로, Application Pool에 설정할 수 있는 "Generate Recycle Event Log Entry"의 경우,

iis_apppool_recycle_event_log_1.png

AppDomain Recycle 시에는 적용되지 않습니다. 해당 항목을 보더라도,

  • Application Pool Configuration Changed (default: No) - Event is logged when the application pool recycles due to a change in its configuration
  • ISAPI Reported Unhealthy (default: No) - Event is logged because an ISAPI extension has reported itself as unhealthy
  • Manual Recycle (default: No) - Event is logged when the application pool has been manually recycled
  • Private Memory Limit Exceeded (default: Yes) - Event is logged when the application pool recycles after exceeding its private memory limit
  • Regular Time Interval (default: Yes) - Event is logged when the application pool recycles on its scheduled interval
  • Request Limit Exceeded (default: Yes) - Event is logged when the application pool recycles after exceeding its request limit
  • Specific Time (default: Yes) - Event is logged when the application pool recycles at a scheduled time
  • Virtual Memory Limit Exceeded (default: Yes) - Event is logged when the application pool recycles after exceeding its virtual memory limits

AppDomain Recycle의 조건에 해당하는 것이 없습니다. 즉, 이번 글에서 테스트한 Recycle의 경우에는 이벤트 로그에 전혀 남지 않습니다.

해당 원인을 알고 싶다면, 예전에 설명한 대로,

작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/11145#code

Application_End에 HttpRuntime 인스턴스의 Reflection을 통해 _shutDownMessage를 가져오면 됩니다. 오랜만에 .NET Framework 4.8 환경에서 테스트했더니 여전히 해당 소스코드는 잘 동작합니다. ^^

약간 callstack의 깊이는 달라졌지만 web.config을 변경하는 경우 다음과 같은 메시지를 가져올 수 있고,

_shutDownMessage=CONFIG change 
HostingEnvironment initiated shutdown 
HostingEnvironment caused shutdown 
 
_shutDownStack=   at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) 
   at System.Environment.get_StackTrace() 
   at System.Web.Hosting.HostingEnvironment.InitiateShutdownInternal() 
   at System.Web.Hosting.HostingEnvironment.InitiateShutdownWithoutDemand() 
   at System.Web.HttpRuntime.ShutdownAppDomain(String stackTrace) 
   at System.Web.Configuration.HttpConfigurationSystem.OnConfigurationChanged(Object sender, InternalConfigEventArgs e) 
   at System.Configuration.Internal.InternalConfigRoot.OnConfigChanged(InternalConfigEventArgs e) 
   at System.Configuration.BaseConfigurationRecord.OnStreamChanged(String streamname) 
   at System.Web.DirectoryMonitor.FireNotifications() 
   at System.Web.Util.WorkItem.CallCallbackWithAssert(WorkItemCallback callback) 
   at System.Web.Util.WorkItem.OnQueueUserWorkItemCompletion(Object state) 
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 
   at System.Threading.ThreadPoolWorkQueue.Dispatch() 

또는, web.config의 numRecompilesBeforeAppRestart 옵션을 3으로 설정한 다음,

<compilation debug="true" targetFramework="4.8" numRecompilesBeforeAppRestart="3" />

aspx 파일을 3번 수정해 발생한 Recycle에서는 이런 메시지가 나옵니다.

_shutDownMessage=Recompilation limit of 3 reached 
HostingEnvironment initiated shutdown 
HostingEnvironment caused shutdown 
 
_shutDownStack=   at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) 
   at System.Environment.get_StackTrace() 
   at System.Web.Hosting.HostingEnvironment.InitiateShutdownInternal() 
   at System.Web.Hosting.HostingEnvironment.InitiateShutdownWithoutDemand() 
   at System.Web.HttpRuntime.ShutdownAppDomain(String stackTrace) 
   at System.Web.Compilation.DiskBuildResultCache.ShutdownCallBack(Object state) 
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 
   at System.Threading.ThreadPoolWorkQueue.Dispatch() 

물론, 코드를 좀 더 추가해 EventLog.WriteEntry를 호출한다면 얼마든지 EventLog에도 로그를 남길 수 있습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/6/2024]

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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  69  70  [71]  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12162정성태2/26/202021852디버깅 기술: 166. C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생 [6]파일 다운로드2
12161정성태2/26/202017900오류 유형: 597. manifest - The value "x64" of attribute "processorArchitecture" in element "assemblyIdentity" is invalid.
12160정성태2/26/202018254개발 환경 구성: 469. Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest
12159정성태2/26/202015358오류 유형: 596. Visual Studio - The project needs to include ATL support
12158정성태2/25/202017943디버깅 기술: 165. C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생파일 다운로드1
12157정성태2/25/202017980디버깅 기술: 164. C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법파일 다운로드1
12156정성태2/25/202016556오류 유형: 595. LINK : warning LNK4098: defaultlib 'nafxcw.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
12155정성태2/25/202016013오류 유형: 594. Warning NU1701 - This package may not be fully compatible with your project
12154정성태2/25/202015391오류 유형: 593. warning LNK4070: /OUT:... directive in .EXP differs from output filename
12153정성태2/23/202019511.NET Framework: 898. Trampoline을 이용한 후킹의 한계파일 다운로드1
12152정성태2/23/202018571.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)파일 다운로드1
12151정성태2/22/202019359.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)파일 다운로드1
12150정성태2/21/202019613.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 [1]파일 다운로드1
12149정성태2/20/202018418.NET Framework: 894. eBEST C# XingAPI 래퍼 - 연속 조회 처리 방법 [1]
12148정성태2/19/202020946디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법 [1]
12147정성태2/19/202018624디버깅 기술: 162. x86/x64의 기계어 코드 최대 길이
12146정성태2/18/202019374.NET Framework: 893. eBEST C# XingAPI 래퍼 - 로그인 처리파일 다운로드1
12145정성태2/18/202019234.NET Framework: 892. eBEST C# XingAPI 래퍼 - Sqlite 지원 추가파일 다운로드1
12144정성태2/13/202019431.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기파일 다운로드1
12143정성태2/13/202015995.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64파일 다운로드1
12142정성태2/12/202018355.NET Framework: 889. C# 코드로 접근하는 MethodDesc, MethodTable파일 다운로드1
12141정성태2/10/202017382.NET Framework: 888. C# - ASP.NET Core 웹 응용 프로그램의 출력 가로채기 [2]파일 다운로드1
12140정성태2/10/202017933.NET Framework: 887. C# - ASP.NET 웹 응용 프로그램의 출력 가로채기파일 다운로드1
12139정성태2/9/202019188.NET Framework: 886. C# - Console 응용 프로그램에서 UI 스레드 구현 방법
12138정성태2/9/202023213.NET Framework: 885. C# - 닷넷 응용 프로그램에서 SQLite 사용 [6]파일 다운로드1
12137정성태2/9/202016435오류 유형: 592. [AhnLab] 경고 - 디버거 실행을 탐지했습니다.
... 61  62  63  64  65  66  67  68  69  70  [71]  72  73  74  75  ...