The Dopefly Tech Blog

<< The Dopefly Tech Blog Main page

Why precisely is it important to var-scope your variables?

posted under category: ColdFusion on December 9, 2008 at 1:00 am by MrNate

We've all heard it a thousand times. "VAR YOUR VARIABLES !" But why? You say, "I've never had that problem" and I'll tell you what, you probably wouldn't know it if you saw it. Pay attention here.

Let's take a simple ColdFusion function, easy stuff here. Dead simple.

<cffunction name="runMyCode">
<cfset result = 1 />
<cfloop from="1" to="5" index="i">
<cfset result = result * i />
</cfloop>
<cfreturn result />
</cffunction>
What's the harm?
Ok, so you hit your page, which calls this code tucked away safely in a CFC. You put that CFC in the application scope because that's the Singleton pattern, or something like it and it runs faster. You get a good result (120). Great. Hit it again, no problem. Write a unit test if you want, it passes every time.

Now, put that out on production. I dare you.

Minutes later, your phone rings. Bill Billings, your most loyal customer calls up. He must be your most loyal customer, because it's only loyal customers or especially angry customers that ever call. He sounds angry and you rethink Bill's loyalty. Bill says "What's the idea? I got '7200' when I know I was supposed to get 120!" And you're pretty sure he was supposed to get 120. I mean, that's what the function does. It just returns 120. You tested it! You tell Bill that you'll look at it and brush him off because you're busy coding. Check your glasses, Bill. Check your glasses.

As soon as you get off the phone, it's Sally Sallington, another loyal customer calling, asking how she got 518400000 from something that should really be giving something closer to 120. Oh man. Now you have a problem.

Interestingly, your customers are getting round numbers, and they are both divisible by 120.

The Java thread model
If you get the sense that java is screwing with you, you're on to something. What you failed to test for was multithreaded access to your method. When Bob and Sally hit that method at the same time, anything can happen.

You've got 2 users, so 2 threads running some code. Java has to make sure that both requests are satisfied as quickly as possible. How does it do that? Well Java will flip-flop between which thread is running, basically, on a whim. Maybe Bill will run through the loop 3 times and stop before the multiplication line, then Sally will just start the loop. Bill will run another 1 time around the loop, then Sally's thread takes a turn going 2 loops, stopping after the multiplication line.

Both variables in the function, result and i are being shared between everyone who runs the function. Bill sets result to 1, then 5, then 10, at which point, Sally may come in and set result back to 1, or pick up Bill's 10 at the start of the loop and go from there. It's impossible to say what exactly will happen. It's part of the magic of Java.

Why did they do it that way?
Why does Java's threading flip-flop between the active threads? Why can't it just finish one and then start the next? Well then you wouldn't be running a multi-threaded application server, would you?

Then there's HotSpot - Java optimizes code as it runs and chooses the best path while it's running. As far as I know, it's literal, actual, magic. There's no way to know what it will do.

Also, usually your code isn't quite that simple. You put <cfquery> tags in there which could even be on a different server, maybe component instantiation calls that have to go to disk to pull in the CFC in, compile it and call it. While Java waits for the network or the disk or whatever else, it handles the other requests that are waiting.

But I have a 4-CPU, 4-core server
Oh, that's not going to help. Now Java can literally do the same operation across 16 threads at the exact same time. You're really in trouble.


read on for the stunning conclusion...

But my 'V' key is broken
Ok, so let's pretend that you can't fix your code. You really just don't have the budget or something. What can you do? Well, you can change the maximum number of simultaneous threads to 1 from your ColdFusion administrator tool. By not letting anyone do anything concurrently, you have solved your problem and made a new one: now your web site is slow. Another idea, instead of adding 'var' statements, you can exclusively lock calls to methods that have not properly varred their variables. It's a terrible workaround, but would allow more things to happen simultaneously while still single-threading the danger zones. Still, that could be a lot of work instead of fixing the root cause which is almost always going to be simpler.

