Handling a slow process in a Model-Glue (or other MVC) application

This post is more than 2 years old.

This weekend I exchanged a few emails with a reader about how you can handle very slow processes in a Model-Glue application. Typically folks will handle slow processes using one of these methods:

  • cfflush: Print out a 'Please Wait' type message, use cfflush to flush out the content, and then start the slow process
  • cfthread: You can use cfthread to either run a bunch of parallel slow processes at once, or 'fire and forget' a slow process
  • scheduler: Use the ColdFusion scheduler to run the slow process completely outside the view of the site visitor.

Of course the best way to handle a slow process is to ensure you've done everything possible to speed it up. As an example, I was convinced that a particular process on coldfusionbloggers.org was slow because it had to be. Turned out it was a stupid SQL mistake on my part. So before any attempt is made to mitigate or hide a slow process, you need to do everything possible to ensure you haven't missed something obvious.

Once you've done that, what next? If you ever tried to use cfflush within a Model-Glue view, you know what happens:

Message Unable to perform cfflush.
Detail You have called cfflush in an invalid location, such as inside a cfquery or cfthread or between a CFML custom tag start and end tag.

Because your view file ends up being run as a custom tag (behind the scenes) you can't use the cfflush tag. So what about cfthread?

I created a simple demo application (available as a zip to this blog entry) using Model-Glue 3. I began by creating a new event, page.slow, that would represent my slow process:

<event-handler name="page.slow"> <broadcasts> <message name="doItSlow" /> </broadcasts> <results> <result do="template.main" /> </results> <views> <include name="body" template="pages/slow.cfm" /> </views> </event-handler>

The doItSlow controller method is where I put my slow process:

<cffunction name="doItSlow" access="public" output="false"> <cfargument name="event" type="any" required="true">
&lt;!--- First, am I running the slow process? ---&gt;
&lt;cfif structKeyExists(application, "slowprocess")&gt;
	&lt;cfset arguments.event.setValue("status", "ongoing")&gt;
	&lt;cfset arguments.event.setValue("progress", application.slowprocess)&gt;
&lt;cfelse&gt;
	&lt;cfset arguments.event.setValue("status", "began")&gt;
	&lt;cfthread name="slowprocess" priority="low"&gt;
		&lt;cfset application.slowprocess = 0&gt;
		&lt;!--- run 10 processes that take 1 minute each. ---&gt;
		&lt;cfloop index="x" from="1" to="10"&gt;
			&lt;cfset application.slowprocess++&gt;
			&lt;cfset sleep(15000)&gt;
		&lt;/cfloop&gt;
		&lt;cfset structDelete(application, "slowprocess")&gt;
	&lt;/cfthread&gt;
&lt;/cfif&gt;

</cffunction>

There are two main things happening in this method. If I see that a particular application variable doesn't exist, I begin the process within a cfthread block. I use an application variable, slowprocess, to both signify that I've begun the process and to record how far along I am. If the application variable does exist, note how I use the event object to record what's going on and how far along we are.

Now I don't think you would normally be running the slow process from the controller. This would typically be in the model with the controller simply firing it off and asking a service object (for example) for an update on what's going on. I only used the controller here for everything since I wanted a quick demo.

The view is interesting. I'm going to display the current status and do an automatic reload:

<cfset status = event.getValue("status")> <cfset progress = event.getValue("progress")> <cfset event.setValue("usemeta",true)>

<cfoutput> <b>status=</b>#status#<br/> <b>progress=</b>#progress# </cfoutput>

The usemeta is simply a flag to my template view:

<cfset usemeta = event.getValue("usemeta", false)>

<html>

<head> <link rel="stylesheet" type="text/css" href="css/stylesheet.css"></link> <cfif isBoolean(usemeta) and usemeta> <meta http-equiv="refresh" content="10"> </cfif> </head>

<body> <div id="banner">Demo</div>

&lt;!--- Display the view named "body" ---&gt;
&lt;cfoutput&gt;#viewCollection.getView("body")#&lt;/cfoutput&gt;

</body>

</html>

So the end result is - the person starts the process and can just sit back and watch as the page gives an updated status on the process.

Again - this is just a quick demo. It isn't best practice or anything. (In fact, it will continuously reload the process.)

So I was going to stop there. But why stop when you can try something cool? Many moons ago I blogged about XML/SWF Charts, a cheap, and very sexy, charting engine. One of the coolest feature is it's ability to point to an XML file to both configure the chart and create an auto-reload data set for the chart. What follows is video of a modified version (also in the zip) where the chart engine itself runs the request for the status. The resulting data is output in the XML format required for the chart.

I'm really impressed by this charting engine. It may not be as easy as cfchart, but it is certainly as pretty, and the auto-update for data is worth the price in itself.

So outside of the pretty charts - have folks done anything like the above code? (Again, ignoring the fact I used the controller.)

Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Martijn van der Woud posted on 10/28/2008 at 12:26 PM

Thanks very much for blogging about my question Ray, this is really useful for me (and probably for many others)!

Comment 2 by Joel Cox posted on 10/28/2008 at 6:31 PM

Another way to deal with a slow process that doesn't require a response to the user is to use a CFML Event Gateway. Just fire off an asynchronous event and be on your merry way.

Comment 3 by Raymond Camden posted on 10/28/2008 at 6:37 PM

@Joel - thank you for bringing up the EG. I completely forgot that!

Comment 4 by Marf posted on 11/5/2008 at 2:31 AM

Thank You!

Comment 5 by Don posted on 5/20/2009 at 11:16 PM

Now I have to sort this out for old fusebox. I wonder if this can be done in the onRequestStart and onRequestEnd functions of application.cfc

In your example, where would you put the "doitslow" function?

My thinking on this is that with fusebox using cfincludes, that I will have to include a template that starts the "LOADING" view and then includes the slow process itself. I'm leaning towards an iFrame tho.

Comment 6 by Raymond Camden posted on 5/21/2009 at 12:15 AM

doItSlow is a controller method.

Comment 7 by Don posted on 5/21/2009 at 12:30 AM

So that is specific to Model-Glue? I've never used that one but it sounds similar to Mach II.

Comment 8 by Raymond Camden posted on 5/21/2009 at 12:32 AM

All MVC-styled applications should have simularities.