CFThread Must Die (Kill Bill) - Part 1
I saw an interesting question on stackoverflow.com last week asking how to monitor cfthreads when using the CF Standard edition. The Enterprise edition comes with a very slick Server Monitor. However, that is off limits if you are running the Standard Edition. As I am the ever curious type, I decided to do some investigating. After a lot of digging I eventually came up with some (highly experimental) code that seems to work. As the experiment revealed a few interesting things about the internals of cfthreads, I decided to post it for the curious masses.
FX - It's Just an Illusion
Let me say up front this was a learning exercise. The code is highly experimental. I am not gutsy enough to use home-spun code to manage threads on a production system, over a commercial monitoring product.
Commercial products have several advantages over most home-spun code. First, most monitors use listeners to hook into the server. So they are capable of intercepting events at an early stage and routing them directly to an appropriate handler. That is far more elegant than trying to handle events after-the-fact in custom code. Second, commercial products are likely to be more robust and typically more thoroughly tested. That is especially important when it comes to threads. Threads can be a magical thing indeed. But if you have ever spent hours chasing down obscure bugs caused by a race condition in a single line of code, then you have a tiny inkling of the extremely bad things that can happen when threads are mishandled.
O-k-a-y! Now that I have completely burst your bubble (and hopefully weeded out the reckless in the process) on to the example ..
The Java Connection
Since cfthread is just a wrapper around java.lang.Thread, my first inclination was to approach the problem from the java angle. So I hit the old java trail and did some reading to brush up on my threads.
Now given that you can identify the Thread of the current page with a simple bit of java code, I imagined you could use something similar to locate other threads running on the server. <cfscript>
CurrentThread = createObject("java", "java.lang.Thread").currentThread();
</cfscript>
Now threads can belong to ThreadGroups. ThreadGroups are a bit like directories. They have a hierarchical structure and groups can contain both Threads and other ThreadGroups (ie files and subdirectories). So in theory you could work your way up the chain to identify all active threads.
A handy snippet from the slightly dated javaalmanac.com site provided an example of how to traverse ThreadGroups and display all active threads. With a small modification I was able to output the names of all my active threads. (The image below is a much condensed version)
That is when I noticed something curious. All of the threads I created started with the name "cfthread". (You have to love development teams that adhere to naming conventions!). The thread status, or more accurately the Thread.State, is similar to cfthread statuses. But with slightly different names: NEW, RUNNABLE, etcetera.
So I decided to try a brute force approach and stop() any cfthreads in a RUNNABLE state. Now, it is worth noting that Thread.stop() was deprecated because it can be unsafe under certain situations. But as this was just an experiment, I decided to give it a whirl.
In the Event of a Zombie Attack...
It seemed to work. But in one or two of my tests, the threads seemed to die. Yet did not seem to be removed from CF's list of running threads. Since the Standard Edition is limited to ten (10) threads, when I tried to fire up new threads they never executed. They just kept piling up in the queue. I am not sure if it was an anomaly or a flaw in my testing. But that, combined with the warnings about Thread.stop() convinced me to try a different approach.
Dumpster Diving
After much digging and copious usage of cfdump, I found a promising method in the FusionContext class called getUserThreadTask(). Following the trail of this method, I discovered that cfthread runs as a type of scheduled task, under a worker thread. Considering the set-it-and-forget-it nature of threads, the fact that they are scheduled was not that surprising.
The Task class looked extremely promising. It seemed to contain all of the methods I would need for tracking and stopping threads. But it took a bit of work to figure how and when to use the getUserThreadTask() method. As I will explain in Part 2.
----------------
* On a totally unrelated note, in case you were wondering, no I did not see the zombie movie referred to in this entry .. thank goodness.
It is late. Time to pack it in before I turn into a pumpkin. Happy Halloween ;)