Microsoft MVP성태의 닷넷 이야기
.NET Framework: 447. w3wp.exe AppPool 재생(recycle)하는 방법 정리 [링크 복사], [링크+제목 복사],
조회: 21761
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 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




w3wp.exe AppPool 재생(recycle)하는 방법 정리

사실 w3wp.exe에 대한 재생 방법은 검색해 보면 많은 글이 나옵니다.

Restarting (Recycling) an Application Pool
; http://stackoverflow.com/questions/249927/restarting-recycling-an-application-pool

IIS 7 이상이라면 간단하게 Microsoft.Web.Administration.dll 어셈블리에서 제공하는 ServerManager 객체를 이용할 수 있습니다.

ServerManager svr = new ServerManager(); 
ApplicationPool appPool = svr.ApplicationPools[appPoolId]; // appPoolId == IIS AppPools에 등록된 Application Pool 이름

if (appPool != null)
{
    appPool.Recycle();
}

하지만 IIS 6 이하에서는 Directory Services를 이용해 처리해야 합니다.

string appPoolPath = "IIS://localhost/W3SVC/AppPools/" + appPool;

using (DirectoryEntry appPoolEntry = new DirectoryEntry(appPoolPath))
{
    appPoolEntry.Invoke("Recycle", null);
    appPoolEntry.Close();
}

위의 방법은 IIS 7이상에서도 잘 동작하지만 한 가지 제약이 있습니다. 다음과 같이 "IIS 6 Management Compatibility" 옵션이 켜져있어야 하는데, 생각보다 이 옵션이 켜져 있는 IIS 7(+) 시스템이 많지 않습니다.

recycle_app_pool_1.png

그래서 안전하게 하고 싶다면 IIS 6과 7을 구분해서 각각의 recycle 명령을 호출하도록 바꿔야 합니다.

public static bool IsVistaOrLater
{
    get { return Environment.OSVersion.Version.Major >= 6; }
}

참고로 "HttpRuntime.UnloadAppDomain();" 메서드가 recycle과 유사한 효과를 내긴 합니다. 하지만 이 메서드는 w3wp.exe에 대한 재생을 하는 것은 아니고 내부의 웹 가상 응용 프로그램에 대한 AppDomain만 내렸다가 다시 올립니다. 말로만 듣던 프로세스(EXE) 내부의 Application Domain 격리 효과를 직접 눈으로 볼 수 있는 몇 안되는 사례입니다.




그런데 ServerManager를 사용하면 한 가지 문제가 있습니다. 바로 GAC에 등록된 Microsoft.Web.Administration.dll 어셈블리를 로드하는 방법입니다. 제 컴퓨터에 보니 7.0.0.0 버전과 7.9.0.0 버전이 있는데, 향후 지원을 생각한다면 버전을 고정할 수도 없고... 다소 애매합니다. (사실 쓸데없는 걱정일 수 있습니다. 윈도우 2008부터 2012까지 모두 7.0.0.0 버전이 등록되어 있습니다.)

그래서 제가 찾아봤던 것이 다음의 방법입니다.

Assembly.Load를 이용해 GAC에 등록된 어셈블리를 로드하는 방법
; https://www.sysnet.pe.kr/2/0/1703

이 방법을 이용해서 .NET Reflection과 결합해 다음과 같이 자연스럽게 문제를 해결하는 듯했습니다. ^^

try
{
    string gacAsmName = GetAssemblyPath("Microsoft.Web.Administration"); // 7.9.0.0 버전의 어셈블리 로드
    Assembly asm = Assembly.LoadFrom(gacAsmName);
    if (asm == null)
    {
        return;
    }

    System.Type targetType = asm.GetType("Microsoft.Web.Administration.ServerManager");
    if (targetType == null)
    {
        return;
    }

    object objValue = Activator.CreateInstance(targetType);
    if (objValue == null)
    {
        return;
    }

    PropertyInfo apProps = targetType.GetProperty("ApplicationPools", BindingFlags.Instance | BindingFlags.Public);
    if (apProps == null)
    {
        return;
    }

    object appPoolColl = apProps.GetValue(objValue, null);

    if (appPoolColl == null)
    {
        return;
    }

    object appPoolElem = null;
    foreach (PropertyInfo prop in appPoolColl.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        foreach (ParameterInfo propParam in prop.GetIndexParameters())
        {
            if (propParam.ParameterType.FullName == "System.String")
            {
                appPoolElem = prop.GetValue(appPoolColl, new object[] { appPoolId });
                break;
            }
        }
    }

    if (appPoolElem == null)
    {
        return;
    }

    MethodInfo recycleMethod = appPoolElem.GetType().GetMethod("Recycle");
    recycleMethod.Invoke(appPoolElem, null);
}
catch { }

그런데 IIS + ASP.NET에서 실행했더니 다음과 같은 예외가 발생합니다.

System.Reflection.TargetInvocationException was caught
  Message=Exception has been thrown by the target of an invocation.
  Source=mscorlib
  StackTrace:
       at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
       at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
       at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
       at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)

  InnerException: System.IO.FileNotFoundException
       Message=Filename: redirection.config