How do I know what to 'var'?
Use the VarScoper tool! It's painless and simple. It's a friendly ColdFusion program that runs on a CF server. It reports every unvarred variable that it can find in a given CFC or directory structure, tells you what method it was in, tells you which variable it is and what line number it was first found on. The varScoper is truly a great piece of software. Run it frequently, say, weekly, to make sure things are still good.

There are exceptions to the rule
Yes, caveats exist. Specifically, if you are running CFMX 6.1 (No one should run 6.0 or earlier. No one.), you cannot var cfhttp. It just won't work. Don't try it. This is a time when you have to cflock any code that makes an HTTP call. There may be others, but this is off the top of my head, and again, it's CFMX6.1 only.

The right code
Some people requested the corrected code, so here it is:

<cffunction name="runMyCode">
<cfset var result = 1 />
<cfset var i = 1 />
<cfloop from="1" to="5" index="i">
<cfset result = result * i />
</cfloop>
<cfreturn result />
</cffunction>
This function will run with no problems. Also, check out the comments below where Ike shows us the dangers of using variables outside of functions, and Adam Cameron gives a very common example of functions calling each other, each using the generic "i" loop iterator variable, and conflicting with each other.

Thanks everyone out there for your constructive comments!


Wrapping up
Here's the quick summary, speedy delivery, Mr. McFeely style. Var all your ColdFusion function variables, learn about scopes and threading, run the varScoper tool often. Everyone will be happier.

Too old to comment!
On Dec 9, 2008 at 1:00 AM ike (info at the ever-endearing turnkey.to) said:
Nice article. :) There was an issue similar to the cfhttp issue with try-catch on a previous version of cf as well, although I don't remember the version and I think it was specifically in cfscript.

On Dec 9, 2008 at 1:00 AM ike (info@aol.com or @turnkey.to) said:
p.s. I love your email address obfuscation. :)

On Dec 9, 2008 at 1:00 AM ike (info, visiting us from turnkey.to) said:
You should add [name] who dances with @[domain] if it's not already in there. :)

On Dec 9, 2008 at 1:00 AM Nathan Strutz (http://www.dopefly.com/) said:
Thanks for the note on cfscript.

I added "who dances with" just for you :) Refresh a few times and you'll see it.

