2008年12月22日

[ASP.NET] Session 變數在網站(子)目錄更名/刪除後消失 (InProc Mode)

       最近處理了一個 Session 變數會莫名消失的問題,麻煩的地方在於,這個舊系統在.NET 1.1的環境上活的很愉快,但是努力升級到.NET 2.0之後,在某些狀況下 Session 變數就會莫名的消失,導致權控子系統判斷為 Session Timeout,而將 user 自動登出系統。(不會留下任何的Event Log / Error Log)

      遇到這種問題,當然首先要先釐清到底在甚麼狀況下 Session 變數會消失?一開始大家猜測是 ASP.NET 1.1 和 ASP.NET 2.0 的 Session 有不同的運作機制(升級到 .NET 2.0 之後系統就掛了),這個大方向是對的,但最後發現問題的癥結其實跟 Session 的運作機制無關,Session 變數消失只是一個很糟糕的副作用罷了 …

經過反覆的測試,最後終於將範圍縮小至「呼叫一個 WebService (撈外部系統的資料)」後,便會造成 Session 變數消失。此時一位資深的同事注意到,由於外部資料可能常常會有變化,因此系統有一個特殊行為是會將 WebService 回傳的資料寫入一個暫存目錄中,而每次呼叫 WebService 前都會先將整個暫存目錄(及其中的檔案)刪除,而這就是造成 Session 變數消失的關鍵!(跟權控子系統一點關係都沒有)

經過一番 Google,發現這個 Session 變數會消失的問題最早大約在 2005 年就開始被討論,原因是從ASP.NET 2.0 開始新增的「Dynamic Compilation」機制,此玩意兒是指 ASP.NET (2.0+) Runtime 會持續監控應用程式的根目錄(Application Root Folder)以及其下所有的子目錄,出於安全性的考量,當這些目錄被更名刪除時(不管是手動修改或是在 runtime 以程式修改),都會造成 Application Domain Restart!(也就是整個網站瞬間 shutdown、重新 compile 之後,再啟動)

當整個 w3wp process 都被砍掉重練了,Session 自然也不能倖免,直接就被刪除重建,其中的變數更是整個屍骨無存,最後導致 User 自動被登出系統。
      在瞭解了基本觀念之後,又有資深的同事們提出了幾個更深入探討的方向,包括:
      因此接下來又整理出以下各種 scenario,並逐一進行測試(X表示Session會被刪除重建,範例程式下載)。在開始進行測試之前,我的預期是,sessionStateMode 主要的差異在於儲存位置的不同(因此影響 scalability),儲存位置的差異對 ASP.NET Session 物件的運作機制應該沒有影響,因此不管選擇將 Session 的資料存在哪裡,在 Application Domain Restart 的時候,應該全部都會被清空吧!(這樣的話開發人員就不需要特別為了不同的 sessionStateMode 而設計不同的 Session 變數管理方法了)

以下為測試結果:
                                        SessionStateMode
