NOTE: The code in this post can be very misleading if you don't read the explanations that go with it!!
When I was playing around with some ColdFusion custom tags, I stumbled upon some interesting behavior in ColdFusion 8's per-application settings. From what I can see and re-create, the CustomTagPaths property is cached for the life of the application; but, the Mappings property, on the other hand, is not cached at all and needs to be defined in every page request that uses them. To demonstrate this, I have put together a short demo that uses the CustomTagPaths property to execute a custom tag and the Mappings property to create and utilize a ColdFusion component:
As you can see from the video, THIS.CustomTagPaths can be set once for the application and then never set again; THIS.Mappings, on the other hand, needs to be set on every page request. Now, I've seen people in the blogosphere and on the mailing complain that they are getting some intermittent errors with ColdFusion 8's per application settings and I wonder if it has something to do with this partial caching behavior?
To work in alignment with this caching strategy, I considered rearchitecting the way in which I define my per-application settings. In particular, I thought that I should probably only set the CustomTagPaths property once in the OnApplicationStart() event method and the Mappings property in the pseudo constructor such that it will be set on every page request:
<cfcomponent output="false" hint="I define application settings and event handlers."> <!--- Define application settings. ---> <cfset THIS.Name = "TagPathDemo" /> <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 0, 15 ) /> <!--- Define custom mappings. These need to be set on EVERY page request. Put in psuedo constructor so all events can leverage them. ---> <cfset THIS.Mappings[ "/com" ] = ( GetDirectoryFromPath( GetCurrentTemplatePath() ) & "components\" ) /> <cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="I fire when an application needs to be initialized."> <!--- Define ColdFusion custom tag paths. Since these values are cached, they only need to be set once when the application is intialized. ---> <cfset THIS.CustomTagPaths = ( GetDirectoryFromPath( GetCurrentTemplatePath() ) & "tags\" ) /> <!--- Return true so page will run. ---> <cfreturn true /> </cffunction> </cfcomponent>
This makes sense based on the behavior, but unfortunately does NOT work. When I run the page with this Application.cfc configuration, I get the following ColdFusion error:
Cannot find CFML template for custom tag helloworld.
So, it seems that the CustomTagPaths property gets cached, but the behavior is not quite so straightforward. So what next? I tried moving it to the OnRequestStart() event method. Also no luck - same error. It appears that the per-application settings need to be set outside of any of the application event handlers.
At this point, we've run out of elegant solutions - if we don't define the CustomTagPaths in the pseudo constructor, they don't work; however, if we do define them, we set them unnecessarily; and finally, if we don't set them, we can't really check for their existence since they are not available in the THIS scope when not set explicitly.
The only thing I could think of was trying to execute a custom tag, catching any errors, and then defining the custom tag paths if necessary:
<cfcomponent output="true" hint="I define application settings and event handlers."> <!--- Define application settings. ---> <cfset THIS.Name = "TagPathDemo" /> <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 0, 15 ) /> <!--- Define custom mappings. These need to be set on EVERY page request. Put in psuedo constructor so all events can leverage them. ---> <cfset THIS.Mappings[ "/com" ] = ( GetDirectoryFromPath( GetCurrentTemplatePath() ) & "components\" ) /> <!--- Try to execute the tag check custom tag. If this fails (throws an exception), then we need to initialize the custom tag paths. If it DOES work, then the paths have been initialized and cached. ---> <cftry> <!--- This tag doesn't do anything. ---> <cf_checktagpaths /> <!--- Tag failed - Define custom tag paths. ---> <cfcatch> <cfset THIS.CustomTagPaths = ( GetDirectoryFromPath( GetCurrentTemplatePath() ) & "tags\" ) /> </cfcatch> </cftry> </cfcomponent>
As you can see, I am defining the CustomTagPaths property only if the custom tag "CheckTagPaths.cfm" failed to execute.
Not only is this very sloppy looking, it also does NOT work; the CheckTagPaths.cfm custom tag throws an exception on every request. From further experimentation, it seems that you cannot utilize the ColdFusion 8 per-application settings within the pseudo constructor itself - you have to wait until one of the application event handlers.
In the end, it appears that all of this exploration is for naught - the CustomTagPaths get cached for the life of the application, but there is no way for you to leverage that fact that I can find.
UPDATE: Just a quick update based on the comments that Ray Camden mentioned below. I had no idea that ColdFusion has cached tag references; however, after some quick testing, I have verified that it is not the custom tag itself that is being cached in my experimentation, but rather the directory:
Ben, I skimmed this. I did NOT read it closely. But are you forgetting that CF remembers the location of a custom tag? If you call cf_foo, and it finds it in path N, cf will never look for cf_foo elsewhere. This is less a App.cfc bug and just a custom tag behavior that has not changed since CF5.
Again, forgive me if this is not the issue.
REALLY!! I did not know that! Is that really true? Now I have to go test :)
Yep. Super old behavior. Maybe even back to when CTs were added (CF3 I think).
Of course, cfmodule doesn't have this problem.
I just tested this and it is not the case - even when I put in another, not previously used tag, the mapping still exists.
Ah ok. Well, if you ARE changing the CT path during dev, it is still important to remember. Once you run cf_foo and CF finds it, you are screwed.
I had never seen that before. Thanks for letting me know.
Have you actually seen errors with per-application settings under load? I ask, because I too have heard of issues, but haven't actually seen them.
Sidenote: I like the screencasting that you've been doing lately. It's much more approachable than a wall of code -- you're still getting the wall, but having it spoken and shown visually just works better for me, I guess.
Angela and I have been dealing with this for quite some time. We ended up defining the CustomTagPaths outside any contructors, but then defining it again in the onRequestStart() to get around some race conditions we were experiencing.
I think the CustomTagPaths is definitely something that needs to be worked on for CF9.
In your second demo, I didn't see that you commented out the custom tag path before running the second custom tag (helloworld3).
Sorry about that, I just re-did the video. I realized that right after I posted :)
Thanks man. I really like the video walk throughs. I am not sure where the right balance is between code / video because the screen size is so small. If I can find a nice way to record a larger video, but still have it fit inside my blog content, I might go that way as I think it would provide more value.
I have not experienced this error myself; so it's hard to know what does or does not contribute to it exactly.
@RickO: (Ben, forgive me for hijacking this a bit.) I've used Jing a few times, but never really got any good feedback on it. You think it is worthwhile using more often?
Cool. Just wanted to make sure that wasn't the reason it was working...
No problem :) I love Jing, but I want to find a better way to record larger dimension videos yet still embed easily.
@Ben & Ray - I dig the screencasts.
Off-topic (regarding Jing): Aren't you just limited by what your machine can handle? I have Jing Pro and I can record any size I want even a thin 200x1000 video strip? There are limitations, but size isn't one of them.
The video does have the option to go full screen and it looks fine. You have to hover over the video and you'll see a tiny bar at the bottom, it's too small to see the full screen button, but it's there.
1) Right click on the video and Zoom In
2) Drag the video to the left
3) Click the full screen mode
4) Righ click and zoom out a couple of times
5) You may have to reposition the screen
Sorry, I just tried a simpler method:
1) Right click and choose show all
2) Click the full screen button
This bit us recently with the mappings and how part of our platform worked.
All of our applications live in the same CF application and each has a section of the application scope. I had it defining an auto mapping for the root of each sub-application (which is outside the webroot) at the start of every request.
this.mappings["/" & listLast(getRootPath(),"\/")] = getRootPath()
I started seeing random errors about not being able to find certain components, but I couldn't duplicate it... until I decided to cross application load test.
We had also setup explicit mappings in the Admin for most applications from back when we were on CF7 and this had been masking the fact that the implicit root mapping sometimes would vanish! So only new apps that didn't have CF Admin mappings would fail.
So the moral of the story is that *every* request shares the same this.mappings, but it also has to be initialized at the start of *every* request with the *exact same values*. Otherwise requests that are currently running can have their mappings up and vanish in the middle because of a different request!! :/
We also ran into huge problems with the per-app customtagpaths and what seemed like some race time trouble. I'd be interested in seeing the code you used to get around these issues.
Yeah, that's true. I guess I'm just more worried about the embed code; I guess it's cause I never really thought about it. I could always record a large video and then just scale it down for the embed code :)
What would be good would be to allow us to put the values in the OnApplicationStart() event handler. That way, we could keep them to a single setting.
Just wanted to say that I loved the screen capture used in the post Ben. It's the first I've seen you use and actually came here to see how you were using Jing after we talked about it on Twitter.
It worked brilliantly in this example and engaged me a lot more than just reading the blog post.
I like how it really engages you to the viewer as well helping to get the content of the post across.
I don't think the screen size matters too much - at least not in this example. Where more width is needed the bigger screen cap with smaller embed code would do the trick nicely I'd think.
Nice to be able to play it embedded though where possible.
Interesting post by the way. Not something I was aware of and useful to know.
A note here from our conversation on this - if you were to restart your server instance after you commented that out custom tag path I'd expect that template you executed to fail - and not find the CT path - (as the cache would no longer be available). Is that what you found - I'm assuming so?
I assume so, but have not tested that.
Very interesting detail regarding this topic, is How Ben forget to say that he has written this post AFTER I've send him an email regarding problems with application specific customtags mappings, on an application that is build using HTML frames that loads different coldfusion pages in each frame.
I've offer obviously a PROBLEM not a solution, but is sad to see how people can ommit some details.
I'm not need to be named here is not the idea, I've not ego problems.
IMHO instead of starting post saying " .. While I was playing...",
a better approach can be " I've received an email regarding problems with ...., then I've decided to play a little bit to ...' .
@Francisco: Uh... wait. What?! It's his blog, he can write it how he wants? o_O
Here is the email that you sent me:
> I've have started using Application.cfc on CFMX 8 because I want to
> be able to set different customTagPaths for every application hosted on my CF
> server. I have found a problem because my app uses frames (and I can not
> rewritte app). Seems initalization of this.customTagPaths is corrupted, because
> app can not find custom tags after login page (has no frames) when entering main
> screen (has 3 frames). Any hint?
To which, I tried to write a blog post. Unfortunately, I couldn't turn your idea into anything which is why I then told you (CC'ing gualtiero.sappa and alessandro.lia):
Francisco, My blog post was a bust. Sorry.
The blog post I tried to write for you involving frames is completely unrelated and was not something that I could solve. This blog post (the one above) has nothing to do with your email; there is no reference to frames, no reference to login pages. The only thing that your email and my blog post have in common is that they work with custom tag paths... which is a big part of my Custom Tags presentation I've given months ago and will give again at CFUNITED. It is in the research for this presentation that I first discovered partial caching of custom tags.
I am sorry that you feel that I have somehow skipped over you or not acknowledged you; I hope that you see that on my blog, I give credit for things as often as possible - I love giving people credit for stuff. I am sorry if you were confused as to where this blog topic came from.
I would like to apologize for my last comment - I definitely came off as very defensive (followed by offensive). I felt attacked and I should have followed up with @Francisco via email rather than publicly.
While my blog post is not related to the question he originally asked me, I believe our misunderstanding is due to a strong language barrier that skewed communication. I am sorry that I let me emotional side take over my response.
@Ben: I wouldn't worry about it or lose sleep over it. The whole thought of this is just silly. You certainly don't have to defend your every post or where your muse strikes from.
I am having a similar issue. I have specified a per-application customtags path in my application. For the most part it is working just fine, however, when custom tags are being used within CFC methods or scheduled tasks, I'm gettting an exception saying that it cannot find the custom tag. I noticed a bug fix in the latest CF8 hot fix (http://kb2.adobe.com/cps/511/cpsid_51180.html) is supposed to fix this issue, but after applying it, the same behaviour is occurring.
Does anyone have a solution for this? I don't want to rely on custom tag paths in the administrator. I want my applications to be self-contained.
I've got no good advice on this one, sorry.
We ran into an issue where we had intermittent errors where it couldn't find the tag.
Our this.customTagPaths gets set to something like "#expandPath('/path1')#,#expandPath('/path2')#". We recently moved a folder from path2 to path1 and a page that uses a CT in that folder works great 99.9% of the time. In our logs I even see someone hitting a given page which uses one of the relocated CTs 15 times in the same session and the 16th time it gets amnesia and can't find the tag.
I sort of wonder whether there's 'weird' going on where it sometimes decides on the wrong path and if it can't find it, decides not to look in the other path.
Could be a bug in the CF, or a race condition as mentioned above. I'll probably end up using cfmodule since that is save the other 0.1% of the time as well. :)