On Dec 10, 2008 at 1:00 AM Dan Wilson (http://www.nodans.com) said:
This is a very well written description. I totally dig the clarity. Developers should be forced to read this before they can save their first CFC.

DW

On Dec 10, 2008 at 1:00 AM Nathan Strutz (http://www.dopefly.com/) said:
Ah Dan, you're all too flattering. Thanks for that. It's good to see my month long accidental blogging hiatus wasn't for nothing.

On Dec 10, 2008 at 1:00 AM marc esher (marc.esher whose email lies with gmail.com) said:
this race condition business is only true for shared-scope components though, and not variables-scope components. if two users simultaneously hit page.cfm, and page.cfm has a call to createObject("mycomponent"), and my component has your non-var-scoped function in it, result will return what you expect it to return.

unless you run that function inside a cfthread with a loop or something, but you get the idea.

no?

I'm not saying that "you should only var scope stuff that's gonna be used in a shared scope." not even remotely saying that. Just clarifying something that i can see other people misconstruing.

in fact, i can think of other super sucky/dangerous bugs that can result from not var scoping even when multi-threading isn't an issue.

also, "mr. mcfeely" is a classic. nice one!

On Dec 10, 2008 at 1:00 AM marc esher (marc.esher who wouldn't be caught dead at gmail.com) said:
well... it'll return what you expect it to return the first time it's called on that same page. but do this:

<cfset something = obj.runMyCode()>
... other stuff....
<cfset somethingElse = obj.runMyCode()>

that'll be where the hilarity ensues.

On Dec 10, 2008 at 1:00 AM Nathan Strutz (http://www.dopefly.com/) said:
You're right on the money there, Mark. I guess I was trying to explain Java threading as well as var scoping.

At most of the shops I've seen, by default, a CFC contains a bunch of functions that just query a database. Every CFC is set in the application scope because somehow we all decided it was best practice in 2003. I've seen a few better systems, and a few worse, but that's been a large amount of my experience when cleaning up after people.

You're totally right though, this case I described is just shared scope components, which I wasn't clear on.

I was thinking of an easier take on the idea, but those have already been done.

On Dec 10, 2008 at 1:00 AM marc esher (marc.esher at the ever-endearing gmail.com) said:
i think it'd be fun to see people's "how missing var drove me insane" bug stories.

On Dec 10, 2008 at 1:00 AM Mike Schierberl (http://www.schierberl.com/cfblog) said:
I have a live example of similar code running on my site. Non-var-scope believers can see it in action here...

http://www.schierberl.com/cfblog/index.cfm/2008/7/21/Thread-safety-and-the-var-scope--live-example

On Dec 10, 2008 at 1:00 AM Ian (http://catholicinformation.aquinasandmore.com) said:
I needed this article two weeks ago. I rebuilt our order processing code into a cfc and put it in the application scope. I VARd all my simple variables but didn't think about the queries that were being returned within the cfc.

Suddenly, orders are displaying comments from other orders. AAGH!

After talking to the CFJedi (thank you) I vard all my query variables before creating them and suddenly the problem went away.

On Dec 10, 2008 at 1:00 AM ike (info who wouldn't be caught dead at turnkey.to) said:
Actually this particular function would be fine on multiple iterations on the same page, because the result variable is set at the top and the i variable is reset to 1 when the loop starts. So as long as there isn't a race condition, this one will be fine.

But there certainly are examples where that's not the case. Let's say hypothetically that this function is in a CFC and the result variable is actually a "starting point" for the function, but it's stored in the component's variables scope.

<cfcomponent>
<cfset result = 1 />

<cffunction name="get120">
<cfloop index="i" from="1" to="5">
<cfset result = result * i />
</cfloop>
</cffunction>
</cfcomponent>

In that case it would definitely start misbehaving on the 2nd call. Introducing <cfset var result = variables.result /> will turn it into a copy and ensure that it always starts at the same place, however... you still have to var the "i" variable for the race condition too just in case this CFC finds its way into a shared scope.

On Dec 10, 2008 at 1:00 AM Adam Cameron (adam_junk, who eats hotmail.com) said:
This issue is not restricted to CFC instances that are in shared scopes.

It's trivially easy to demonstrate the issue in a single-threaded CF simply by having one method calling another method, both of which use the same (unVARed) variable:

<cfcomponent>
<cfscript>
function f1(){
for (i=1; i <= 10; i++){
writeOutput("f1(): #i#<br />");
f2();
}
}

function f2(){
for (i=1; i <= 10; i++){
writeOutput("    f2(): #i#<br />");
}
}
</cfscript>
</cfcomponent>

That's a very contrived example, but it's a very common practice to use variables like i (for general counters) or getData (for queries), and as soon as one method calls another method using the same variable name... you're in trouble.

--
Adam

On Dec 11, 2008 at 1:00 AM Charlie Arehart (Charlie who wouldn't be caught dead at carehart.org) said:
Yes, really nice job, Nathan. I could see this becoming a classic that many refer to for a long time.

Given that, I think one slight improvement would be to offer the code as it should be, showing how it's to be var'ed. That may seem obvious, but since some may be pointed this who've not yet ever used the VAR keyword, it would help to show it.

Also, if you do create a new entry, perhaps showing an example with a query (as discussed in the comments above), I think it would help to come back and modify this one to add a link to point to that new one. Otherwise some readers well in the future might not realize you did such an update. :-)

Keep up the good work.

On Dec 11, 2008 at 1:00 AM Nathan Strutz (http://www.dopefly.com/) said:
@Adam,
Your example is spot on and very typical. That is the type of error that usually won't make it out of development.

@Charlie,
I added "The Right Code" section near the end. Thanks.
Too old to comment!