Session State Not Working? Check Your Web Garden!
I recently had a client with an urgent issue with a legacy classic ASP web application - users to his site were getting logged out immediately after logging into the site, or after visiting one or two pages. As with most classic ASP applications, user authentication information was being persisted in session state so this symptom implied that session state was not being maintained.
Session state problems usually arise from one of two causes:
- The user is not properly maintaining the session token. By default, the web server is able to associate a user with a particular session store through the use of a SessionID value that is stored on the user's browser via a cookie. Each time a user makes a request to the server, this cookie is sent along and, lo, the web server knows the user's SessionID. If the user's browser is configured to reject cookies, or if there is a proxy or firewall on the user's side that is stripping out this cookie, then each time the user makes a request to the server it will appear as if it's the user's first request and the server will generate a new SessionID and a new cookie for the user.
- A proxy server or firewall on the web server side is stripping out the cookie. It may be that the user is correctly storing and sending the session cookie, but that the hardware on the web server's side is stripping out the cookie before it can be read by the ASP or ASP.NET engine, causing the server to generate a new SessionID and a new cookie for the user on each request.
To narrow down the cause of the session problem, the first step is to fire up Fiddler and examine the HTTP traffic when visiting the site through a browser. In particular, look at the HTTP headers to see the Set-Cookie values sent from the server to the browser, and the Cookie value sent from the browser to the server. In running these initial tests, I saw that the browser was clearly sending the SessionID cookie to the server, but that the server was responding with a new SessionID cookie on most requests. However, every now and then the server would not send back a cookie at all, as if it had correctly identified the user by their SessioniD. Hrm....
My first inclination was to make sure that there was no hardware of software stripping out the cookie before it reached the server. To test this, create a page that dumps out the server variables to the screen, which include the cookies sent by the browser. (For code to do this in classic ASP, see: Using the Request.ServerVariables Object; for code in ASP.NET, see: Iterating Through ServerVariables in ASP.NET and C#.) If there was something stripping out the session cookie then when visiting this page through a browser I should see two things: in Fiddler I should see that the browser sends a Cookie HTTP Header, but in the output of the web page that lists the server variables I should not see that cookie information in the HTTP_COOKIE server variable.
However, when I ran the above test I did see the cookie values. So the browser was correctly storing and sending the SessionID cookie, the server was receiving it, but for some reason it was creating a new session store on (almost) every request. What was going on?
After nearly two hours of searching the Internet and poking through IIS settings, it finally became clear - the client had configured the IIS Application Pool to use a 20-thread web garden. When you use a web garden you instruct IIS to spin up multiple worker processes to handle incoming requests. The idea is that each worker process can be migrated to a different core on a multi-core CPU and thereby improve scalability, performance, and reliability. (Nicholas Piasecki has an excellent overview of web gardens, their benefits, and common pitfalls over at his blog: On Web Gardens, ASP.NET, and IIS 6.0.) One potential issue with web gardens is that they do not support InProc Session State, as with InProc Session State the session data is stored in the same memory space as each worker process.
What was happening with this client is that when a user visited the site the request would be randomly dispatched to one fo the worker processes in the garden and it would create (and store) session state for that user, sending back a valid SessionID value. When the user went to the next page the browser sent back the SessionID as expected, but there was a 19/20 chance (95%) that the user would be sent to a different worker process in the garden. This different worker process would not have access to the session store in the original worker process and would therefore create a new session store and create a new SessionID cookie.
In ASP.NET you can configure session state to use the OutOfProc setting, which stores session data using a state server rather than at the worker process level; alternatively, you could configure session state to store session in SQL Server. Either configuration could be used to have session state work in a web garden. With classic ASP applications, there are no configuration options for session state storage. Consequently, the only fix was to disable the web garden. And anyway, I think that setting was made accidentally, as I don't know why this client would want to suddently increase the number of worker processes from 1 to 20, as this web application is hosted on a dedicated server and gets at most a few dozen unique users per day.
Long story short, the vast majority of session state problems center around the cookie not being properly saved by the client or by the server not getting the cookie due to some hardware or software stripping it out of the incoming request. But if you've exhausted those avenues, be sure to check the web garden setting for the application pool. It may save you a couple of hours! :-)