It's the little things that get you
I'd like to say I made a great discovery here, but instead I'll say that I made the same mistake everyone else has and will. I thought my code was perfect, but there was a tiny flaw that grew. Let me tell you what it was and how a single loop could cause so much havoc.
Desire for change
It all started off with a desire to make the mailing list digests on House of Fusion better. This code has not been enhanced in a while and while it was solid code, I felt I could do better.So how does one make code better? Step one is to literally write down a description of what the code in question is supposed to do. The second is to write some pseudo code to translate the written ideas into a framework to work with (no, not that framework). Once the pseudo code is done and looks like the written description, it can be turned into real code and tested step by step. The end result should be exactly what was written down.
My idea was to take the 50+ mailing lists on the site and the 8 different digest options that have to be checked every hour and combine them with a cached CFC that will be called multiple times using a scheduled Asynchronous Gateway. Every hour the scheduled event will call a template. This template will generate the 400+ requests and send them to the Asynchronous Gateway. The gateway will actually only be a 'stub' that will send the request to the cached CFC that does the work.
Potential problem and solution
Now before I go on you should already see where a problem can come up. Whenever you have a cached CFC, the chances are high that a variable set to the Variables scope of the CFC will cause a problem. I knew this and tried to make sure that every variable I created was created inside a local structure that was vared to begin with. For example<CFQUERY name="local.users" datasource="#Variables.DSN#">
This technique allowed me to be very clear about what was a local variable and what was a global one. There was no chance of data being saved to the global Variables scope without me knowing about it. At least that was what I thought.
Part of the real problem
Somehow I overlooked something that most of you will overlook: the index variable of a loop. How many of us use i as a loop index? (I really have no clue but if the answer was over 90% I would not be surprised). Without having vared the index at the beginning of the function or setting it to the local structure, it would automatically be set to the Variables scope. This means that each loop will set its index to a cached global scope shared between 400+ other calls to the same cached CFC.The secret of CFLOOP
Now this really would not be a problem if you're using CFLOOP, as the tag cheats.Think about how a For loop is supposed to work. You start with a number (your From attribute). You run the loop once. When the loop is done, you increment the number and then check if the new number is higher than the ending condition (your To attribute). If it's not higher, then the loop is run again, otherwise, the loop is stopped.
CFLOOP doesn't work that way. It does not do a check at each loop iteration but instead it creates each iteration first and then runs through them one at a time. Changing the index in the middle of an iteration and expecting it to have any effect is basically a pipe dream. Don't trust me? Try it yourself:
<CFOUTPUT>#i#</CFOUTPUT>
<CFSET i=200>
</CFLOOP>
No matter how you alter the index variable, it has no effect on the loop operation. So this means that if the index variable was set to the global Variables scope, it would have no effect on the loop itself. On the other hand, if the global index variable was changed between the time that it was set by the loop and the time it was used in the CFLOOP body, it could have an effect. The chances of this happening is exceptionally slim and can almost be discounted as the problem....almost.
Loops the way you expect them
While CFLOOP may cheat, the loops used within a CFSCRIPT block do not. A For loop in CFSCRIPT checks each loop iteration to see if it should continue or not. Try it yourselffor(i=1;i LTE 100; i=i+1)
{
Writeoutput(i);
i=200;
}
</CFSCRIPT>
Now what will happen if the index is changed while it's being used? Chaos. You're expecting a loop to go from 1 to 2 to 3 but because the index is a global variable, it can be set to some other value in a different call to the cached CFC and the index value can go from 1 to 2 to 500, which is far from what is expected. This is what happened to me. Great new code with an overlooked loop variable causing chaos.
Summary
A few things you can take away from this:1. Var all of your local variables or better yet, assign local variables to a Vared local structure. (I always use the Variables prefix for global variables as well).
2. Always be super careful when dealing with cached objects. Triple check your code and then have two other people check it again.
3. Keep an eye our for tags that create their own variables.
4. CFLOOP does not operate the way you think a loop should - CFSCRIPT loops do.


I didn't realize that CFLoop made every iteration of the loop first. That is really interesting. It actually ties into something I had tested a while ago. A lot of the time, I have a CFloop where the TO attribute is based on the length of an array (to="#ArrayLen( .. )#"). This always made me uncomfortable because ArrayLen() adds a method-call overhead and I feared that this was being evaluated for every iteration of the loop and therefore adding N method calls.
After testing a bit (as you did above), I found that ArrayLen() is only evaluated once and not evaluated again. So no extra overhead.
It does seem odd though that you can't update the index variable independently. Oh well.