Heavy Handed, but effective. Passing entire scopes into CFTHREAD.
When you’re dealing with and lots of variables that exist in (potentially) many different scopes, one approach could be to pass the whole scope into the thread.
Recently, a company I work with wanted to improve the performance of their communication platform. Their SaaS website allows their clients to communicate with their customers by sending out mass emails; pretty common stuff.
When the client sends out an email, the ColdFusion template performs some pre-processing of the message.
- Add some preamble and some boilerplate to the message itself.
- Examine the “to” and “from” addresses, adjusting the sending mechanism if certain rules apply. (When the receiving domain is known to block messages from vendor A, send the email through vendor B, etc.)
- Create a record in the database recording the message, who it was sent to, what provider vehicle was used, etc.
Again, this is all pretty basic stuff, and was originally built over a fifteen years ago and maintained by different programmers.
Where is the Problem?
Here’s where the issue rears its ugly head. When a client was sending, say, 10,000 emails out to their customers, the template would:
- Query the list of users to send to.
- Loop over that list of users.
- Massage the body of the email.
- Massage the to/from addresses.
- Determine which vendor to use to send the email.
- Send the email.
- Record a record in the database.
- Repeat the loop until all of the emails are sent.
- Once all of this is done, display the results to the user.
Sending 10,000 emails, recording 10,000 records in the database, and the processing in between could take 5-10 minutes; all before the page loads and a confirmation is given to the customer. What is worse, the company has an all-in-one server. It’s IIS + CF + SQL Server + Mail Server (SmarterMail) all in one. It’s a powerful server, but it’s not immune to some process consuming 100% of the CPU and bringing everything else to a crawl. (I’m looking at you, SmarterMail.)
Imagine clicking send and having to wait for the system to completely determine who to send to, generate the message and send it before you even received a response from the page!
There are better ways!
Yes, of course there are better ways to handle this kind of task. You could offload the mail delivery to a service that’s not located on the local machine. You could spin up separate database servers. You could do a great many things.
However… time and money are factors in our scenario and the goal is to do one thing: Speed up the response to the customer. We chose to thread the generation of the emails and the insert into the database into a separate process. We could have used ColdFusion 2018’s new runAsync() function, but for this scenario, and to minimize impact and development time, the decision was made to wrap the generation of the email and the insert into the database with a <cfthread> tag and be done with it.
ColdFusion 8 introduced the <cfthread> tag, and honestly, it’s been very rare that I’ve felt a need to thread a process to run asynchronously. However, <cfthread> comes with some gotchas. Specifically, since the thread is running as an independent process, any variables that are needed have to be passed into the thread as attributes before they can be used. The legacy application I was working with pulled data from all sorts of different scopes; some data was pulled from the application scope, some from the session, some from a form scope, and some from the variables scope.
Originally I tried to pass in specific variables as I needed them, but since threads are running as independent processes, they’re more difficult to troubleshoot without liberal use of logs, try/catch blocks and emailing yourself scope dumps. I ran into a problem where I thought I was passing everything in properly, but the system still wasn’t working as expected. The approach that I ended up using looked like this:
<cfthread name="myThread application="#application#" session="#session#" form="#form#" variables="#variables#" > [Generate the emails and insert into the database here] </cfthread>
…which seems to me to be very heavy handed. I described it to another developer as “sandblasting a soup-cracker” but it was 100% effective. Maybe this whole exercise is an example of what not to do, but in this case, it solved the problem. Needless to say there are lots of opportunities for improvement in this template moving forward.