Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Ray Camden
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Ray Camden@cfjedimaster )

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

By Ben Nadel on
Tags: ColdFusion

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.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader 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:

https://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.

Reply to this Comment

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.

Reply to this Comment

@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.

Reply to this Comment

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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.