Getting a handle on Application variables can be difficult. Even folks who know what they are sometimes use them incorrectly. There are 2 common forms of simple application variables (we'll leave the discussion of objects and CFCs for another time). There is the "set once read many" variable. Usually these are "settings" for the application - things like paths and data source names - Application.dsn, or Application.imagePath.
Then there is the "global tally" variable. This is less common, but it is usually some variable that is incremented or updated throughout the life of the application. For example, it might be a variable to track the number of logged in users. This variable is actually set (sometimes frequently) as the application is used. The main thing to remember about an application variable is that it lives beyond the request or session. It's something that is put in place as a variable and stays put for every user of the application. Some call it persistence - but the OO purist pee their pants when they hear that so I will refrain (too bad too, it's a good word for it). Let's focus on that first example of "set once read many".
Take this example:
It's a trickier question than you might think. The answer is, if you are using simple variables and no custom tags or CFCs and your code organization is easy then you may not need these variables as application variables. You might just as well use:
Here's a tricky gotcha that will drive you crazy the first time you see it. Developers are used to dealing with an Application as a collection of files. Usually these files are part of the same folder structure (though not always). So, for example, we start a site or application with a "root" for the application, and add files and folders into this folder or sub-folders. For Example:
/application.cfmWe know that when we access the file "userEdit.cfm" in the above example, that the "application.cfm" file is run first - and any variables are calls made in that file will be available inside of the included file.
/index.cfm
/forms/userEdit.cfm
/display/userList.cfm
... and so on...
What you may not know is that the Application variables are not tied to the folder structure. They are tied to the "name" of the application. Remember the "name" part of the cfapplication tag?
Why is this important? If you are like me, you do a lot of code reuse. You might, for example, copy an application.cfm file from another application because it did basically what you wanted and just needed some minor adjustments. If you don't change the name of the application, the 2 application.cfm files will share the same scope. So, for example, if you change the data source name and then reset the application variables, your first application will break because it's now pointed to the wrong data source
Try this experiment. Create folder A and folder B. In both folders create a cfapplication tag with the same "name". Then add a set statement like this.
A friend of mine developed a "franchise" application. He sold web sites to franchise members for the parent company. When a franchise signed on his plan was to create a new folder, copy in his franchise site code, customize the application variables, and point the user to the admin page for setting up his data. When the program launched he had 8 or 10 franchises sign up right away. He followed his plan and sent them all emails the first day. Each user would sign on and edit his information. You guessed it. Because they were all sharing the same scope, one user was overwriting the information of another. They competed all day to get their individual sites updated, and they were frustrated that it kept reverting to some other franchise's data. The fix? Go into each site code and change the name of the application. That's it.
In your first example, you're not setting values that change from request to request so: (a) you don't really need to lock since you can't get different results in a multi-threaded environment (b) you might as well you request scope instead and avoid the whole issue.
However, let's assume you're doing some complex one-off initialization of app scope variables... If you're setting atomic or non-dependent data, you still really don't need to lock (because it wouldn't matter if you initialized things twice). So let's assume your initialization is sensitive to multi-threading issues... As you have the code, it isn't thread safe. Two threads could both hit the condition and both succeed, one locks and performs the initialization, the other thread waits for the lock and then performs the initialization again.
In order to be safe when initializing complex data, you need to do the following:
cfif someCondition
cflock name="someName" type="exclusive" timeout="nnn"
cfif someCondition
do the initialization
/cfif
/cflock
/cfif
You need the test inside the lock to prevent double initialization. You also want the test outside the lock to avoid locking in every request. And you want to use a named lock so that you don't block on the entire application scope. Named locks allow you to lock just for the stuff you want, letting other code in other requests execute their own named locks independently.
Thanks - that's an excellent point.
Let me see if my pea brain understands this. Thread A and B both complete for the lock. Thread A wins and initializes the data while threed B waits. Thread B does NOT initilize because of the second condition inside the lock - is that right?
That's really cool. Thanks for contributing!
Thread A hits the outer condition, succeeds, gets the lock, re-tests the condition and initializes stuff. Thread B hits the outer condition and fails (everything is initialized). [Thread B represents every subsequent request as well]
Or:
Thread A hits the outer condition, succeeds. Thread B hits the outer condition, succeeds. One of the threads gets the lock, the other waits. The thread with the lock re-tests, succeeds and initializes stuff. The blocked thread now gets the lock, re-tests the condition and fails (because the other thread initialized stuff) so it just releases the lock. [Any subsequent requests behave like thread B in the first scenario above]