Error: Cannot read configuration file

문제 범위를 축소하기 위해 .NET Reflection이 아닌 ServerManager 객체를 곧바로 사용해 보았는데, ApplicationPools 객체를 반환받는 과정에서 오류가 나는 것을 확인했습니다.

ServerManager svr = new ServerManager();

ApplicationPoolCollection apPools = svr.ApplicationPools; // 예외 발생

foreach (var item in appPools)
{
    Console.WriteLine(item.Name);
}

System.IO.FileNotFoundException occurred
  HResult=-2147024894
  Message=Filename: redirection.config
Error: Cannot read configuration file

  Source=""
  StackTrace:
       at Microsoft.Web.Administration.Interop.AppHostWritableAdminManager.GetAdminSection(String bstrSectionName, String bstrSectionPath)
       at Microsoft.Web.Administration.Configuration.GetSectionInternal(ConfigurationSection section, String sectionPath, String locationPath)
  InnerException: 

아니... 뜬금없이 redirection.config 파일을 찾을 수 없다고 나옵니다. 좀 더 문제 범위를 축소하기 위해 IIS + ASP.NET이 아닌 단순 콘솔 프로그램을 만들어 테스트 했는데, 이번에는 문제 없이 잘 동작해서 다음과 같은 출력 결과를 얻었습니다.

Clr4IntegratedAppPool
Clr4ClassicAppPool
Clr2IntegratedAppPool
Clr2ClassicAppPool
UnmanagedClassicAppPool

오호~~~ 그런데 이상하군요. 콘솔에서 잘 동작하는 것은 둘째치고, 출력 결과로 나온 AppPool의 목록이 IIS 서버의 것이 아닌 IIS Express의 것처럼 보였습니다. 확인을 위해 "%USERPROFILE%\My Documents\IISExpress\config\applicationhost.config" 파일을 열고 appPool 목록을 확인하니 일치했습니다.

더욱 재미있는 것은, 7.0.0.0 버전의 Microsoft.Web.Administration.dll 어셈블리를 참조하는 경우 정상적으로 IIS 서버의 것을 가져왔다는 점입니다.

DefaultAppPool
Classic .NET AppPool
.NET v2.0 Classic
.NET v2.0
.NET v4.5 Classic
.NET v4.5

현상을 정리해 보면, 7.9.0.0 버전의 Microsoft.Web.Administration.dll 어셈블리를 IIS에서 직접 호스팅하는 웹 애플리케이션(ASP.NET)의 페이지(.aspx)에서 테스트하면 오류가 발생하고 7.0.0.0 버전의 경우에는 동작을 합니다.

이에 기반해서 검색을 해보니 답이 나오더군요. ^^

Microsoft.Web.Administration.ServerManager looking in wrong directory for IISExpress applicationHost.config
; http://stackoverflow.com/questions/11208270/microsoft-web-administration-servermanager-looking-in-wrong-directory-for-iisexp

아래의 글이 눈에 띄는데요.

How are you trying to get the application pools? Are you using MWH (Microsoft.Web.Administration) APIs?
1.Full IIS ships with Microsoft.Web.Administration.dll (version 7.0.0.0). 
2.IIS Express ships with a different version of Microsoft.Web.Administration.dll (version 7.9.0.0). 

Microsoft.Web.Administration (MWA) version 7.9.0.0 is shipped with IIS Express 7.5 and it is only used by IIS Express. 

한마디로, Microsoft.Web.Administration.dll 어셈블리의 7.0.0.0 버전은 "IIS 서버"를 위한 것이고, 7.9.0.0 버전은 "IIS Express"를 위한 것입니다. 이후에 나온 IIS Express 7.5는 Microsoft.Web.Administration 어셈블리 사용에 대한 동작을 자연스럽게 일치시키기 위해 aspnet.config 파일에 다음과 같이 7.0.0.0을 7.9.0.0으로 사용하도록 bindingRedirect를 지정한다는 설명도 나옵니다.

<dependentAssembly>
  <assemblyIdentity name="Microsoft.Web.Administration"
                    publicKeyToken="31bf3856ad364e35"
                    culture="neutral" />
  <bindingRedirect oldVersion="7.0.0.0"
                   newVersion="7.9.0.0" />
  <codeBase version="7.9.0.0" 
            href="FILE://%FalconBin%/Microsoft.Web.Administration.dll" />
</dependentAssembly>

여기까지 보고 나니 모든 상황이 이해가 되었습니다. 제가 7.9.0.0 버전에 포함된 ServerManager를 테스트했던 웹 사이트는 "Local SYSTEM"에서 구동했기 때문에 "%USERPROFILE%" 경로가 "C:\Windows\system32\config\systemprofile"로 설정되었고, 그 하위의 "\Documents\IISExpress\config" 폴더에서 redirection.config 파일을 찾으니 그와 같은 예외가 발생한 것입니다.

