Providing A Timeout To A Future Method Changes It To A Synchronous Blocking Call In ColdFusion 2018
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:
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
@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.Ben,
Please try the below code snippet -
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.
@Ben,
I have replied to this in corresponding blog post.
@Vijay,
Looks good -- it seems like the nested
runAsync()
call can account for both:I've got some ideas on how to use the nested
runAsync()
to build some interesting functionality... we'll see how it shapes up.Good to know that Ben. Please let us know if you have any further queries.
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
Sure Aaron, Thanks.