4

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!

Phil
  • 4,029
  • 9
  • 62
  • 107

3 Answers3

3

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.

user12031119
  • 1,228
  • 4
  • 14
  • 3
    I'm not sure who down voted your answer, but I want to say thank you for your time and input. I have thought of this solution as well, however the process I am firing off in the thread is a single continuous process, so in order to make this method work I would need to add a check for this "flag" at multiple places through the process. As this would then need done on all the other areas that will use threading, I've decided to forget about it and simply let the request complete on it's own without trying to interrupt. Thank you for your time though, and welcome to SO. – Phil Sep 06 '19 at 15:13
  • @Phil Thanks for your kind words. This question actually helped me and got me thinking how I could have done things differently when I was in a similar situation by providing a workaround solution when a real one is currently unknown. Hopefully someone can provide the community with a more elegant java based answer than my kludgy but usable workaround. – user12031119 Sep 06 '19 at 20:20
  • 1
    @user12031119 - I think the person that downvoted didn't actually read the answer ;-) Using a flag isn't a bad idea, and is similar to some concepts used in java thread interrupts. Exiting gracefully, or in a known state, is critical for some processes. Kind of surprising cfthread doesn't support that. For non-critical processes, where it's okay to terminate in the middle without adverse affects, you could probably hook into some internal voodoo to get around that. – SOS Sep 07 '19 at 00:52
2

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) />
Dan Roberts
  • 4,664
  • 3
  • 34
  • 43
  • That works well for threads which are behaving correctly. I've implemented that scheme with files on disk, one file per thread, and in the thread `` - this way I can kill long-running threads by deleting the associated files. But if the thread mis-behaves it's much harder. For example if the thread calls a CF tag that simply never returns (happens to me with `` right now). – Tomalak Nov 04 '20 at 15:17
2

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

SOS
  • 6,430
  • 2
  • 11
  • 29