Sinds enige tijd heb ik een wederkerend probleem in een ASP.NET v1.1 applicatie framework dat ik heb geschreven. De applicaties werken perfect, alles doet het naar behoren en iedereen is happy, totdat op een gegeven moment de chaostheorie inslaat gewoon terwijl iemand een pagina opvraagt. Op dat moment is de applicatie permanent borked totdat aspnet_wp.exe wordt afgeschoten of IIS zelf overnieuw opgestart wordt.
Dus wat is het probleem? Laten we eerst even kijken naar de korte samenvatting van de originele code. M'n framework biedt zelf een class die afleidt van System.Web.HttpApplication, en waar uiteindelijke applicaties hun Global class weer van afleiden. Van de 'tussenclass' heb ik de Init() overloaded:
Hierin plaats ik dus 3 belangrijke events op application startup, waarvan de Session_Start event het belangrijkste is: net zoals ik in de HttpApplication tussenclass veel framework-related informatie opsla, heb ik voor iedere session ook een 'schaduwobject' waarin ik eenmalig securitysettings van een centrale server ophaal en deze voor de sessieduratie laat bestaan. Enige relevante code uit dit event:
De CreateSession boeit niet echt, de constructor van WebSession wel:
Instance is simpelweg de singleton-accessor van m'n tussenclass die het correcte type retourneert, en SessionVariableName is een constant string. En hier gaat het dus fout: waar deze code continu perfect werkt, gaat het ooit, binnen 1 uur tot 14 dagen bij stevig gebruik, in een toestand komen waar iedere nieuwe sessie permanent kapotknalt op de "app.Session"-regel hierboven met "Session State not available in this context". Dit is sowieso al onzin, omdat zelfs de callstack op dat moment helder aangeeft dat de event uit de SessionStateHandler komt en Session dus wel degelijk beschikbaar is (zie ook bijv. deze tutorial die exact hetzelfde doet).
In ieder geval gingen we het probleem onderzoeken. Eerst heb ik een workaround geprobeerd die heel de afhankelijkheid van SessionState dropte. Dit was een hoop creatieve code die op basis van Request.Cookies en HttpApplication.Cache simpelweg het effect van Sessions emuleerde. Werkte perfect, totdat binnen X tijd ineens de applicatie borked was en iedere nieuwe sessie permanent kapotknalt met "Request not available in this context" zodra ik de cookies uitlees.
Intussen heel Internet en de Knowledge Base uitgegraven, maar niemand leek een vergelijkbaar probleem te hebben. Sterker nog, Internet staat vol met voorbeelden waarin mensen in Session_Start events de Request en Session objecten aanspreken, wat hier dus absoluut niet wil. Wel vond ik een hoop referenties dat men het ook in AcquireRequestState event deed. Dus heb ik dat ook geprobeerd... ik heb alle SessionStart code 'genuked' en het verplaatst naar het event wat ik al had:
Helaas bleek deze code nog instabieler, nu gaat het ding binnen ~15 minuten intensief gebruik per definitie op z'n gat, deze keer met "Session State is not available in this context" doodleuk binnen de callstack van de AcquireRequestState event.
De vraag dus: kent iemand dit gedrag? Weet iemand een oorzaak/workaround? Maak ik vreemde aannames die niet zouden mogen? Kijk ik wellicht over een multithreading/synchronization issue heen?
Dus wat is het probleem? Laten we eerst even kijken naar de korte samenvatting van de originele code. M'n framework biedt zelf een class die afleidt van System.Web.HttpApplication, en waar uiteindelijke applicaties hun Global class weer van afleiden. Van de 'tussenclass' heb ik de Init() overloaded:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public override void Init() { SessionStateModule module; // Invoke base implementation first base.Init(); // Register event handlers module = (SessionStateModule)Modules["Session"]; module.Start += new EventHandler(Application_SessionStart); AcquireRequestState += new EventHandler(Application_AcquireRequestState); Error += new EventHandler(Application_Error); ... } |
Hierin plaats ik dus 3 belangrijke events op application startup, waarvan de Session_Start event het belangrijkste is: net zoals ik in de HttpApplication tussenclass veel framework-related informatie opsla, heb ik voor iedere session ook een 'schaduwobject' waarin ik eenmalig securitysettings van een centrale server ophaal en deze voor de sessieduratie laat bestaan. Enige relevante code uit dit event:
C#:
1
2
| // Create new session variable (no need to store, it'll be done static) new WebSession(securityManager.CreateSession(User.Identity.Name)); |
De CreateSession boeit niet echt, de constructor van WebSession wel:
C#:
1
2
3
4
5
6
7
8
9
| internal WebSession(SecuritySession securitySession) { Application app = Application.Instance; // Set static reference app.Session[sessionVariableName] = this; ... } |
Instance is simpelweg de singleton-accessor van m'n tussenclass die het correcte type retourneert, en SessionVariableName is een constant string. En hier gaat het dus fout: waar deze code continu perfect werkt, gaat het ooit, binnen 1 uur tot 14 dagen bij stevig gebruik, in een toestand komen waar iedere nieuwe sessie permanent kapotknalt op de "app.Session"-regel hierboven met "Session State not available in this context". Dit is sowieso al onzin, omdat zelfs de callstack op dat moment helder aangeeft dat de event uit de SessionStateHandler komt en Session dus wel degelijk beschikbaar is (zie ook bijv. deze tutorial die exact hetzelfde doet).
In ieder geval gingen we het probleem onderzoeken. Eerst heb ik een workaround geprobeerd die heel de afhankelijkheid van SessionState dropte. Dit was een hoop creatieve code die op basis van Request.Cookies en HttpApplication.Cache simpelweg het effect van Sessions emuleerde. Werkte perfect, totdat binnen X tijd ineens de applicatie borked was en iedere nieuwe sessie permanent kapotknalt met "Request not available in this context" zodra ik de cookies uitlees.
Intussen heel Internet en de Knowledge Base uitgegraven, maar niemand leek een vergelijkbaar probleem te hebben. Sterker nog, Internet staat vol met voorbeelden waarin mensen in Session_Start events de Request en Session objecten aanspreken, wat hier dus absoluut niet wil. Wel vond ik een hoop referenties dat men het ook in AcquireRequestState event deed. Dus heb ik dat ook geprobeerd... ik heb alle SessionStart code 'genuked' en het verplaatst naar het event wat ik al had:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
| private void Application_AcquireRequestState(object sender, EventArgs e) { ... // Is this the first request of a session? if(Session.IsNewSession) { // Create new session variable (no need to store, it'll be done static) new WebSession(securityManager.CreateSession(User.Identity.Name)); } ... } |
Helaas bleek deze code nog instabieler, nu gaat het ding binnen ~15 minuten intensief gebruik per definitie op z'n gat, deze keer met "Session State is not available in this context" doodleuk binnen de callstack van de AcquireRequestState event.
De vraag dus: kent iemand dit gedrag? Weet iemand een oorzaak/workaround? Maak ik vreemde aannames die niet zouden mogen? Kijk ik wellicht over een multithreading/synchronization issue heen?