ColdFusion : terminate CFTHREAD in separate request

773 views Asked by At

I am using CFTHREAD in my ColdFusion application. From what I've read from Ben Nadel (https://www.bennadel.com/blog/2980-terminating-asynchronous-cfthreads-in-coldfusion.htm) ColdFusion only exposes and tracks threads in the current request. In my situation, I am spawning a thread via an ajax call and then providing the user with a cancel button. I was hoping the cancel button could call the terminate method on the thread, but no matter where I store it (application,server,session) ColdFusion always returns an error that it was unable to terminate thread "THREAD_NAME" because "THREAD_NAME" was not spawned.

I know that under the hood, ColdFusion is mostly Java. So I'm hoping that there may be a way. Could anyone either confirm or deny this possibility? Any example of how?

Thanks!

3

There are 3 answers

0
SOS On BEST ANSWER

Can you?

Yes, but only using internal classes. When the cfthread is created, use the local THREAD_NAME to retrieve a reference to the underlying thread object.

context = getPageContext().getFusionContext();
thread = context.getUserThreadTask( "theLocalTaskName" );

Since the local name can be used by multiple requests, the reference should be stored under a unique name, like a uuid. The reference is actually an instance of an internal class coldfusion.threads.Task. To terminate it, call its cancel() method.

thread.cancel();

Should you?

That's a big question and all depends on what the thread does - how it does it - and how the resources it uses would be affected if the process just stops dead, midstream, with no warning.

The reason is that calling <cfthread action="terminate"..> kills the thread - instantly. CF doesn't care if it's in the middle of a critical section. The server just whacks it with a mallet and stops it cold. The exception logs show that CF does this by invoking Thread.stop()

"Information","cfthread-47","09/07/19","17:10:44","","THREAD_V_2: Terminated"
java.lang.ThreadDeath
  at java.base/java.lang.Thread.stop(Thread.java:942)
  at coldfusion.thread.Task.cancel(Task.java:257)
  at coldfusion.tagext.lang.ThreadTag.terminateThread(ThreadTag.java:345)
  at coldfusion.tagext.lang.ThreadTag.doStartTag(ThreadTag.java:204)

The java documentation says stop() method is deprecated because it's inherently unsafe:

Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.

So it's important to consider what a thread actually does, and determine if it's even safe to terminate. For example, if a thread processes a file with FileOpen(), forcibly terminating it might prevent the thread from releasing the handle, leaving the underlying file in a locked state, which is undesirable.

The recommended way of stopping threads in java is with an interrupt(). That's essentially the concept user12031119 described. An interrupt doesn't forcibly kill a thread. It's just a flag that suggests a thread stop processing. Leaving it up to the thread itself to determine when it's safe to exit. That allows threads to finish critical sections or perform any cleanup tasks before terminating. Yes, it requires a little more coding, but the results are much more stable and predictable than with "terminate".

3
user12031119 On

Sorry, I don't have a 50 reputation to comment, so I'll post this as an answer. Recently, I was in the same situation with a CFThread spawned via ajax and I needed to terminate it somehow but was unable to. I had a CFQuery inside a CFLoop that used its datasource in the application scope. So what I came up with was to sign into ColdFusion Administrator and temporarily renaming the datasource which caused the thread to throw a database error. While it was inelegant termination, it served the purpose at the time.

So after seeing this question it got me thinking about a possible workaround if there isn't a known way to accomplish this. Suppose during your thread processing, it tests for the value of a variable in the application/server/session scope. Supposing the value is initially set to "true" and then subsequently set to "false" by another process, when the thread finds the false value, it can terminate gracefully.

1
Dan Roberts On

What you will want to do is setup a data structure somewhere like application or session scope that keeps track of threads running that you want to be able to cancel.

Application.cfc OnApplicationStart

<cfset application.cancelThread = {} />

Before entering thread create id and then pass into thread

<cfset threadId = createUUID() />
<cfset application.cancelThread[threadId] = false />

Pass the threadId back to the client for the cancel button. On click of the cancel button pass back the threadId

<cfset application.cancelThread[form.threadId] = true />

During thread execution

<cfif application.cancelThread[threadId]>
    <cfabort />
    <!--- or your chosen approach to ending the processing --->
</cfif>

If thread reached end then remove thread reference

<cfset structDelete(application.cancelThread, threadId) />