Tuesday 4 August 2015

CFML: revision to thread syntax (revisited)

G'day:
Whilst messing around JavaScript Promises ("JavaScript: getting my brain around Promises" and "JavaScript: expectations re-adjusted re Promises") a week or so ago, I also started looking at how CFML's innate multi-threadedness, and how one would leverage this along with a CFML Promise implementation to be able to have clean & tidy & OO asynchronous code.

The general concept of firing off secondary threads in CFML is adequate, but I think the implementation leaves a bit to be desired. I'm going to forget the tag-based implementation as that is an artefact of more primitive times in CFML, and focus on the script implementation.

This is an example of lazy dull-headedness from the Adobe ColdFusion team who simply did a "tags-without-angle-brackets" implementation of <cfthread> for the script:

thread name="t1" action="run" {
    // stuff to run in its own thread here
}

This is just leaden.

A while back I mused about some improvements to the syntax ("CFML: Another suggested tweak to script: thread" and then "CFML: A possible variation on the thread idea..."). Initially I just started out suggesting changing the syntax to be a hybrid of standard OO script syntax and tags-without-angle-brackets notation:

t1 = Thread.new({someattribute="value"}) {
    // stuff to run in its own thread here
}

And then further to this:

t1 = Thread.new(required function functionToRun, optional struct dataToPass, optional struct eventHandlers);

This would result in code like this:

t1 = Thread.new(function(){
    // stuff to run in its own thread here
}, {}, {
    success = function(){
        // stuff to do after the first function finishes
    },
    failure = function(){
        // stuff to do after the first function fails
    }
});



This is an improvement, but it's got one glitch in it I think: it could use closure instead of needing to pass the dataToPass structure. Also it's using a very bespoke implementation still. This is OK in an enclosed CFML world, but we don't live in this world any more, so we might as well look at how the outside world might implement this.

We could perhaps have this Thread class, but also have it implement a Promise interface. What about this:

t1 = new Thread(function(resolve, reject){
    // stuff to run in its own thread here
    
    if (itCompletedOK){
        resolve(result);
    }else{
        reject(reason);
    }
});


Which can then have its event handlers bound to it via the Promise contract:

t1.then(function(result){
    // stuff to do after the first function finishes
}, function(reason){
    // stuff to do after the first function fails
});


And still be able to do Thread-specific stuff:

Thread.join([t1,t2,etc]);

This approach doesn't require any special block constructs, and - back to the fact it uses closure - doesn't require the clumsiness CFML currently has that passed-in values aren't all deep copied; they're simply referenced.

Actually, revisiting the initial code, I wonder if the resolve()/reject() process could be streamlined?

t1 = new Thread(function(){
    // stuff to run in its own thread here

    return result;
});


Where the return value is handled the same way as a Promise's resolve() call would handle it. Conversely In an error situation the exception is internally caught, and wrapped in a ThreadException (or something) object, and that is passed to any rejection handler, as if it was passed using a reject() call. If one needed to complete the threaded process with a failure which itself didn't cause an exception, then one could actively raise said exception:

t1 = new Thread(function(){
    // stuff to run in its own thread here

    if (thereWasSomeProblem){
        throw(ThreadException, reason);
    }
    
    return result;
});

This would just leverage existing functionality, and not require having special resolve() / reject() calls in the threaded code's body. The question here is whether the exiting of the thread code in a non-successful fashion is an exceptional situation, so the usage of exception-handling is sensible? I think a reasonable case for this could be made.

Thoughts?

--
Adam