Tag Archives: IIS

Debugging Story: Bug v IIS recykluje AppDomains

Posledních 14 dní mě zaměstnal problém, jehož vyřešení mi zabralo více času a energie než kdykoliv předtím. Nedá mi to, abych se o situaci nepodělil, protože pokud někoho potká něco podobného, nemá snad šanci než se dogooglit. Uvádím zde velmi zkrácenou verzi, celkem jsem za tu dobu otestoval devět různých hypotéz příčiny, analyzoval 40 GB memory-dumpů, prohlédl tisíce řádek zdrojového kódu System.Web.HttpRuntime a související infrastruktury i tisíce řádek kódu aplikace samotné.

Symptom – počáteční situace

U jednoho z našich zákazníků jsem byl povolán k prozkoumání aplikace, která se chovala tak nějak divně. Dokud zabírala relativně málo paměti (~ 1.5 GB w3wp.exe), běžela. Ve špičkách však začínala brát paměti více paměti a zpomalovat se až tuhnout. Zákazník to prozatím řešil nastavením recyklace aplikačního poolu při dosažení limitu 1.85 GB + naplánovanými recyklacemi ráno a večer. Přes den se tak pool recykloval několikrát. V poolu byla jediná website.

Diagnostika – základní => upřesněný Symptom – AppDomains

První, co si v takové situaci obvykle dělám, je analýza memory-dumpu odebraného z produkce ve její slabší chvilce. Pro analýzu používám WinDbg (+SOS.dll + NETEXT.dll + atp.) a paralelně si hned pouštím i základní reporty DebugDiag. Obvykle se danými symptomy projevuje nějaký typ memory-leaku, takže ve WinDbg člověk automaticky začíná přes !DumpHeap -stat, resp. !EEHeap.

V tomto případě však hlavní anomálii ukázal DebugDiag a potvrdil !wdomain (z NETEXT.dll), popř. !DumpDomain (z SOS.dll) – v procesu nebyly jen obvyklé domény (System, Shared, Default, 1x pro WebSite ála /LM/W3SVC/4/ROOT-1-130737467133911036), ale web-site byla ve worker-procesu reprezentována několika doménami:

System Domain
Shared Domain
DefaultDomain
/LM/W3SVC/4/ROOT-41-130737467133911036 
/LM/W3SVC/4/ROOT-72-130737499283949318 
/LM/W3SVC/4/ROOT-73-130737499297231139 
/LM/W3SVC/4/ROOT-57-130737482790332688 
/LM/W3SVC/4/ROOT-63-130737490107147967 
/LM/W3SVC/4/ROOT-71-130737497444302282 

Každá taková aplikační doména website pak měla samozřejmě i svůj HttpRuntime (včetně cache), svoje statické fieldy, všechno. Paměť worker-procesu tedy byla plněna samostatnými instancemi celé aplikace, spoustou jejich infrastrukturních věcí a na samotný běh aplikace zbývalo paměti málo.

Diagnostika – recyklace AppDomains (základní)

První moje myšlenky se ubíraly směrem, že něco přivozuje neplánovanou recyklaci aplikačních domén (nezaměňujme s recyklací aplikačního poolu!). Připomeňme si základní situace, které recyklaci aplikační domény způsobují:

    1. Machine.Config, Web.Config or Global.asax are modified
    2. The bin directory or its contents is modified
    3. The number of re-compilations (aspx, ascx or asax) exceeds the limit specified by the <compilation numRecompilesBeforeAppRestart=/> setting in machine.config or web.config (by default this is set to 15)
    4. The physical path of the virtual directory is modified
    5. The CAS policy is modified
    6. The web service is restarted
    7. (.NET 2.0 only) Application Sub-Directories are deleted

V úvahu zde připadá cokoliv z uvedeného, seznam navíc nemusí být nutně vyčerpávající, striktní výčet podmínek se v dokumentaci dost těžko hledá.

