Saturday, 13 April 2013

"Request functionality" doesn't not work with CFThread

G'day:
That was an intentional double negative.

This is a follow-up to my article yesterday, questioning why Adobe / Shilpi / Rupesh have said not to use "request-related functionality" within CFThread, and specifically choose to mention this when releasing a security update for ColdFusion 9-10. This makes me concerned as to why they are telling us this. I am moderately confident it was not simply a CFML tip for the community (because that'd be a first, surely, if nothing else ;-), there's a problem they're not telling us about.

The guidance was a bit vague, so I've looked into it more.


Shilpi gave no detail as to why she was telling us this at all, but Rupesh clarified by pointing out threads spawned by <cfthread> can continue after the main request is finished, therefore there's no point performing actions like manipulating cookies, HTTP headers, MIME types etc, because there'd be no point. So kinda similar to having output generated in a thread (but this is suppressed already).

Basically because the response to the request might have been already sent back to the browser by the main thread (ie: the main request) before the spawned threads try to perform this HTTP-response-manipulating activity, one shouldn't do it.

This has a ring of truth to it, but consider this. Not all threads are simply fired off and forgotten. Pretty much every time I've used additional threads to do something, it's just because I can parallel process some parts of a request, but I still want to hold off sending the response until all that processing is done. So I fire off some threads, then wait for them to finish before continuing with my main request. IE: I use <cfthread action="join"> pretty much all the time. Now that's not to say one never does the "fire and forget" type thread spawning, but there's certainly both use cases there.

So in the situation where the spawned threads are all known to have completed before the main request's thread will complete, can one still do this "request-related functionality"? Well... here's some code that emulates that very situation:

<cfscript>
    function delay(){    // emulates processing that takes some time
        sleep(randRange(1,5) * 1000);
    }
</cfscript>

<cfset start = getTickCount()>
<cfthread action="run" name="dealWithHeaders">
    <cfset delay()>
    <cfheader name="from" value="adam@example.com">
</cfthread>
<cfthread action="run" name="dealWithCookies">
    <cfset delay()>
    <cfcookie name="testCookie" value="#createUuid()#">
</cfthread>
<cfthread action="run" name="dealWithContentType">
    <cfset delay()>
    <cfcontent type="text/plain">
</cfthread>
<cfthread action="join" name="dealWithHeaders,dealWithCookies,dealWithContentType" />

<cfoutput>
Individual threads:
<cfloop collection="#cfthread#" item="thread">
    #cfthread[thread].name#: #cfthread[thread].elapsedTime#ms#chr(13)#
</cfloop>
Total: #getTickCount()-start#ms
</cfoutput>

Here I have a number of long-running threads, which perform activities on HTTP headers, cookies and the MIME type respectively, and then once they're finished, the request continues. This sort of thread processing is not completely far-fetched.

The output of this was this:

Individual threads:
DEALWITHHEADERS: 5001ms
DEALWITHCOOKIES: 2002ms
DEALWITHCONTENTTYPE: 2001ms

Total: 5004ms


But more importantly the relevant header information from the HTTP response was:

Content-Type    text/plain;charset=UTF-8
Date    Sat, 13 Apr 2013 22:18:22 GMT
From    adam@example.com
Server    Apache-Coyote/1.1
Set-Cookie    TESTCOOKIE=36C6B0E8%2D215D%2DAD32%2DCEE0B63A847AA23B; Path=/
Transfer-Encoding    chunked


So it works fine, and exactly as I intended it.

For the sake of completeness I tested this with the JOIN removed, and - obviously - none of those headers were set, because the code to set them occurred after the main request was done. But this is obvious? I mean... even a bit too obvious for Shilpi / Rupesh to make a point of mentioning, surely? Purely on the basis of what they've said, this can't qualify as "unexpected results", can it?

I still think there's something more to this than they're letting on. The thing is, if there is more to this than they're letting on, then a simply one-liner in an unrelated blog entry is a negligently poor approach to making sure people know about this. The docs for <cfthread> should be updated, as well as those for <cfheader>, <cfcookie>, <cfcontent> [etc]. Plus they should make a specific comment covering it on the blog.

If there's nothing else afoot, it's just a really bemusing thing to stick in that original blog entry. But the upside is I tested the code above, and I didn't know whether it would work or not, and now I know it does. And I got two blog articles out of it.

--
Adam