Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Rob Brooks-Bilson
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Rob Brooks-Bilson

Providing A Timeout To A Future Method Changes It To A Synchronous Blocking Call In ColdFusion 2018

By
Published in Comments (7)

As a quick follow-up to my previous post on Future Timeouts in ColdFusion 2018, I just realized that timeouts have a very curious side-effect: they turn asynchronous callbacks into blocking, synchronous calls. This appears to apply to both the runAsync() method as well as any chained .then() or .error() method.

To see this in action, all we have to do is provide a timeout to a runAsync() call and then try to execute a writeLog() directly after it:

<cfscript>

	try {

		future = runAsync(
			function() {

				sleep( 2000 );

			},
			// CAUTION: While the runAsync() method is normally asynchronous, providing
			// a timeout will turn runAsync() in into a BLOCKING, SYNCHRONOUS call.
			1000
		);

		// Try writing to the LOG and the Page. I'm writing to the Log in case the output
		// buffer is somehow being reset by the error (which wouldn't affect the log).
		writeLog( "Pre-get()" );
		writeOutput( "Pre-get() <br />" );

		future.get();

		writeOutput( "Done -- No Task timeout error. <br />" );

	} catch ( any error ) {

		writeDump( var = error, label = "Caught Error" );

	}

</cfscript>

If the runAsync() method is executing the callback asynchronously, then the writeLog() and writeOutput() calls should execute before the generated Future times-out. However, when we try to run this, we get the following ColdFusion output:

Future callbacks become synchronous when provided with a timeout in ColdFusion 2018.

As you can see, we get a Task timeout error with no preceding page output (and no Log output - not in screenshot). This is because the timeout value that we provided to the runAsync() method turned it from an asynchronous call into a synchronous call. As such, the writeLog() and writeOutput() calls never had a chance to execute.

The same appears to be true of the .then() and .error() calls. If we move the timeout from the runAsync() method to one of the chained methods:

<cfscript>

	try {

		future = runAsync(
			function() {

				// ....

			}
		).then(
			function() {

				sleep( 2000 );

			},
			// CAUTION: While the then() method BLOCKS and resolves the preceding Future,
			// the callback is normally asynchronous. However, when you provide a timeout
			// value, the callback becomes a BLOCKING, SYNCHRONOUS call.
			1000
		);

		// Try writing to the LOG and the Page. I'm writing to the Log in case the output
		// buffer is somehow being reset by the error (which wouldn't affect the log).
		writeLog( "Pre-get()" );
		writeOutput( "Pre-get() <br />" );

		future.get();

		writeOutput( "Done -- No Task timeout error. <br />" );

	} catch ( any error ) {

		writeDump( var = error, label = "Caught Error" );

	}

</cfscript>

... we get the exact same outcome: a Task timeout error with no preceding page or log output.

This feels like an unexpected behavior to me (and is not mentioned in the documentation that I can see). However, I'll admit that I'm operating with a Promise-oriented mindset that implies that the term "asynchronous" means "always asynchronous". Futures are clearly not Promises. With a Future - or, at least with a ColdFusion 2018 Future - "asynchronous" clearly means "sometimes asynchronous". I'm just trying to figure out when that "sometimes" caveat is applied.

Want to use code from this post? Check out the license.

Reader Comments

15,902 Comments

@All,

So, another interesting quirk around the Task Timeout error is that the .error() method in a Future chain does not appear to be able to catch the Task Timeout errors:

www.bennadel.com/blog/3493-the-error-method-cannot-catch-future-task-timeout-errors-in-coldfusion-2018.htm

But, there are some work-arounds, like using a Try/Catch block. Or, moving the timeout setting to an inner runAsync() call that won't change the control-flow nature of the outer Future chain.

27 Comments

Ben,

Please try the below code snippet -

try {

    future = runAsync(
	    
        function() {

         runasync(
		   function(){
            sleep( 2000 );
			},1000);



        }   

    );

    writeLog( "Pre-get()" );

    writeOutput( "Pre-get() ");

    future.get();


    writeOutput( "Done -- No Task timeout error. ");


} catch ( any error ) {


    writeDump( var = error, label = "Caught Error" );



}

You will see "Pre-get" printed in the browser. What I mean to say is that first level timeout check to the runasync is blocking. However, if you don't want your main thread to be blocked then you can have a nested runasync (as mentioned in the snippet above) which will not block main thread.

15,902 Comments

@Vijay,

Looks good -- it seems like the nested runAsync() call can account for both:

  • Catching timeout errors.
  • Ensuring asynchronous control-flow.

I've got some ideas on how to use the nested runAsync() to build some interesting functionality... we'll see how it shapes up.

20 Comments

Hi Vijay,

Can it please be documented that timeout and .then()/.error() are sometimes blocking? Also, can it please be documented that a workaround is nested RunAsync()?

Thanks!,
-Aaron

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel