ColdFusion Session Management And Asynchronous Page Requests
I haven't use a frameset in a long time; I don't even remember all the HTML markup for it (as you can see in the following demo, I use minimal markup). But, in my other post on session management and OnSessionStart(), Stefan mentioned that he had experienced similar problems when using a frameset. This got me thinking about session management with multiple requests. Unfortunately, we often think about user experiences as a synchronous stream of user requests; but, in reality, users might have multiple tabs open, or find other interesting ways to make asynchronous requests (ex. double-clicking a web link [yes - many people still treat web links like desktop links]).
That said, I was curious to test what happens when the user-request that triggers the OnSessionStart() method takes longer than a subsequent user-request that uses SESSION information. I think we can all guess what happens, but I wanted to see it for myself. To test this, I created a small Application.cfc
<cfcomponent
output="false"
hint="I define application settings and event handlers.">
<!--- Define application settings. --->
<cfset THIS.Name = "FramesetSessionTest" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 10, 0 ) />
<cfset THIS.SessionManagement = true />
<cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 0, 20 ) />
<cffunction
name="OnSessionStart"
access="public"
returntype="void"
output="false"
hint="I run when a session needs to be initialized.">
<!---
Sleep the current thread for a few seconds. This is
simply to mimic *anything* that might accidentally
slow the page down.
--->
<cfthread
action="sleep"
duration="#(5 * 1000)#"
/>
<!--- Initialize the session scope. --->
<cfset SESSION.User = {
ID = 0,
Name = "Guest User"
} />
<!--- Return out. --->
<cfreturn />
</cffunction>
</cfcomponent>
As you can see, I am using ColdFusion 8's CFThread tag in the beginning of my OnSessionStart() event method to mimic a slow request. My timeout is greatly exaggerated, but any kind of lengthy initialization method that might hit a database and load some CFCs could easily be paused for a noticeable amount of time from a machine processing standpoint.
Then, I set up a simple frameset to simulate multiple requests. NOTE: The frameset has to be an HTM page or the frameset itself would trigger the OnSessionStart() event method before the frames were even requested:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html>
<head>
<title>Frameset Demo With Sessions</title>
</head>
<frameset rows="200,*">
<frame src="frame.cfm" />
<frame src="frame.cfm" />
</frameset>
</html>
Then, I created a single frame that would be used in both the top and bottom frames (for simplicity's sake):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Frame</title>
</head>
<body>
<cfoutput>
<p>
Hello #SESSION.User.Name#, I am one of the frames.
</p>
</cfoutput>
</body>
</html>
As you can see, each frame will output the name of the SESSION.User value.
When I run the above code, it does what I suspected it would do - the top frame triggered the OnSessionStart() and took a few seconds to finish. The bottom frame, did not trigger the OnSessionStart() event (as this event was already triggered) and executed before the top frame was done processing. This resulted in a ColdFusion variable reference error in the bottom frame:
When I stop and think about this, it suddenly becomes clear why ColdFusion creates the SESSION before it calls the OnSessionStart(). Yesterday, in my blog comments, I stated that it seemed odd that OnSessionStart() didn't return a boolean to determine whether or not the session should actually be created. But, when you realize that multiple requests share the same session, you realize that this is not possible. If it were, then you might have multiple asynchronous requests triggering the OnSessionStart() event method thinking that no one else had triggered it yet.
You could get around this behavior if ColdFusion added a boolean return to OnSessionStart() AND single-threaded it, but that might be a totally restructuring of how ColdFusion handles session management.
But that's neither here nor there. Rather than thinking about what could be, can we figure out a way to get around this? The biggest problem here is that we actually have to possible reasons that the session is not yet initialized:
- The session initialization is taking a while.
- The session initialization broke.
If it's taking a while, we could wait for it to finish. But, if it broke and we try to wait for it to finish, we would take a thread out of commission for good (which is very very bad).
So, how can we make sure that a request waits for session initialization, but won't wait forever if the session initialization breaks? The best solution that I could think of was a SESSION scope CFLock. If we can get both the request and the session to use the same scope lock, then I believe they will have to naturally wait for each other to complete:
<cfcomponent
output="false"
hint="I define application settings and event handlers.">
<!--- Define application settings. --->
<cfset THIS.Name = "FramesetSessionTest" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 10, 0 ) />
<cfset THIS.SessionManagement = true />
<cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 0, 20 ) />
<cffunction
name="OnSessionStart"
access="public"
returntype="void"
output="false"
hint="I run when a session needs to be initialized.">
<!---
To make sure that no request actually fires before
the session is done intializing, we need to run it
in a scope lock that is specific to this user. And,
to make sure that it's user specific, we will need
to use the session scope.
--->
<cflock
scope="SESSION"
type="exclusive"
timeout="30">
<!---
Sleep the current thread for a few seconds.
This is simply to mimic *anything* that might
accidentally slow the page down.
--->
<cfthread
action="sleep"
duration="#(5 * 1000)#"
/>
<!--- Initialize the session scope. --->
<cfset SESSION.User = {
ID = 0,
Name = "Guest User"
} />
<!--- Return out. --->
<cfreturn />
</cflock>
</cffunction>
<cffunction
name="OnRequestStart"
access="public"
returntype="boolean"
output="false"
hint="I run when a request needs to be initialized.">
<!---
To make sure that this request ONLY runs once the
session scope has been initialized, perform a
meaningless scope lock on the session. The only
purpose here is to create a pause if need-be.
--->
<cflock
scope="SESSION"
type="exclusive"
timeout="30">
<!---
No logic here - the CFLock is the only logic
that we needed to invoke.
--->
</cflock>
<!--- Return out. --->
<cfreturn true />
</cffunction>
</cfcomponent>
Because we want each page request to pause, we had to add the OnRequestStart() event method as a place to have our second CFLock instance. Now, every request made by the user will have to wait if the OnSessionStart() event method is still running. This will single-thread a user's requests to the CFLock in the OnRequestStart() method; but, since it's a scope lock on SESSION, one user's request will not affect another user's requests. And, since there is no logic in the OnRequestStart() CFLock, the bottle neck will be completely non-noticeable.
Now, with the two CFLocks in place, when we run the code, we get the following:
As you can see, by using the SESSION scope CFLock, all asynchronous requests now wait for the session initialization to complete. And, the beauty of this is that if the SESSION initialization fails for some reason, the OnRequestStart() will not wait indefinitely.
Looks like I'll be putting this solution into all of my future web applications. Thanks Stefan for proving some most excellent inspiration!
Want to use code from this post? Check out the license.
Reader Comments
Well this is very interesting. From what I know, both onAppStart and onSessionStart are supposed to be single threaded. I would have assumed that the second frame request would have been delayed waiting for the first one to complete. I think this is a bug. If you can't count on onSessionStart being single threaded then it kinda defeats the purpose of the method (imho).
Yep, I call this a bug. The docs says:
You never need to lock the Session scope to set its variables using this method.
@Ben,
Just a thought, but isn't it best practice to always lock reads to the session scope anyways? I remember reading at a couple places that unless you're in a single threaded method (OnSessionStart et al.) you need to lock your access to the session scope to prevent misfirings.
My 2 cents.
@Ray,
Does that explicitly mean we don't need to lock subsequent calls from multi-threaded methods?
Maybe the flu has me down but right now I'm having trouble wrapping my head around this :P.
Great explanation, and nice looking solution. I've had some issues with AJAX requests causing this same problem. I'll be playing with this solution as well.
Thanks,
Dan
FYI, I've posted this to an internal Adobe list asking for clarification. At minimum it is a doc bug. I'm also going to run the test myself. I refuse to believe it. ;)
@Ray,
Hmmm, that might just mean that OnSessionStart() will never be called multiple times concurrently for the same user. I am not sure if it means that session management itself is single threaded.
And, as in my previous post on session initialization fail:
www.bennadel.com/index.cfm?dax=blog:1535.view
... we see that OnSessionStart() is really a secondary effect of session management. But, that feels weird to me as well, so that could be a bug possibly?
I will submit to /go/wish and see what they think. Thanks!
@Francois,
I think CFLock'ing all SESSION reads is a hold-over from older versions of ColdFusion. I personally believe that locking is overused.
@Daniel,
Glad to help.
@Ray,
Ok sounds cool.
I ran a test myself and I don't think you have to use the lock in the onSessionStart for this to work. It is locked by default as the docs specify. However it is not single threaded so other requests will run, but only the first request calls the onSessionStart. Curious though if the first request is not complete ie no cookies set then how does cf know the second request is from the same client/session? Need more tests.
@Lee,
That's interesting re: the automatic SESSION lock on OnSessionStart(). I will have to look into that. As far as cookies, those are all created before the OnSessionStart() event method even gets invoked (which is why OnSesssionStart() has access to them).
@Ben: You can never be too careful with locking session reads and especially writes.
I inherited a high volume CF 7 app where UserA would log in and sometimes get UserB's session. UserC would be logged in correctly, go through a few pages/processes and suddenly be in UserD's session.
Once we put a series of Session Facades in place to manage all session reads and writes under cflocks, the problems went away.
Even with CF8, I'd do the same thing.
@Ben,
I agree with less locking = better. Normally I'd just do something like this in OnRequestStart:
<cflock scope="session" type="exclusive" timeout="5">
<cfset request.session = duplicate( session ) />
</cflock>
And I'd have something in OnRequestEnd to save any altered values back to the session scope.
Anyways, can't wait to see where this is going :).
@Ben
I don't think locking is overused. Since you are getting these errors, wouldn't that lead you to believe that you are underusing locking?
I think you may be starting out with your session already set because you already have the cftoken and cfid cookies. If the server session has timed out then onSessionStart will only be called by the first request. If you start without your cookies set you will see something different. Open up chrome in incognito mode(really handy) or clear your cookies so your starting with no client state. Add cfdump var="#session#" to your frame page. Both pages will call onSessionStart and each will have different cfid/cftoken values. Then if you refresh they will be the same because your cookies are now set.
@Adrian,
I cannot see how one user could possibly get another user's session. ColdFusion should manage each user's session completely independently. I am not sure if this is something that might happen in CFMX6 and before, but I have no idea of the mechanics that would allow it to happen in MX7 or CF8? Did you track down the actual problem?
@Francois,
I have nothing against that kind of approach. Although, if you use Duplicate(), I believe that ColdFusion 8 will now make actual copies of CFCs rather than just copying references - this could lead to very unexpected behavior across page calls.
@Anthony,
There are times when locking is definitely needed (such as when accessing shared files). But, most people operate on a fear-based locking strategy. Locking is not a silver-bullet; and, using locking in many many cases doesn't actually prevent the problems that developers think they are going to solve.
To be honest, most people operate under the VERY OUTDATED mentality that scope access needs to be threaded as this was a bug in previous versions of ColdFusion. This is no longer an issue, but many people have not yet updated their view on locking.
Of course, I don't want to make blanket statements - if you have any specific examples you want to discuss, I'd be more than happy to explain why I do or do not think locking is necessary for that use case.
@Lee,
Interesting. I will try that in a little bit. Thanks.
@Ben
The example in your blog post is a good example of when locking might be necessary. But instead of placing the lock around your session variables, you place it in the onRequestStart. If the framed cfm wrote to the session variables then displayed, you would have race conditions in which a frame could have a different value in session than expected.
If the following were in the frames, you might get the wrong numbers.
<cfset SESSION.number = Rand() />
<cfoutput>
<p>#SESSION.number#</p>
</cfoutput>
<cfthread action="sleep" duration="#(5 * 1000)#" />
<cfoutput>
<p>#SESSION.number#</p>
</cfoutput>
Maybe it is just my old school way of thinking, but I think that anything that can go bad, will go bad. If all my session and application scoped variables get locks around them, I know I will get the expected values.
I almost didn't believe my eyes :) Do you have j2ee sessions enabled?
I'm thinking about the cookie that identifies the session.
When a session starts, the cookie is returned to the client, to be passed on the next request. Or at least, that happens when the j2ee session (jsessionid) is created. Maybe not for CF sessions(?).
So what about if you close all browsers and start a brand newie? Doesn't that mean each frame is concurrently trying for a new session? What actually ties those two requests together anyways? (makin no sense here)
Also I notice your session timeout is 20 seconds, probably as a means for testing, but could that be introducing something funky? Did you observe that OnSessionEnd always runs?
Not sure if this matters, but the documentation for OnSessionStart says "do not use the cfreturn tag." (do what they say, just to be sure)
It also says "You never need to lock the Session scope to set its variables using this method." That really does tell us that does the locking for us. Maybe the cfthread is causing shenanigans? Maybe try using something like a loop to create the delay instead.
Thanks and good post - I do still see the occasional 'session is invalid' error. Not sure if I'm the only one...
@MrBuzzy,
By default cftoken and cfid are persistent cookies assigned on the return of the first request to the server. They don't expire so even if you close and reopen the browser they are still there. The server should run the onSessionEnd after 20 seconds, but the cookies will remain.
btw, I'm a slow typist and none of you had commented when I started :)
Ditch that cfreturn tag is all I've got.
@Lee, well, yes that is the non-j2ee behaviour. (it's an option in cfadministrator).
When j2ee sessions are on, the cookie is not persisted.
@Anthony,
If one frame wrote to the SESSION scope, even putting locking around the reads and the writes wouldn't matter. You lock would presuppose that one frame will absolutely execute before the other; while the request might be in that order, I don't think there is any rule that allows one to assume that requests complete processing in the order they were requested.
As such, if Frame A wrote to the SESSION scope and Frame B output a session value, there is nothing that CFLock can actually do to ensure that Frame A completes executing before Frame B executes unless you put CFLock around the entire read/write sequence of events (which is really the only time locking should be used - to lock a sequence of events).
Plus, just because we can create something like a "dirty read"... that doesn't necessarily mean that a race condition has occurred. Taking your example, you are implying that is is mission critical that both those number outputs match up. But, is that really true? To create a race condition, yes, it would have to. But, if it were not mission critical that those values be the same, then no race condition exists.
@MrBuzzy,
I am not very familiar with J2EE session variables. I am not sure how they differ in functionality and how they might change this behavior.
@Lee,
So, I tried running it different browsers that had NOT run this example before. They all fail on the non-locked code, except for Google Chrome. Google Chrome never fails on the non-locked code version, even when the session has timed out. My guess is that this is related to the way Google Chrome is handling the Frame requests, not the cookies??
All browsers tested (IE8, Chrome, Safarie, FireFox) started working after lock was put in place.
The problem isn't that one frame is expected to load before another, it's that if both are running, the session value before the sleep and after the sleep could be different within the same frame.
My understanding, according to the Adobe docs, is that all of the built-in methods for the application.cfc do not require locking. Maybe the docs need to be more clear on this, as there still seems to be confusion on this.
I agree with Ray though, this appears to be a bug.
@Anthony,
Fair enough, but I think we need to explain that in that case, the locking would be around the random number, the output, the thread, and the second output. That would lock the entire sequence of events.
This is a good place for locking (if you think it is mission critical - which I would bet in most cases it is not). However, I think it is very important that people realize that putting CFLock around the random number and output individually would NOT add any value in any way.
I think the latter mentality is the one that people have in a very incorrect way.
I think cookies are the same for all applications on the same server, so you have to be sure those browsers never hit a cf page on your server to get a cookie. I cleared cookies in IE and get the same results as chrome. Both frames load ok after the pause and have different cfid/cftoken values - no lock in onSessionStart.
@Lee,
Help me think this out (I don't often think about Cookie storage). What is the difference between a non-existent cookie and one that represents a timed-out ColdFusion session?
Whether a cookie exists or not, ColdFusion is going to return a NEW set of CFID / CFTOKEN values, right?
@Lee,
Also, if it mattered if the cookies existing yet or not, then I think Chrome would work the first time and fail the second time. Yes, Chrome continues to work even after the session times out (without browser close).
Nope, at least that's not what I have seen. If you have cfid, cftoken cookies then cf uses those even if your session has timed out. Same cfid, and same cftoken forever or until you clear your cookies.
@Lee,
Hmm, I'll have to do some testing on that. To be honest, I am not sure at all :)
@Ben,
You are right that chrome never fails. Hadn't noticed that before. It's almost like chrome waits for the first frame to load before requesting the second, but it still sends both requests without the cookies on the first shot. In IE if you clear the cookies and restart, the frame requests are sent simultaneously without cookies and you get two different sets of cfid/cftoken values. I had to open up fiddler to make sure I wasn't crazy. Ok, I'm done for now gotta get some work done today. Doh I missed lunch. . .again. .
@Lee,
Thanks a lot for your help an insight. You've given me some other things I want to test.
And thanks for your blog. Every time I have a cf question and "Google it" your page comes up.. usually with something relevant and helpful.
Thanks a lot!
Hi, I hope you can hear me out here (445am in Oz) - I don't actually think this is a 'bug' at all. It's the right behaviour and it protects the health of the server.
Ben, lets say we put your locking mechanism in to production: It's a busy day, something's running slow. A few users get impatient waiting for their OnSessionStart to complete and start banging on the refresh key. Their request are now banking up at the cflock in OnRequestStart() if they do it enough within the 30 seconds, they hog up all of the request threads. Server appears to be dying.
Now lets say we have a 'normal' Application.cfc instead: Same scenario - the users get trigger happy - on refresh no. 2 they experience the error 'USER.NAME is undefined in SESSION". User scratches their head and curses IT. They hit refresh a third time and everything is working again (the OnSessionStart method has finally completed).
So (if you agree that the latter scenario is better) we CFers should simply handle the situation with a couple lines of code and move along ;)
<cfif not IsDefined("SESSION.USER.NAME")>
<cfset message = "Please try again, we like the attention">
</cfif>
I think this also explains how the OnSessionStart method is thread safe. It only runs ONCE (no do overs!) when the session starts, such that the session scope is empty anyhow and there is no possibility of race conditions. Not a bad place to be after all.
MrBuzzy, I disagree that the latter is better. The first may be slower, but works better to ensure integrity. If your onSessionStart is that slow, it SHOULD be backing people up and you should look to rework your logic a bit (ie, increase the darn speed). I'd rather have safer code than what is being described above by Ben.
(FYI, still planning on testing this code myself.)
@MrBuzzy,
Hmm, I have to say, I really like it! I could imagine putting something like this in the OnRequestStart():
<cfif NOT StructKeyExists( SESSION, "Initialized")>
. . . . <cfoutput>System busy, please refresh page.</cfoutput>
. . . . <cfreturn false />
</cfif>
Something like that. Why even let the page request be processed at all if the conditions for processing are not perfect.
I have to say, I think that is exactly the right approach!
@Ray,
I think we have to realize that this will happen very rarely (and in fact happens so rarely that it is hard to debug). With that in mind, I am comfortable halting a request that is not yet initialized properly.
Yeah but the whole point of App.cfc and these methods was to get RID of the old hacks. The old 'if not isdefined, do x'. To me, this is a step backwards.
Ok, so this is interesting. I tested, Mac, 801, and it works as I expected. THe second request is delayed waiting for the first one.
Dude... woah - why is your ApplicationTimeout less than your sessionTimeout? That looks pretty bad to me.
@Ray,
I agree that this is hack.
The times are off - one is minutes, the other is seconds.
Tested again. Used a 'proper' (IMHO) app timeout of 1 hour. Set sessiontimeout a bit higher (60 seconds) and it worked as expected. One frame was slow, then the second one showed up right after the first one loaded.
I think this is related to your app timeout, Ben.
I don't think its just a hack, I think it may be part of your problem. Having an application timeout BEFORE a session would be a bad idea and could lead to weirdness like you are seeing.
@Ray,
My application timeout is longer - 10 minutes. The session timeout is only 20 seconds. It's just an optical illusion that they are in the same field of CreateTimeSpan() :)
Are you getting the same results if you hit the page, wait for session timeout, and then refresh the frameset?
Wait for sessionTimeout? I can't get one. As I said, the first frame waits 5 seconds. The second frame shows up a split second after the first frame finishes, which is what I expected.
Oh... wait for sessionTimeout. Ok, testing now.
Well well well. One frame throws an error and the other does not.
So it acted single threaded on the first hit. THe second request was definitely blocked waiting for the first. But on creating a _new_ session it did not.
@Ray the latter is really the only viable way, from the perspective of building ColdFusion server to be rock solid. Which it is (as much as any other technology). Okay, that sounds sucky. Not going back now.
Ben is free to risk swamping his servers with impatient/duplicate requests :) But it doesn't have to be the default behaviour of the server.
The frames thing is the same choice: one or more requests either waiting or failing (remembering that frames and ajaxy divs and stuff can easily try for a reload).
@Ray,
Hmmm, funky chicken. I think this goes back to what Lee was saying that it has to do with the existence of *any* CFID / CFTOKEN values. If none exists, then maybe it single threads. If one does exist (but is expired) perhaps it handles in a different way.
If I hit the frameset without session cookies stored (in FF 3 on Linux, BTW), then I am observing the same results as Ray -- the second frame waits for the first to complete, no error is produced, and, interestingly, each frame has a unique session.
If I retain the session cookies and allow the session to time out, then I *do* receive the error in the second frame on the first page request while the first frame is waiting in the OnSessionStart method.
So the session identifier is definitely playing a role here...
@MrBuzzy: We will have to agree to disagree. I think you can err on the side of performance/safety, and I think safety is better. ;)
@Ezra: Yeah I think it has to do with the cookies too. I really think we have a bug here (and not a doc bug).
@Ezra,
Awesome - I think we're seeing a trend here.
@Ray,
Now *this* has got to be a bug. There's no way that ColdFusion should handle new session differently if there is or is not an expired session.
@Ray I'm happy to disagree, maybe Ben can talk you round :) G'night.
ps: you guys are now just describing how session management works :P Browsers are quirky, just don't use frames.
@Ezra
If you hit the page with no cookies, you should get 2 different sessions. It's that darn race condition. The first frame to return sets a session cookie, but before it returns the second frame should have made a request. The second request is happening without the session cookies. If you refresh the page, now both requests happen with the last session cookie you received.
Sigh, sorry, one last stab...
@Ray I feel like we're talking about different things. I'm not stating a preference for safely or performance, I'm saying CF Server is built to work that way, as a guessed explanation of this 'bug' being discussed.
Do some tests on pages without frames, try repetitive/incomplete page refreshes. I hope you don't agree that all page requests in the same 'session' should be made to wait for OnSessionStart to complete. What if it never ends...
To make this even more fun - now I can't reproduce the issue at all. I can let my session timeout, hit the site, and it works perfectly (first frame slow, second frame loads right after first).
@Ray,
Oh man! This error is nuts :)
@MrBuzzy: To me, onSessionStart, implies: These are critical things that must be done when the session starts.
So yes, I _do_ mean that if the method never ends, your session would never start, and you would never be let in.
This is like your ticket into the application. You MUST have it before you enter.
If you use frames and your site is slow, then re-engineer your onSessionStart logic to not be so darn slow.
@MrBuzzy,
While I like your "solution" to the current buggy problem, I have to agree with @Ray that OnSessionStart() should be single threaded, just like OnApplicationStart().
Here's an interesting twist: unless I am mistaken, once the application times out, the error will not occur, even if the cookies still exist in the browser.
I don't know - in my tests, the app timeout is 1 hour.
What we need to do is add some AdminAPI code to get the internal stuff, like session, timeout, etc. I'll try to whip that up so we can dump it. I've already modified frame.cfm to cfdump the session and show the applicationname.
@Ray
I don't know if this is really a bug. This is why we should be locking session scope. By locking, you can be sure that even if onSessionStart hasn't finished, your request can wait for it to finish.
I do agree that one should improve the performance of their site instead of making the end user refresh the page.
@Ben
I think that onSessionStart is single threaded. If you make 2 requests at the same time, without any session cookies, you are making 2 requests with 2 different sessions. Your session doesn't get associated with your browser until one of the request comes back and sets your cftoken and cfid.
@Ray, I'm running CF in a terminal window, and logging the session and application timeouts so I can see exactly when they occur.
If I refresh after session timeout and before application timeout, I get the error (I set the timeout values to 10 and 30 seconds, respectively). Once the application timeout occurs, the next request works as it does when no cookies are stored.
Ahah. Anthony is on to something. Ben, I added code to make the user id a createUUID()... ie, random.
When I clear cookies and hit the site pure - guess what - I see 2 different values. It is like he said - the cookies aren't set so the two requests act like 2 different sessions.
But - we get one set of cookies back.
I don't have anything more smart to add now - but I think we are closer.
I dumped cookie, and yep, the cookie values aren't the same. So we have two requests setting 2 cookies w/ the same name, but different values. No wonder we have a problem here.
@Anthony, I'm aware of the typical race condition scenario, but what threw me a bit here is the fact that in Firebug, it appeared to me that the second request was queued behind the first when the frames were both calling the same inner file.
It did not look like the second request was initiated until the first completed, but perhaps this is just a quirk of Firebug's reporting, and the HTTP handshake already occurred, thus triggering the creation of the second session.
@Ray, there are definitely two distinct sessions being created when the cookies are not present, and the cookies of the second request are overwriting the first.
So then is it safe to say then that onSessionStart is working right, but the frames simply created two sessions? If so, an even easier fix (besides locking) would be to use a loading page. Loading page is the word word, but, just one simple request _before_ you load up the frames.
@Ray
I dont think that locking will even fix this mess. If the 2 requests are coming in as different sessions, locking the session scope in the cfm will lock 2 different session scopes. It looks to me as though the only solution is your solution of loading a page before the frames or changing the .htm containing the frames to a .cfm.
Well duh, changing the main guy to .cfm _would_ fix it in like 1 second. ;)
I _knew_ there was a good reason I tended to make all my files .cfm, even when just working on jQuery stuff. ;)
I definitely think there's something wonky here. I just tried this (not removing session cookies):
1. Page request -- application & session initialized
2. Wait for session timeout (as indicated by message logged to console)
3. Page request -- NO ERROR, session re-initialized
1. Page request -- application & session initialized
2. Page request (just a refresh, nothing re-initialized)
3. Wait for session timeout
4. Page request -- ERROR
WTF?
Given that, I retract my earlier speculation about the application timeout, as it seems that there is something else going on here...
I wonder if it makes sense to debug the cookies sent to the CFM? Just throwing out ideas here - almost Miller time for me (and I need it this week).
@Ray, regarding the frameset issue, using a .cfm would indeed solve that particular angle (and I most certainly agree that they are just a bad idea regardless). As Ben noted, however, it is possible for user behavior to produce concurrent requests (by double-clicking links or re-submitting forms, for example).
I would regard it as an edge case, but I could potentially see how unexpected errors could result from this.
@Ezra,
This is a tricky one. How many sessions are being initialized? There should be 2 sessions, 1 for each frame right? Are there also 2 timeouts?
@Anthony, that depends...
If I clear the session cookies, then hit the test page, two sessions are created, and both do indeed time out. The latter set of cookies "wins", however, and the first session is disconnected from the browser.
Once the cookies exist in the browser, then only one session is created -- the second frame waits for the first to complete (in my environment, anyway), and they are both sharing the same session.
Either way, I can still reproduce the behavior I mentioned: allowing the session to time out *without* an additional page request after the initializing request and then refreshing will not produce an error, whereas an initial hit, refresh, time out, refresh *will* result in the error, so I'm not sure what the deal is there.
Ezra - you sure about only one session being created? Dig into the Admin API to get the session list. If you need help with that, let me know.
And some of this evidently has to do with the browser's behavior, as if I use two different but identical files for the frame contents (e.g., frame1.cfm and frame2.cfm), I consistently receive an error on the second frame when reinitializing with the cookies persisted.
So it does indeed appear that Firefox is queuing the second frame behind the first when they are requesting the same file, which I think is why I did not see the error under certain circumstances.
@Ray, I'll confirm that shortly...
I seem to remember a Firefox setting that controlled how many 'pipes' were open at one time. You would normally muck with it to help improve performance on pages w/ lots of crap on it. Does it make sense to find that setting and see if it is set to 1 now in the latest builds of Firefox? (I seem to remember it being 4 by default.)
Hey guys, sorry had to run out for a bit. I think I need to play around a bit with understanding how CFID / CFTOKEN react to new sessions started from timed out ones. Lee suggested that they just re-open the session, but I want to double check.
In case this helps - obviously change the login password to match your domain:
<cfset admin = createObject("component", "CFIDE.adminapi.administrator")>
<cfset admin.login("admin")>
<cfset sm = createObject("component", "CFIDE.adminapi.servermonitoring")>
<cfset sessions = sm.getActiveSessions(application.applicationname)>
<cfdump var="#sessions#" label="#sm.getActiveSessionCount()#">
You might consider a utility like wfetch or wget to run some of these tests. These tools let you look at the raw HTTP conversation and will take cookie handling out of the equation. I find that really handy when working through issues like this.
@Ray, there are most definitely two sessions created when starting without cookies, and only one when the cookies exist.
My relevant FF settings are:
network.http.pipelining: false
network.http.pipelining.maxrequests: 4
These are both the defaults, and setting network.http.pipelining to true did not appear to change the queuing behavior I mentioned when both frames are loading the same file.
@Ben, while the same CFID and CFToken are re-used, I'm pretty certain that the actual session in memory is created anew, as the value of the elapsedTime key is reset in the dumped array of session structures from using the getActiveSessions() method of the Admin API.
@Marc,
I dont think we want to take cookie handling out of the equation. Tools like Firebug or livehttpheaders for firefox or Fiddler for IE work great for inspecting http information. Not sure if there is a way to see that info in Chrome though.
@Ezra,
Yeah, that's what Lee was saying also. It just never occurred to me that the CFID / CFTOKEN would stay the same. It never occurred to me that it would change either, just never checked it.
@Ezra
And you're saying the requests are queued up in firefox? I wonder if frames are blocking tags, similar to script tags. The pipelining is mostly for downloading images, css and other assets at the same time, however script tags block all other downloading and queue everything until it is donw downloading. If you put an image on the page after the frames, in firebug can you see if the image is queued up while the frames are loading?
@Anthony, you mean in another frame?
If I add an image in a third frame, that request goes through immediately, while the first two frames that are requesting the same .cfm file both wait for the delayed session instantiation to complete.
If I instead use two different files for the first two frames, then one will error immediately while the other waits for completion of the paused OnSessionStart call.
@Ezra
I didn't mean in another frame, but your response answers my question anyway. So the frames are only queued if the requested file are the same. I'm out of ideas now.
FWIW, the relevant Firefox setting appears to be network.http.use-cache, which defaults to true. If I set this to false, then the second frame's request for the same filename does not wait for the first to complete, and the error is produced.
This would seem to make sense, as it looks like the browser is optimizing to avoid another round trip to the server, but it cannot load the page from the cache until the first request completes.
Regardless of all the intervening browser behavior, the original point still holds, and my current understanding would summarize it like so:
OnSessionStart is fired by the first request of a new session, and subsequent requests from the same session will *not* trigger this event, so if the OnSessionStart method takes a bit of time to complete, errors can result.
To easily see this without the potential frameset confusion, make sure you have the CFID and CFToken cookies present in your browser, access the inner frame page directly, and while the OnSessionStart method is sleeping, immediately refresh the page (to simulate a second click or form submission).
One final point worth clarifying here (at the risk of beating a dead horse):
Without any session cookies set, if you hit the demo multiple times within the "sleeping" window, then *every* request will fire OnSessionStart, and each will receive a new set of session identifier cookies, as they are all new sessions from the perspective of the server.
Only one of these multiple sessions will be "hooked up" to the browser, as the last set of cookies to be set will "win", and all the other sessions will simply time out, as they will no longer have any user interaction.
On the other hand, if the session cookies *are* already present in the browser, then OnSessionStart will only fire once, and additional requests during the delay will produce the error, as they will be referencing the same session in memory (as indexed by the cookies), but are attempting to access a session variable that does not yet exist.
@Ezra,
I think the take away from this whole post is that getting these random errors is not longer such a huge mystery. At least it make more sense than it did a few days ago and I see that some steps can be taken to avoid it.
@Lee,
I just ran a test that did NOT do the CFLock on the OnSessionStart() method and the bottom frame did indeed error. While the OnSessionStart() might be single threaded if no cookies exist yet, it seems that it won't automatically single thread if expired session cookies are passed through. As such, CFLock would be required in both the OnSessionStart() and the OnRequestStart() method.
@Ben,
I think you are right that it must be locked. I was thrown off because Chrome seems to queue up the requests. OnSessionStart is single threaded, but only get's called once if you have cookies set, because like you said in the beginning "ColdFusion creates the SESSION before it calls the OnSessionStart()". At least that's what I got from all this.
Way to get people thinking on this one.
Lee
@Lee,
Yeah, I am not sure what Chrome is doing.
Much to read here. I am working on a admin app that requres login, so I use a CFLOGIN the traditional way (copied straight from the docs) in the onRequestStart(). If I use loginstorage="session" I get a very random behaviour even in single frame use.
It does not go inside <cflogin> if the session is just timed out, but it always goes through onSessionStart(). But for some reason OnSessionEnd is never triggered.
If use loginstorage="cookie" I get better result, but still not consistent.
If I use a sessiontimeout of 30 seconds, it usually works if I wait 60 seconds. With "works" I mean that It will process this in OnRequestStart():
<cflogin>
<cfif NOT IsDefined("cflogin")>
<cfoutput><h1>Login</h1></cfoutput>
<cfinclude template="loginform.cfm">
<cfabort>
<cfelse>
<!--- session init --->
<cfloginuser hhsdh>
</cfif>
</cflogin>
As I said, it will always bring up the loginform after 30 seconds.
But what exactly is it that tells CF to process the cflogin tag?
"The body of this tag executes only if there is no logged-in user" it says in the docs, but... it is strange, I get the loginform because there is no logged in user, but when I post the login info it does not process the cfloginuser. Still, a getAuthUser() on the requested page will give the username of the last logged in (before session timeout) user... what does CF check to see if there is a logged in user?
The last 15 minutes I have run with disabled cache and logged in randomly between 35 and 120 seconds and it have worked well so far. Maybe it is a cache issue?
My planned solution for this is to replace the cflogin with a session.initialized check, and set session.initialized to false in the onSessionStart(), but it is very frustrating because I have used the same basic Application.cfc before without problems. Must be a combination of settings that messes things up.
Now I have tested for 30 min with disabled cache and have a 100% success rate. But I can not force the ppl to switch off their cache. Maybe a header in the cflogin can solve it. But I need an espresso before I even try to think about it.
33 minutes. After opening the page in a new window it fails again.
I feel weak.
One should proof read before posting.
I said "As I said, it will always bring up the loginform after 30 seconds."
Actually, I had not written that in the post it seems, only in my my mind.
So to clarify:
-<cflogin> will always be processed after 30 seconds, bringing up the login form
-After the login form is posted, it is not at all certain that it will enter <cflogin> again to process the form info and do a <cfloginuser>, apparently because it has found a logged in user while I was typing my username/password or something. Who knows. Computers are like cows, you never really know what's on their minds.
So.. am I right that the bottom line resolution is that an empty cflock on the session scope in onRequestStart() will address the issue as long as the cause isn't an error in onSessionStart()?
I followed the whole comment thread on both posts and I *think* that thought has held true throughout. :-)
@Joshua,
The CFLock in the OnSessionStart() and the empty one at the start of the OnRequestStart() will prevent a request from carrying on during the session start of a concurrent page request.
However, if OnSessionStart() fails (throws some sort of error), subsequent page requests will still be screwed as they will be acting on a partially initialized session.... I have some more ideas on this shortly.
I have a little problem somehow related to frames and session or cookie. Do you guys know that if you have an iframe, the parent page don't share cookie and session to the iframe.
I'm developing a website that uses a Kelley blue book Control Mode. I have to include the kbb.com info into an iframe instead of pop up. Similar to what usedcars.com use (http://cm.syndication.kbb.com/kb/ki.dll/ke.kb.sp?ucc&&&usedCars;slp)-pop up. At the very end of the steps on the Iframe it will postback hidden values to my website but that post back page will still be on the iframe. When i try to retrieve the value of the cookie from/inside the iframe to store the data it doesn't exists. The cookie value that i'm trying to retrieve is a customer id which is created when the customer log's in to the site. Any idea's please.
Thanks in advance.
@Jun,
As long as the iFrame has the same domain as the parent frame, they should share cookies.
I tried what you just sugested but unfortunately it didn't work. I already that thought in mind before trying my last resort that solves my problem. Anyway, here's what i did that is same as what you suggested. On the website the customer has already a cookie named custID and the styleID (car style), when it goes to the page where there was an iframe. Let's call the page as trade-in.cfm, and under that page i have an iframe:
----------------<br />
<iframe id="Iframe1" frameborder="0" vspace="0" hspace="0" marginwidth="0" marginheight="0" width="620" scrolling="yes" height="600" src="iframe.cfm"></iframe>
-----------------<br />
the page iframe.cfm is obviously still part of my site. When I tried to dump the cookies, all the information I wanted was still there. But here's the problem, inside the iframe I have to put a cflocation to redirect to (http://cm.syndication.kbb.com/kb/ki.dll/ke.kb.sp?ucc&&&usedCars;slp). Notice that this is already a kbb.com site not ours, and were still inside the iframe. In short the website inside the iframe is not anymore mine but kbb.com. The customer will go to the step by step process on kbb.com. When they're done they will submit a button that goes back to our site. Still inside the iframe i tried to retrieve the cookies again but there gone.
But here's the solution i used, when the customer is back to my site but still inside the iframe, since i can't retrieve the cookies they will to go to one additional step that is to submit the page but in the form attribute i included a target set to parent (target="_parent") just to get rid of the iframe and set the values from kbb.com to hidden fields, and there i was able to successfully retrieved back the cookie as well as the hidden values.
Thanks anyway for the reply.
@Jun,
That's odd that the last step in the IFrame (which lands back on your domain) does not have access to the page cookies (or rather, that those cookies are not there any more). I cannot think of a reason why this would be true.
Funny, I just ran into this problem (or one that acts an awful lot like it) recently on my CF9 installation. I have a cfm that generates an RSS feed. When the page is visited by a browser, everything runs fine. But when an RSS reader hits it, several session variables set in the OnSessionStart method come up undefined.
So far I've just worked around it by putting in an "isDefined" into the RSS page, but that's lame way to fix the problem.
Has any new news about this bug come up since last year?
@Doug,
Definitely sounds like the same kind of problem. If a spider were to hit your RSS feed multiple times with some existing cookies, you could run into this kind of problem (theoretically).
No new news on my end. In fact, I just yesterday recorded a video of my "Mastering The ColdFusion Framework" presentation and mentioned this kind of behavior as something to be aware of.
I check for the session validity before allowing the user to download a doc file.
This check is being done in the Application.cfm file.
But, if the user specifies the URL of the doc directly (which is also under the web root of the CF), the check is not done and the file is getting downloaded.
Is there a way to avoid this?
@Naveen,
ColdFusion only intercepts CFM/CFC pages. This is always a tough problem. I don't have a great answer for this. You could try using CFContent.
Sorry to revive this thread, but this is the only place we can find that's talking about anything close to what we're seeing.
We're having a problem with CF sessions and Google Chrome. Diagnosing it using your test code, we have found the following:
With a normal, cookies-enabled Chrome session, the Ben Nadel Test (as we're calling it) FAILS: it says "cookies accepted: no", and the five token values are different. (But a simple cookie test it says it accepts cookies.)
Pop open an "incognito" window, not changing any settings otherwise, and run the selfsame Ben Nadel Test, and it works: "cookies accepted: yes", all five tokens the same.
This makes no sense to us. How can it fail with cookies enabled, but then work in an incognito session? Moreover, just what is wrong with Chrome outside incognito mode? It's seriously screwing up anything we write using sessions. We can't just check for cookies, because Chrome in both instances accepts cookies. I'm at this point thinking of modifying the Ben Nadel Test and running that each time to figure out if you're on Chrome and Chrome is misbehaving and asking you to go into Incognito mode if it is...
Has anyone experienced anything like this with Chrome? It doesn't always happen on every copy of Chrome, but once it starts happening, it continues like I describe above. Basically, WTF?
AArgh!! I'm sorry, I posted the above comment on the wrong blog post! I meant to add a comment to: www.bennadel.com/blog/730-Testing-ColdFusion-Session-Cookie-Acceptance.htm
Apologies all around, I'm an idiot.
I always lock SESSION variables anyway, despite being criticised for it. Better to be safe than sorry. Now, I am glad, even though I never use frame sets and wouldn't work for a client that demanded me to do so...
But thanks Ben, for doing this test!