Deployment Style
InProc StateServer SQL Server
Copy Web Site (直接將 Source Code 佈署出去) X
Pre-Compilation (for deployment) X
Pre-Compilation (for deployment and update)
(等於執行Visual Studio 內建的 Publish WebSite)
X

      但是!事情並不是懶惰的我所想的那麼簡單!由以上的測試結果可明確的看出:選擇不同的 sessionStateMode 對 Session 變數會有截然不同的影響!在 sessionStateMode 採用「InProc Mode」時,只要更名(或刪除)網站的子目錄就會造成 Application Domain Restart(新增、刪除檔案則沒有這個副作用),若採用 StateServer/SQL Server 來儲存 Session 資料,則不管 Application Domain 如何反覆的砍掉重練,Session 變數還是可以堅忍不拔的活的很快樂!(其餘的 sessionState 屬性都採用預設值

結論:
     
ASP.NET 2.0+ 的 Dynamic Compilation 行為是無法被改變的,若系統採用「InProc Mode」來儲存 Session 資料,且想要用程式在 runtime 清除暫存資料可以採取以下兩種作法:
  1. 迴圈的方式刪除暫存目錄下的所有檔案,不要把暫存目錄整個刪除,此作法的effort 較小,亦可將暫存目錄設置於網站根目錄下一併管理。
  2. 將暫存目錄的位置移出網站的根目錄,如此便可任意更名/刪除此暫存目錄,缺點為管理上的 effort 較大(多一個虛擬目錄要管理)。
後記:
 
      我不太懂為何 ASP.NET 會設計成,當採用 StateServer/SQLServer Mode 時就不清空 Session 資料。這樣就表示不同的 sessionStateMode 所代表的不僅僅是 Session 資料的儲存位置不同,連帶的 ASP.NET 對於 Session 資料的管理策略也是不同的,感覺對於開發人員來說這會是一個額外的負擔。

      從效能的觀點來看,若系統中有某個功能一旦被執行就會造成 Application Domain Restart,那絕對不是甚麼好事。但是在開發階段只有採用「InProc Mode」或者已經事先深入瞭解 Dynamic Compilation 的運作機制,才能避免效能低落的程式寫法。若是經驗不足,或是不小心採用了 StateServer/SQLServer Mode,那麼系統只會默默的把 Application Domain 砍掉重練,不會丟出任何警告訊息,也不會寫下 Event Log,而Session 變數的內容看起來都會是很正常的!(權控子系統都可正常運作)那麼效能也只能默默的變差了 …

      為了補足 ASP.NET 預設對於 Application Shutdown 事件不會寫 log 的缺憾,我在Microsoft Developer Division 的 VP - Scott Guthrie 的 blog 找到了一個方法,在Global.asax的「Application_End」事件中將Application Shutdown的資訊寫入到Event Log,如下圖所示,如此便可明確看出 Application Shutdown 的原因是因為「dir change or directory rename」:
ApplicationShutDown

Reference:
  1. ASP.NET Application Life Cycle Overview for IIS 5.0 & 6.0
    (See Community Content #1)
  2. Logging ASP.NET Application Shutdown Events
  3. ASP.NET Deployment Overview
  4. sessionState Element

7 則留言:

坎尼 提到...

喔喔喔 感謝大大解答我多年的疑惑
一直沒對這東西作比較深入的研究
難怪我之前的案子動不動就 session out
客戶一直抱怨這個 XDDD 不過最近好像又沒事了?

好文我頂 (三花聚頂

Unknown 提到...

最近客戶又沒有跟你們抱怨就對了 ...
那應該是認命了吧 XDD 就跟股票認賠一樣 ..

尼克 提到...

"StateServer/SQLServer Mode 時就不清空 Session 資料" -> 會不會是考量到有多個 IIS Server 做 Cluster 共用 Session 時, 其中一個 Application Domain Restart 就清掉 Session 不太合理的問題 XD

Unknown 提到...

Cluster 我就很不熟了@@ 但是印象中負責 Cluster 機制的軟體主要的任務就是記錄外來的 request 揪~~竟是交由內部的哪一台 Server 處理(類似 NAT?),因此若該 Server 上的 IIS 有 AP Domain Restart,應該把 Session 清掉也不會不合理? 每個 User 的 Session 應該還是被保存在內部的某一台 Server 上吧 .. ?

Anyway ... 我個人認為既然 AP Domain 都 Restart 了,不管採用何種 Session Mode,Session 還是一起死一死比較好 XD 這樣開發的時候才有機會檢視揪 ~~~ 竟是什麼原因導致 AP Domain Restart ~~~~

尼克 提到...

如果是 Development mode-> 同感,這樣的確可以方便找出問題


but 如果是 Production mode, Session 一起死一死會影響 User 的"奇摩子", 個人覺得這樣就不太好, 這個前提是環境是 IIS Cluster, SessionStateMod 是 State Server 或 SQL Server, 假如是InProc 的話就一起死一死吧 XD

鐵皮屋 提到...

感謝大大分享
剛好解決了我最近Session的問題
最終的作法,我是將會異動的資料夾移到IIS根目錄下,而非應用程式本身,就解決了這樣的問題。
我想,這樣也剛好可以避免掉一些檔案上傳的攻擊。不曉得我的見解有沒有誤~

Unknown 提到...

Hi 鐵皮屋:

您說的「檔案上傳的攻擊」是說藉由上傳新的檔案,導致 session 被清空,造成 IIS 重建 session 的 loading / 使用者被登出,這樣的情境嗎?如果是這樣的話應該就可以避免,但如果是「利用上傳大量檔案來消耗 server 的資源」這種攻擊,應該是要額外實作一些偵測的機制來處理才行~

Google Spreadsheet 裡用規則運算式

最近因為工作關係,遇到要用 Google Form 及 Google Sheet 所以研究了 Google Sheet 裡的一些 function 怎麼用 首先,分享一下如何在 Google Sheet 裡用規則運算 :D