Why precisely is it important to var-scope your variables?
posted under category: ColdFusion on December 9, 2008 by Nathan
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.