redirection.config 파일은 IIS Express 설정 폴더에는 제공되지만 IIS 서버에는 제공되지 않습니다.

반면, 7.0.0.0은 무조건 IIS 서버를 바라보기 때문에 redirection.config같은 것은 찾지 않으므로 정상 동작하는 것이었고!

결국, 최신 버전의 Microsoft.Web.Administration.dll을 사용하도록 배려한 것이 잘못된 결과를 가져왔습니다. IIS 서버를 위해서는 7.0.0.0 버전으로 고정해서 어셈블리를 사용해야만 하는 제약이 생긴 것입니다. 과연... 마이크로소프트가 향후 이 2가지 기능의 Microsoft.Web.Administration 어셈블리 파일을 어떤 식으로 버전 개정을 해나갈지 기대(?)가 되는군요. ^^





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







[최초 등록일: ]
[최종 수정일: 7/10/2024]

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

비밀번호

댓글 작성자
 




... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12716정성태7/17/202115378개발 환경 구성: 580. msbuild의 Exec Task에 robocopy를 사용하는 방법파일 다운로드1
12715정성태7/17/202122586오류 유형: 736. Windows - MySQL zip 파일 버전의 "mysqld --skip-grant-tables" 실행 시 비정상 종료 [1]
12714정성태7/16/202115937오류 유형: 735. VCRUNTIME140.dll, MSVCP140.dll, VCRUNTIME140.dll, VCRUNTIME140_1.dll이 없어 exe 실행이 안 되는 경우
12713정성태7/16/202117396.NET Framework: 1077. C# - 동기 방식이면서 비동기 규약을 따르게 만드는 Task.FromResult파일 다운로드1
12712정성태7/15/202116282개발 환경 구성: 579. Azure - 리눅스 호스팅의 Site Extension 제작 방법
12711정성태7/15/202116263개발 환경 구성: 578. Azure - Java Web App Service를 위한 Site Extension 제작 방법
12710정성태7/15/202118947개발 환경 구성: 577. MQTT - emqx.io 서비스 소개
12709정성태7/14/202114484Linux: 42. 실행 중인 docker 컨테이너에 대한 구동 시점의 docker run 명령어를 확인하는 방법
12708정성태7/14/202118729Linux: 41. 리눅스 환경에서 디스크 용량 부족 시 원인 분석 방법
12707정성태7/14/202185869오류 유형: 734. MySQL - Authentication method 'caching_sha2_password' not supported by any of the available plugins.
12706정성태7/14/202117066.NET Framework: 1076. C# - AsyncLocal 기능을 CallContext만으로 구현하는 방법 [2]파일 다운로드1
12705정성태7/13/202117496VS.NET IDE: 168. x64 DLL 프로젝트의 컨트롤이 Visual Studio의 Designer에서 보이지 않는 문제 - 두 번째 이야기
12704정성태7/12/202116260개발 환경 구성: 576. Azure VM의 서비스를 Azure Web App Service에서만 접근하도록 NSG 설정을 제한하는 방법
12703정성태7/11/202121612개발 환경 구성: 575. Azure VM에 (ICMP) ping을 허용하는 방법
12702정성태7/11/202117406오류 유형: 733. TaskScheduler에 등록된 wacs.exe의 Let's Encrypt 인증서 업데이트 문제
12701정성태7/9/202116932.NET Framework: 1075. C# - ThreadPool의 스레드는 반환 시 ThreadStatic과 AsyncLocal 값이 초기화 될까요?파일 다운로드1
12700정성태7/8/202117392.NET Framework: 1074. RuntimeType의 메모리 누수? [1]
12699정성태7/8/202115993VS.NET IDE: 167. Visual Studio 디버깅 중 GC Heap 상태를 보여주는 "Show Diagnostic Tools" 메뉴 사용법
12698정성태7/7/202120131오류 유형: 732. Windows 11 업데이트 시 3% 또는 0%에서 다운로드가 멈춘 경우
12697정성태7/7/202115197개발 환경 구성: 574. Windows 11 (Insider Preview) 설치하는 방법
12696정성태7/6/202116176VC++: 146. 운영체제의 스레드 문맥 교환(Context Switch)을 유사하게 구현하는 방법파일 다운로드2
12695정성태7/3/202116220VC++: 145. C 언어의 setjmp/longjmp 기능을 Thread Context를 이용해 유사하게 구현하는 방법파일 다운로드1
12694정성태7/2/202118151Java: 24. Azure - Spring Boot 앱을 Java SE(Embedded Web Server)로 호스팅 시 로그 파일 남기는 방법 [1]
12693정성태6/30/202115114오류 유형: 731. Azure Web App Site Extension - Failed to install web app extension [...]. {1}
12692정성태6/30/202115623디버깅 기술: 180. Azure - Web App의 비정상 종료 시 남겨지는 로그 확인
12691정성태6/30/202115769개발 환경 구성: 573. 테스트 용도이지만 테스트에 적합하지 않은 Azure D1 공유(shared) 요금제
... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...