Friday, November 22, 2002

Alert: when CF will fail to auto-compile--might be big news to some

Have you heard about people complaining that they find the automatic compile process is sometimes not detecting changed code? Did you wonder how that could ever be? Or had a hard time recreating it?

I can now offer a couple of interesting scenarios that are recreatable and do explain it, and seem worthy of further investigation. The problem has to do with copying old code onto a server when there's already compiled code for a newer version. This is the sort of thing one may do when backing out code versions on a prod or test server.

Of course, the automatic compile process works fine if the "new" code has a date/time stamp greater than the old compiled class file. That's the obvious time when it should recompile, but I've learned that CF has a little harder time dealing with when the date/time stamp of the "new" file is older than that already compiled, such as that described above in a version backout process.

The problems I've found are quite serious and very surprising.

The first problem is that if you copy the old file onto the server while CF is down, it WILL NOT detect the changed old code on restart. Wow.

Second, if you copy such old code onto the server while CF is up and running, it again WILL NOT detect the changed old code if--and this is a weird if--the file has not yet been browsed in the run when the copy is made. Further, even if you copy the old code while the server is running and you HAVE previously browsed the file, it's still not enough. You need to execute it after the copy, otherwise even after a restart it will still not see that new "older" code.

I know it may sound a little confusing, but here are some scenarios with more detail. First, the process of copying an old file while the server is down.

In the following, t1 and t0 are used to represent states of the file at points in time, with t0 being before t1:

filea is created/newly edited
filea (t1) is browsed
- auto-compiled into class under the covers
- is executed
- results show output of filea (t1)

server is stopped
old copy of filea (t0) is copied on to server
server is started

filea (t0) is browsed
- auto-compile does NOT create new class under the covers --- this seems the root of the problem 
- is executed 
- results show output of filea (t1), NOT the results of filea (t0)! -- even though t0 is the current code 

Also, note carefully that I indicated the stopping and starting of the server during which time the old file was copied. This is certainly a reasonable scenario, again as in when a production or testing server has code rolled back to a previous release. It's reasonable to expect that upon restarting the server, the old code should be newly compiled and executed, yet it's not. That's clearly a problem that needs to be solved. 

Now, perhaps more curious is what happens when the old file is copied onto the server while CF is up and running. It's not obvious. Indeed, only sometimes will it detect the changed "older" code. Sometimes it will not. No wonder people are pulling their hair out, both experiencing it and trying to recreate the problem. (If this is already known in support, sorry. I hadn't seen it explained anywhere before.) 

I've found that it depends on whether the file being replaced had been browsed/executed already at least once during the run of the server. If it HAD already been browsed/executed since the server was started and then an old version copied in, then it does detect the change--as long as you execute the page in that run after the copy. Otherwise, even after a restart, that new "older" code is not detected and executed. 

Further, if instead you make the copy during the run when it had NOT yet been browsed/executed since the server was started, then again it will NOT detect the change in that run or on restart. This is all very interesting. Here's a scenario: 

Run 1: 

filea (t1) is newly edited 
filea (t1) is browsed, autocompiled, executed, shows new (t1) output 

Run 2: 

filea (t1) is browsed/executed during this run 
- autocompiled, executed, shows (t1) output 
filea (t0) (older version predating t1) is copied onto server 
filea (t0) is browsed, autocompiled, executed, shows new (correct, t0) output 

That's what we'd all expect (or hope for), of course. 

But if we had not performed that last step of browsing the file after the copy, then even on a restart we'd still see t1! 

Further, if in run 2, you did NOT browse t1 during the run before copying in t0, again you will NOT see the output of t0 on a refresh of the page. You'll still see t1 from the previous compilation. And again, a restart of the server also won't fix this. 

So this is ugly. Really ugly. It seems old code copied in will only be executed a) if the server is up and b) the file has been executed in that run before the copy is made and c) the file if browsed/executed after being copied in. 

Has this been recognized? I'm running on the built-in web server, if that's significant, and running with the updater. 

