2

I have read a lot of posts about storing CFCs in the Application scope, and I understand that if a CFC stores data then it should not be in the Application scope. All CFCs that do non-util stuff would store data - when you pass in parameters like a username or email address - so I don't get when and when not to use the Application scope for a non-util cfc.

My question is that I have a posthandler.cfc component of about 500 lines of code which handles posts from a user (just like SO would handle each question being posted on this site). The posthandler.cfc component:

  • 'cleans' any images and text submitted by the user
  • places the images in the correct folder
  • writes all the text to a database
  • returns a URL where the post can be viewed

The returned URL is received by a simple Jquery ajax call which redirects the user to the URL.

This happens quite regularly on the site and at the moment a new CFC instance is being created for each post. Would it safe to put it in the Application scope instead and not cause race/locking conditions?

volume one
  • 6,800
  • 13
  • 67
  • 146
  • 3
    Put more succinctly, the general question I tend to ask is "what's is the impact of this function on *other* requests"? 1. Does the cfc *maintain* information beyond the one function call? 2. Does the function modify the input in any way? 3. If yes, is the input itself already stored in a shared scope? If the answer to any of those questions is yes, then either it's *not* safe for a shared scope OR it needs locking. In your case, it sounds like most of the information is not shared, and is request (user info, uploaded images, etc..), so it's probably safe to store in the app scope. – SOS Jun 02 '19 at 18:18
  • Correction: *3. If yes, is the input itself already stored in a shared scope?* ... s/b .... 3. Does the function modify the any data stored in a shared scope? – SOS Jun 02 '19 at 18:37
  • If you are talking about a service to save data then, that is OK. I do it all the time. – James A Mohler Jun 03 '19 at 03:33
  • 1
    Just be sure to scope everything properly. Lots of threads on S.O. about functions that *could be* thread safe, but suffered from race conditions due to the dev forgetting to localize one more variables. – SOS Jun 03 '19 at 07:31

1 Answers1

2

Just passing in parameters doesn't "save" anything. Conceptually, each thread has its own arguments and local scope, which are not visible to any other thread, and cease to exist when the function exits. So from that perspective, there's no conflict.

Also, storing data doesn't mean saving it to a database table. It refers to components that maintain state by storing data in a shared scope/object/etc.. With "shared" meaning the resource is accessible to other threads, and can potentially be modified by multiple threads at the same time, leading to race conditions.

For example, take this (contrived) component function that "saves" information in the variables scope. If you create a new instance of that component each time, the function is safe because each request gets it's own instance and separate copy of the variables scope to play with.

 public numeric function doStuff( numeric num1, numeric num2 ) {
    variables.firstNum = arguments.num1 * 12;
    variables.secondNum = arguments.num2 * 10;

    return variables.firstNum / variables.secondNum;
 }

Now take that same component and put it in the application scope. It's no longer safe. As soon as you store it in the application scope, the instance - AND its variables - become application scoped as well. So when the function "saves" data to the variables scope it's essentially updating an application variable. Obviously those aren't thread safe because they're are accessible to all requests. So multiple threads could easily read/modify the same variables at the same time, resulting in a race condition.

// "Essentially" becomes this .... 
public numeric function doStuff( numeric num1, numeric num2 ) {
    application.firstNum = arguments.num1 * 12;
    application.secondNum = arguments.num2 * 10;

    return application.firstNum / application.secondNum;
}

Also, as James A Mohler pointed out, the same issue occurs when you omit the scope. Declaring a function variable without a scope does NOT make it local to the function. It makes it part of the default scope: variables - (creating the same thread safety problem described above). This behavior has led to many a threading bug, when developers forget to scope a single query variable or even a loop index. So be sure to explicitly scope EVERY function variable.

 // Implicitly creates "variables.firstNum" and "variables.secondNum"
 public numeric function doStuff( numeric num1, numeric num2 ) {
    firstNum = arguments.num1 * 12;
    secondNum = arguments.num2 * 10;

    return firstNum / secondNum;
 }

Aside from adding locking, both examples could be made thread safe by explicitly using local scope instead. By storing data in the transient, local scope, it's not visible to other threads and ceases to exist once the function exits.

 public numeric function doStuff( numeric num1, numeric num2 ) {
    local.firstNum = arguments.num1 * 12;
    local.secondNum = arguments.num2 * 10;

    return local.firstNum / local.secondNum;
 }

Obviously there are other cases to consider, such as complex objects or structures, which are passed by reference, and whether or not those objects are modified within the function. But hopefully that sheds some light on what's meant by "saving data" and how scoping can make the difference between a stateless component (safe for the application scope) and stateful components (which are not).

TL;DR;

In your case, it sounds like most of the information is not shared, and is request level (user info, uploaded images, etc..), so it's probably safe to store in the application scope.

SOS
  • 6,430
  • 2
  • 11
  • 29
  • 1
    The only thing I would add is not putting any scope does NOT make a variable local to a function. It makes it object wide – James A Mohler Jun 04 '19 at 03:57
  • @JamesAMohler - Good point, that's worth mentioning more strongly. I'll update the answer. – SOS Jun 04 '19 at 10:06
  • I am using `var` to local scope all my variables. – volume one Jun 04 '19 at 11:06
  • Is there any need to consider the size of the `cfc` before putting into the Application scope? My handler is 500 lines of code, but I could see that going past 1500 soon. – volume one Jun 04 '19 at 11:11
  • 1
    I try and avoid big cfc's, but I have seen a few threads about [compiling issues](https://stackoverflow.com/questions/2029292/why-does-a-long-cfc-file-work-in-cf8-but-not-cf9-getting-branch-target-offset) with lengthy components, so that's always something to watch out for. Though IMHO, usually when you have a lengthy cfc, it's an indication the component is trying to doing too much, and should be refactored into several smaller, more focused components. – SOS Jun 04 '19 at 11:29
  • 1
    @volumeone My cutoff for any file is 10kB for any file. After that I tried to split it up. I have worked at places where 100kB files were common. – James A Mohler Jun 04 '19 at 13:21