V mém případě jeden z dalších odebraných memory-dumpů ukazoval zajímavé údaje. Část z nadbytečných aplikačních domén se ukázala být v běžícím shutdownu (HttpRuntime._shutdownInProgress = 1), přičemž jako důvod bylo udáváno ConfigurationChange (HttpRuntime._shutDownReason = 3 = ApplicationShutdownReason.ConfigurationChange). Call-stack ukončování pak byl

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.Configuration.WebConfigurationHostFileChange.OnFileChanged(Object sender, FileChangeEvent e) 
at System.Web.DirectoryMonitor.FireNotifications() 
at System.Web.Util.WorkItem.CallCallbackWithAssert(WorkItemCallback callback) 
at System.Web.Util.WorkItem.OnQueueUserWorkItemCompletion(Object state) 
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(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() 
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

V HttpRuntime._shutDownMessage pak bylo něco jako

CONFIG change 
HostingEnvironment initiated shutdown 
HostingEnvironment caused shutdown 
CONFIG change 
CONFIG change 
CONFIG change 
CONFIG change 
CONFIG change 
CONFIG change 
CONFIG change 
CONFIG change 
CONFIG change

…i když trochu podezřele, ale poměrně jasně se tedy ukazuje, že se aktivovala detekce změn na disku, kterou ASP.NET vyhodnotil jako změnu konfigurace, a proto spouští novou aplikační doménu v nové konfiguraci a starou doménu ukončuje (že se domény trochu množí ponechme prozatím stranou – lze to vysvětlit například tím, že zatímco nová doména se spouští ihned, stará aplikační doména se ukončuje až po doběhnutí všech rozpracovaných requestů a úklidu).

Instance FileChangeEvent, ze kterých by se dalo vyčíst jméno souboru, již bohužel garbage collector uklidil, k dalším detailům jsem se v tu chvíli nedopracoval.

Diagnostika – CONFIG change

V tu chvíli se zdálo být jasné, že něco hrabe na disku na konfigurační soubory aplikace (antivir? backup? aplikace?). Abych se dobral, co se tam děje, použil jsem Process Monitor ze SysInternals (lze snadno spustit přímo z http://live.sysinternals.com) a abych to dokázal nějak dobře hledat v čase, zapnul jsem si monitorování Application Lifecycle Events do Event Logu – do globálního web.configu (lze i do aplikačního) jsem přidal do sekce <healthMonitoring><rules>

<add name="Application Lifetime Events Default" eventName="Application Lifetime Events"
                    provider="EventLogProvider" profile="Default" minInstances="1"
                    maxLimit="Infinite" minInterval="00:01:00" custom="" />

Výsledkem této zdlouhavé a vyčerpávající diagnostiky (pořád jsem hledat ten golden-bullet řádek ProcMonu, kde se mi to celé zápisem sestřeluje) bylo několik zjištění:

  • do .config souborů, složky /bin, Temporary ASP.NET Files, atp. s největší pravděpodobností nic nezapisuje, v podstatě krom ASP.NET samotného ani nečte,
  • aplikace si do několika podsložek poměrně často sama zapisuje (konkrétní složky nejsou podstatné, dojme tomu App_Data) + dokonce křížem zapisuje několik aplikací do svých podsložek navzájem – jedná se však o různá logování (TXT soubory), PDF reporty, atp. – nic, co by mělo být konfigurační změnou a restarty způsobovat.
  • aplikace se přes den restartuje hodně často, klidně každých 15 minut, někdy se spustí i několik instancí během minuty, v noci naopak aplikace „žije poklidně“

Pokus – FCN Mode = Disabled

Abych se v tu chvíli posunul někam dále, rozhodnul jsem se pro vypnutí File Change Notifications ASP.NET, tj. vypnout detekci změn souborů na disku. Věděl jsem, že tím nenajdu příčinu, ale chtěl jsem si alespoň potvrdit hypotézu, že problémem jsou změny souborů, a že mám hledat tímto směrem dál.

File Change Notifications lze vypnout dvěma způsoby:

  • od .NET 4.5 lze použít nepříliš dokumentovanou application-level volbu <httpRuntime fcnMode=“Disabled“ /> do web.configu
  • použitím server-wide přepínače FCNMode v registrech
    HKLM\SOFTWARE\Wow6432Node\Microsoft\ASP.NET\FCNMode = 1 (DWORD)
    HKLM\SOFTWARE\Microsoft\ASP.NET\FCNMode = 1 (DWORD)
    

K mému velkému překvapení restarty aplikace neustaly. Stále se v Event Logu objevovaly události

    • Application is starting.
    • Application is shutting down. Reason: Configuration changed.

WTF?!

Dalším memory-dumpem jsem si potvrdil, že FileMonitor instance na HttpRuntime skutečně zmizely, a že HttpRuntime._fcnMode je opravdu 1 (Disabled).

Nový symptom – IIS configuration change

Podrobnějším zkoumáním memory-dumpu se však ukázalo, že něco se přecijenom změnilo. Přestože zůstal u končících appdomains HttpRuntime._shutDownReason = ConfigurationChanged, v _shutDownMessage se najednou změnil obsah na

IIS configuration change
HostingEnvironment initiated shutdown
HostingEnvironment caused shutdownn

a HttpRuntime._shutDownCallStack se změnil na něco jako

at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
at System.Environment.get_StackTrace()
at System.Web.Hosting.HostingEnvironment.InitiateShutdownInternal()
at System.Web.Hosting.PipelineRuntime.StopProcessing()

Diagnostika – IIS configuration change

…mno, začíná jít do tuhýho, metoda StopProcessing() vypadá nějak takhle:

        [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)]
        public void StopProcessing() {
            Debug.Trace("PipelineDomain", "StopProcessing with stack = " + Environment.StackTrace
                        + " for AppId= " +  s_thisAppDomainsIsapiAppId);
 
            if (!HostingEnvironment.StopListeningWasCalled && !HostingEnvironment.ShutdownInitiated) {
                // If GL_STOP_LISTENING wasn't triggered, the reset is likely due to a configuration change.
                HttpRuntime.SetShutdownReason(ApplicationShutdownReason.ConfigurationChange, "IIS configuration change");
            }
 
            s_StopProcessingCalled = true;
            // inititate shutdown and
            // require the native callback for Stop
            HostingEnvironment.InitiateShutdownWithoutDemand();
        }

…jak je vidět, tak na řádku 6-9 se zapisuje reason „IIS configuration change“, ale z komentáře by se dalo usuzovat, že existují i další nepravděpodobné důvody, pro které se IIS rozhodne app-domain sestřelit a dojde na StopProcessing().

Process Monitor opět nic neukázal, nevypadá to, že by něco do konfiguračních souborů hrabalo.

Došlo tedy na další úroveň znásilnění produkčního serveru – pomocí DebugDiag (proč by se člověk dnes drbal s WinDbg či ADPLUS, když to jde tak snadno) jsem si nastavil breakpointy na vytváření nové aplikační domény (mscorlib.dll!System.AppDomain.InternalCreateDomain) a ukončování aplikační domény (tuším chytáno na System.Web.dll!System.Web.HttpRuntime.RaiseShutdownWebEventOnce) a odebral memory-dumpy přímo z krizového okamžiku. Případné pokusníky upozorňuji, že toto je již velmi invazivní metoda, která v podstatě způsobí naattachování debuggeru na proces, nastavení breakpointů, čekání na jejich hit, což samotné aplikaci výkonově degraduje a dostane do další křeče.

Díky častému restartování aplikačních domén se mi podařilo získat poměrně rychle memory-dumpy.

Thread spouštění nové aplikační domény vypadal takto:

	mscorlib_ni!System.AppDomain.InternalCreateDomain(System.String, System.Security.Policy.Evidence, System.AppDomainSetup) 
	mscorlib_ni!System.AppDomain.CreateDomain(System.String, System.Security.Policy.Evidence, System.AppDomainSetup)+28 
	mscorlib_ni!System.AppDomain.CreateDomain(System.String, System.Security.Policy.Evidence, System.AppDomainSetup)+28 
	mscorlib_ni!System.AppDomain.CreateDomain(System.String, System.Security.Policy.Evidence, System.AppDomainSetup, System.Security.PermissionSet, System.Security.Policy.StrongName[])+8c 
	mscorlib_ni!System.AppDomain.CreateDomain(System.String, System.Security.Policy.Evidence, System.AppDomainSetup, System.Security.PermissionSet, System.Security.Policy.StrongName[])+8c 
	System_Web_ni!System.Web.Hosting.ApplicationManager.CreateAppDomainWithHostingEnvironment(System.String, System.Web.Hosting.IApplicationHost, System.Web.Hosting.HostingEnvironmentParameters)+f10 
	System_Web_ni!System.Web.Hosting.ApplicationManager.CreateAppDomainWithHostingEnvironment(System.String, System.Web.Hosting.IApplicationHost, System.Web.Hosting.HostingEnvironmentParameters)+f10 
	System_Web_ni!System.Web.Hosting.ApplicationManager.CreateAppDomainWithHostingEnvironmentAndReportErrors(System.String, System.Web.Hosting.IApplicationHost, System.Web.Hosting.HostingEnvironmentParameters)+25 
	System_Web_ni!System.Web.Hosting.ApplicationManager.CreateAppDomainWithHostingEnvironmentAndReportErrors(System.String, System.Web.Hosting.IApplicationHost, System.Web.Hosting.HostingEnvironmentParameters)+25 
	System_Web_ni!System.Web.Hosting.ApplicationManager.GetAppDomainWithHostingEnvironment(System.String, System.Web.Hosting.IApplicationHost, System.Web.Hosting.HostingEnvironmentParameters)+6c 
	System_Web_ni!System.Web.Hosting.ApplicationManager.GetAppDomainWithHostingEnvironment(System.String, System.Web.Hosting.IApplicationHost, System.Web.Hosting.HostingEnvironmentParameters)+6c 
	System_Web_ni!System.Web.Hosting.ApplicationManager.CreateObjectInternal(System.String, System.Type, System.Web.Hosting.IApplicationHost, Boolean, System.Web.Hosting.HostingEnvironmentParameters)+4d 
	System_Web_ni!System.Web.Hosting.ApplicationManager.CreateObjectInternal(System.String, System.Type, System.Web.Hosting.IApplicationHost, Boolean, System.Web.Hosting.HostingEnvironmentParameters)+4d 
	System_Web_ni!System.Web.Hosting.ProcessHost.StartApplication(System.String, System.String, System.Object ByRef)+18f 
	System_Web_ni!System.Web.Hosting.ProcessHost.StartApplication(System.String, System.String, System.Object ByRef)+18f 
	System_Web_ni!DomainNeutralILStubClass.IL_STUB_COMtoCLR(IntPtr, IntPtr, IntPtr)+7d 
	System_Web_ni!DomainNeutralILStubClass.IL_STUB_COMtoCLR(IntPtr, IntPtr, IntPtr)+7d 
	clr!COMToCLRDispatchHelper+6b 
	clr!COMToCLRWorker+3e6 
	0x01b1a062 
	webengine4!W3_MGD_APP_CONTEXT::CreateAppDomain+92 
	[[ComMethodFrame]] 
	webengine4!W3_MGD_APP_CONTEXT::OnApplicationResolveModules+90 
	webengine4!CMgdEngGlobalModule::OnGlobalApplicationResolveModules+51 
	iiscore!VIRTUAL_MODULE::GlobalDoWork+da 
	iiscore!W3_SERVER::GlobalNotify+e5 
	iiscore!W3_APPLICATION::ResolveModules+22 
	iiscore!W3_CONTEXT::SetupStateMachinePhase2+350 
	iiscore!W3_CONTEXT::SetupStateMachine+263 
	iiscore!W3_CONTEXT_BASE::StartNotificationLoop+71 
	iiscore!W3_MAIN_CONTEXT::OnNewRequest+a8 
	w3dt!UL_NATIVE_REQUEST::DoWork+79 
	w3dt!UL_RECEIVE_CONTEXT::DoWork+1b 
	w3dt!WP_CONTEXT::OnCompletion+22 
	w3tp!THREAD_POOL_DATA::ThreadPoolThread+a8 
	w3tp!THREAD_POOL_DATA::ThreadPoolThread+2b 
	w3tp!THREAD_MANAGER::ThreadManagerThread+4d 
	kernel32!BaseThreadInitThunk+24 
	ntdll!__RtlUserThreadStart+2f 
	ntdll!_RtlUserThreadStart+1b 

Thread ukončování staré aplikační domény takto:

		ntdll!NtReleaseSemaphore+c 
		KERNELBASE!ReleaseSemaphore+14 
		clr!CLRSemaphore::Release+2f 
		clr!ThreadpoolMgr::UnfairSemaphore::Release+d2 
		clr!ThreadpoolMgr::MaybeAddWorkingWorker+1c6 
		clr!ManagedPerAppDomainTPCount::SetAppDomainRequestsActive+2f 
		mscorlib_ni!DomainNeutralILStubClass.IL_STUB_PInvoke()+33 
		[[InlinedCallFrame]] 
		mscorlib_ni!System.Threading.ThreadPoolWorkQueue.EnsureThreadRequested()+4c 
		mscorlib_ni!System.Threading.ThreadPoolWorkQueue.Enqueue(System.Threading.IThreadPoolWorkItem, Boolean)+42 
		mscorlib_ni!System.Threading.ThreadPool.QueueUserWorkItemHelper(System.Threading.WaitCallback, System.Object, System.Threading.StackCrawlMark ByRef, Boolean)+bd 
		mscorlib_ni!System.Threading.ThreadPool.QueueUserWorkItemHelper(System.Threading.WaitCallback, System.Object, System.Threading.StackCrawlMark ByRef, Boolean)+bd 
		mscorlib_ni!System.Threading.ThreadPool.QueueUserWorkItem(System.Threading.WaitCallback)+1d 
		mscorlib_ni!System.Threading.ThreadPool.QueueUserWorkItem(System.Threading.WaitCallback)+1d 
		System_Web_ni!System.Web.Hosting.HostingEnvironment.InitiateShutdownInternal()+12e 
		System_Web_ni!System.Web.Hosting.HostingEnvironment.InitiateShutdownWithoutDemand()+21 
		System_Web_ni!System.Web.Hosting.PipelineRuntime.StopProcessing()+3f 
		System_Web_ni!DomainNeutralILStubClass.IL_STUB_COMtoCLR()+12 
		clr!COMToCLRDispatchHelper+28 
		clr!COMToCLRWorkerBodyWithADTransition+217 
		[[ContextTransitionFrame]] 
		clr!COMToCLRWorker+61 
		0x01b1a062 
		webengine4!W3_MGD_APP_CONTEXT::EndApplicationInstance+54 
		[[ComMethodFrame]] 
		webengine4!W3_MGD_APP_CONTEXT::OnApplicationEnd+3b 
		webengine4!CMgdEngGlobalModule::OnGlobalApplicationStop+1f 
		iiscore!VIRTUAL_MODULE::GlobalDoWork+16a 
		iiscore!W3_SERVER::GlobalNotify+e5 
		iiscore!W3_APPLICATION::DereferenceApplication+2e 
		iiscore!W3_METADATA::DereferenceMetadata+17 
		iiscore!W3_MAIN_CONTEXT::OnIoCompletion+86 
		w3dt!UL_NATIVE_REQUEST::DoWork+a0 
		w3dt!UL_SEND_CONTEXT::DoWork+1b 
		w3dt!WP_CONTEXT::OnCompletion+22 
		w3tp!THREAD_POOL_DATA::ThreadPoolThread+a8 
		w3tp!THREAD_POOL_DATA::ThreadPoolThread+2b 
		w3tp!THREAD_MANAGER::ThreadManagerThread+4d 
		kernel32!BaseThreadInitThunk+24 
		ntdll!__RtlUserThreadStart+2f 
		ntdll!_RtlUserThreadStart+1b 

V obou případech je vidět, že operace iniciuje nativní část W3WP, tedy zjednodušeně řečeno IIS. Zdrojové kódy této části bohužel u sebe nemám a poprvé se mi stalo, že by se mi hodily. Jako MVP bych si o ně mohli požádat, ale je to procedura poměrně zdlouhavá. Každopádně z call-stacku ukončování by se dalo více-méně usuzovat (např. dle funkce OnIoCompletion), že se jedná opět o callback nějaké file-change-monitoru, tedy vážně nějaké dosud neobjevené drbání na disku.

Cause

…už to začíná být neúměrně dlouhé, tak to zkrátím. Problém jsem konzultoval prostřednictvím uzavřeného mailing-listu s ostatními ASP.NET/IIS MVPs + s Microsoftem a díky podrobným nasbíraným symptomům jsme se poměrně rychle dostali k jádru věci.

Problém způsobuje hotfix KB3007507, který se na předmětný Windows 2012 R2 server nainstaloval jako součást KB3000850 přes Windows Update (někdy v půlce prosince 2014).

Projevuje se tak, že pokud ve složce webové aplikace (+ podsložkách) probíhá intenzivnější disková aktivita, IIS to neuhodnotí a odpálí AppDomain, jako by se jednalo o změnu konfigurace. Nepotvrzené vysvětlení je, že se přehltí change-monitory, ostatně podobné problémy mělo IIS už dříve:

I když již existuje pre-release podoba hotfixu KB3052480, která by měla problém řešit, stačí odinstalovat KB3000850, restartovat server a problém je vyřešen.

Kdyby se někdo do podobné situace dostal, mám i jednoduchou WebSite, která problém dokáže relativně spolehlivě reprodukovat.

HTTP Redirect nastavovaný z IIS Manageru umí zavařit

Dneska ráno nás v práci uvítal critical ticket v HelpDesku, jednomu ze zákazníků „nešel web a všechno, co po přihlášení zkusil, vedlo na homepage“. Hned se nám spojilo, že to je web, na který kolega nastavoval přesměrování HTTP requestů na HTTPS a zkoušel si různé způsoby, jak to udělat.

Jeden z testů, které udělal, byl přes <httpRedirect> (HTTP Redirection). Pak od něj ale upustil a redirect nastavený přes IIS Manager zase vypnul.

Souhra okolností chtěla, aby toto zapnutí a vypnutí způsobilo poměrně velký problém.

Po IIS Manageru zůstalo v hlavním ~/web.configu webu toto:

<system.webServer>
		<httpRedirect enabled="false" destination="https://chester.xerox.cz" httpResponseStatus="Permanent" />
</system.webServer>

To by samo o sobě nevadilo. Peklo však nastalo v okamžiku, kdy se to potkalo s web.config soubory v podsložkách, v nichž byla různá specifická přesměrování od vývojářů:

<system.webServer>
		<httpRedirect enabled="true" exactDestination="true">
			<add wildcard="/Old-URL.aspx" destination="New-URL.aspx"/>
			...
		</httpRedirect>
</system.webServer>

Když se to celé sečetlo, tak ve všech takových podsložkách se reaktivoval disablovaný redirect z rootového web.configu a veškeré requesty na resources v dané složce přesměrovával.

…další důvod proč nemám rád, když IIS Manager modifikuje web.config. Hlavním je ten, že při nasazování chci web.config přepisovat vždy celý novou verzí a jakékoliv production-specific volby z něj mít vyextrahovány třeba pomocí atributu configSource.

Chyba 404 při přístupu na ASP.NET aplikaci v Classic managed pipeline modu na II7

Symptom

Pokud přepneme application pool do Classic managed pipeline mode a dostáváme podivnou chybu 404, jedná se velmi pravděpodobně o chybu

HTTP Error 404.2 - Not Found
The page you are requesting cannot be served because of the ISAPI and CGI Restriction list settings on the Web server.
Error Code 0x800704ec

Tuto podrobnější chybu se můžeme dozvědět, pokud přístup uděláme přímo ze browseru běžícím na serveru (což bohužel ne vždy lze, viz třeba problémy s lokální integrovanou authentizací).

Cause

Každopádně problém je v tom, že nejsou v nastavení IIS povoleny ISAPI extenze pro ASP.NET 4.0.

Action

  1. IIS Manager
  2. server level
  3. ISAPI and CGI restrictions
  4. u zakázaných ASP.NET 4.0 extenzí, které potřebujeme, nastavit Allow

IIS: Jaký w3wp.exe patří k jakému AppPoolu?

Na IIS6 existoval ve složce System32\ jednoduchý skript k vypsání seznamu worker-procesů a jejich příslušnosti k jednotlivým application poolům IIS:

iisapp.vbs

Na IIS7 už tento skript není, ekvivaltentní příkaz z System32\inetsrv\ však je

appcmd list wp

image

Pozor na IIS Application pool: Maximum Worker Processes (Web Garden)

V nastavení application poolu na IIS je zastrčeno jedno nastavení s výchozí hodnotou 1, které je poměrně zrádné, pokud ho někdo změní a neví, co to přesně znamená.

Jde o volbu „Maximum Worker Processes“ a neznalé svádí tuto hodnotu z jedničky zvětšit na vyšší číslo.

Co to však ve skutečnosti znamená – jde o to, že IIS pustí pro Application Pool více instancí Worker Processu (w3wp.exe), které jsou navzájem izolované. Vznikne tak více nezávislých instancí aplikací běžících na daném Application Poolu a celé se to začne chovat jako Web Garden (= lokální virtuální Web Farm na jednom serveru). Následky jsou pak prakticky stejné, jako běh aplikace na webové famě – instance aplikace mezi sebou nezdílí kontext (Application, Cache, in-proc Session, atp.).

Protože každý Váš request pak může vyřdit jiná instance aplikace, projevy mohou být např. následující:

  • ztrácí se Vám Session, pokud používáte výchozí InProc session (hodnoty Session jsou uloženy na jiném „serveru“, než který vyřizuje aktuální request),
  • nefunguje dobře cachování (každý „server“ si udržuje vlastní cache a musí se tedy naplnit tolik cachí, kolik „serverů“ Vám běží),
  • ztrácí se Vám hodnoty Application contextu (opět stejný důvod),
  • nefungují Vám grafy DevXpress (protože si obrázek ukládají do lokálního contextu v okamžiku renderování stránky s grafem a požadavek browseru na obrázek grafu se dostane na jiný „server“),
  • atp. atp.

Osobně v běžném provozu nenacházím scénář, kde by Maximum Worker Processes mělo být nastaveno jinak než na 1. Dokážu si představit jedině, že někdo chce záměrně testovat chování své aplikace na webové farmě, nebo nějaké obskurní důvody s unmanaged componentami, nebo řešením nedostatečné thread-safety aplikace.

Css styly a obrázky nefungují v IIS7

Instalace IIS7 ve Windows Vista obsahuje více možností než tomu bylo dříve. V dobré víře jsem si zvolil nástroje pro správu a podporu ASP.NET v minimalistické podobě.

Díky podpoře nainstalovené podpoře ASP.NET fungovaly webové aplikace, ovšem statický obsah (jako jsou právě obrázky nebo soubory kaskádových stylů) prohlížeč neobdržel. Při zadání libovolné URL na statický obsah (například i adresa k obrázku, který ani na disku není) prohlížeč nezobrazí nic (i zdrojový kód stránky je prázdný).

Aby IIS7 poskytoval statický obsah, je nutné mezi instalovanými komponentami IIS zvolit i „Statický obsah“.

IIS6: Volba výchozích regionálních nastavení pro ASP stránky

Můžeme se dočkat nepříjemností, pokud v ASP stránkách spoléháme na konkrétní regional-settings. Přesuneme-li aplikaci na server jiné lokalizace, nepomůže nám totiž ani nastavení Control Panel ~ Regional Settings (Ovládací panely ~ Regionální nastavení).

Je v podstatě několik možností, jak správný region vnutit, nicméně na IIS5.1+ mi jako nejlepší přijdezměna hodnoty AspLCID v IIS metabázi:

\\LM\W3SVC\AspLCID

Pro české regionální nastavení je správná hodnota 1029, hodnoty ostatních uvádí Microsoftí List of Locale ID (LCID).

Související zdroje informací:

FTP: Nastavení práv na složku pro příchozí soubory – /incoming

Poměrně často dostávám dotaz, jak nastavit práva na FTP složku pro příchozí soubory – složku /incoming (i když já doporučuji jiný název, viz dále).

V podstatě chceme dosáhnout toho, aby anonymní uživatel:

  • mohl do složky libovolně přidávat soubory,
  • mohl zobrazovat seznam souborů ve složce – chceme mu dát možnost, aby si ověřil, že jeho soubor byl úspěšně nahrán,
  • nemohl ze složky soubory mazat – nechceme přeci, aby nám kdokoli mohl smazat soubory určené pro nás,
  • nemohl ze složky číst – nejenom, že nechceme, aby nám někdo cizí mohl číst soubory určené pro nás, ale navíc bychom si povolením čtení připravili hodně krušné chvíle – roboti totiž pravidelně procházejí FTP servery a přímo vyhledávají takovéto složky, kde je povolen zápis a čtení – v případě úspěchu se velmi rychle stanete uložištěm pro warez, videa a jiné nechtěné soubory.

Právě z posledního uvedeného důvodu, kdy se roboti zaměřují převážně na složku /incoming a ostatní složky pro zrychlení ignorují, doporučuji pojmenovat složku pro příchozí soubory jinak. V českém prostředí například /prichozi.

A teď jak na práva:

  • práva budeme nastavovat účtu anonymního uživatele (obvykle IUSR_SERVER),
  • práva aplikujeme přímo na složku pro příchozí soubory – /incoming, /prichozi,
  • nevystačíme si se standardní záložkou Vlastnosti ~ Zabezpečení, ale musíme dát „Upřesnit“,
  • vytvoříme pro anonymního uživatele pravidlo, kde Použít pro nastavíme na Tato složka a podsložky (standardně je to totiž Tato složka, podsložky a soubory) apovolíme vše mimo
    • Full Control – Úplné řízení,
    • Delete Subfolders and Files – Odstraňovat podsložky a soubory,
    • Delete – Odstraňovat,
    • Change Permissions – Měnit oprávnění
    • a Take Ownership – Přebírat vlastnictví.
  • ještě můžeme zvážit práva Zapisovat atributy, popř. Zapisovat rozšířené atributy, ale to už celkem není nic proti ničemu.
  • musíme si dát také pozor, jestli se nám z nadřazené složky nedědí nějaká další práva, ale nebývá to obvyklé (spíše by se dalo považovat za chybu, kdyby anonymní uživatel měl na kořenovou složku FTP serveru nějaká práva, která by se dědila na podsložky).

Tím, že pravidlo aplikujeme pouze na objekty typu složka, nikoliv na soubory, dosáhneme právě toho, že uživatel sice všechno uvidí, ale nebude moct číst jednotlivé soubory.

Také si dejte pozor, jestli Vám nevznikají práva k souborům nějakým jiným děděním, klasickou chybou jsou třeba práva pro CREATOR OWNER.