I'd think this warrants a KB, and hopefully a fix. 

In the meantime, for those needing a workaround, there are a couple: 

First is that while the -f fails to work you could instead delete the corresponding class files from the server (in wwwroot\WEB-INF\cfclasses) before making the copy in of the old code (or at least before starting the server after making the copy). Then when you run the code there will be no class file there and CF will auto-compile it. 

Unfortunately, it's not trivial mapping a given filename (and its directory location) into the equivalent filename used in the cfclasses, to be able to delete just the files you'd be interested in. There's a hashing algorithm that I've seen explained but not well enough that I was able to devise (nor have I seen) a routine to do that conversion for us. If anyone offers it, I'll post it here. 

Another alternative may seem (and some have done this) to delete ALL the files in the cfclasses directory. Sadly, this is way overkill. The directory holds all the classes for all files in all directories (even virtually mapped ones outside the wwwroot), so deleting them all to just effect a change for one or even a few is a pretty high price to pay. It will force recompilation of everything, thus slowing down the server and the end-user experience of the first person hitting each page. While the precompile.bat may help if you then recompiled everything before restarting, this all just seems like hitting a gnat with a hammer. 

Another solution is simply to take advantage of the observation I made that if you start the server, then browse the templates, then make the copies while the server is up, and then browse them, then indeed the changes will be reflected. This is arduous if you need to automate the process (such as overnight), to automatically run the templates and then copy them and execute them again. Of course, you could use CFHTTP to execute the pages--and have it look at some list of files you are going to be copying. And you could use CFFILE to do the copying also from that list, and then use CFHTTP again to re-execute the newly copied files. 


I had said originally here that
One other idea (again, if we had the agorithm to map source dir/file names to their compiled cfclasses counterparts) would be to automate the process and have it CF automatically run a check at startup to find any files that have dates older than their previously compiled counterparts (to catch such copied older files) and run them so that if they were copied in during the run they'd "take" immediately on their next run. CFHTTP could be used to execute the code.

Now that I think about it, that's no solution at all. All that matters is that anytime old code is copied in during a run, the code needs to have been run once and then be run again after the copy in, in order for the change to be respected (for a new class to be created based on that older code). The bottom line is that there's no automation at startup to do. You simply need to manually run the code, copy it in, then run it again.

That still won't solve the problem of old code copied in while the server was down. It's not enough to execute it in the next run, you need to browse it in a new run, copy it during that run, and browse it again during that run). Otherwise the server will just never see that new "older" code that was copied in.

There are so many permutations and variables here that it's possible I've missed or left something out. Open to thoughts.

Hopefully, MM will recognize this and either has a fix coming or can add one soon. I know some updaters are planned to be released in the very near future.

BTW, my testing has been on the built-in web server. Don't know if that will have an impact. And again I do have the first updater applied. Don't know if things are different pre-updater.

Steve Ringo, manager of the South Africa CFUG, made this suggestion:

I was thinking about an easy workaround - how about this? Use a file "touch" utility to change all dates/times to "now()". This should be fine for a production server, as the date and time modified will still be intact for the development server, where the original date and time may be useful to have - for source control programs for instance.

I found a great freeware one at (See win32 Console ToolBox 1.0). It also can recurse subdirectories. Just pop this line into your precompile batch file:
touch /s *.cfm

You can add /q to "quieten" the output if you dont want a status report on each file - hence touch /s/q *.cfm

I haven't had the chance to try it yet in CFMX, but the utility works fine testing with some arbitrary files on my hard drive (with subdirs). AFAIK, the touch utlity is standard with most flavours of Unix (I dont think it has subdir recursion though).

Sounds like it could work. Thanks for that, Steve. One negative is that it would change the date/time of the file on the server which would make it out of synch with the place from which it was copied (whether another server, a develoepr's workstation, or a version control system). It's not a show-stopper, especially if it solve the problem, but it may annoy some as much as the problem. But otherwise worth a look. Thanks

No comments: