I'm working on creating a simple email server status page that calls two different CFCs.
The status page requirements:
- Query a MariaDB database table via a CFC and return data from two fields: server_name (ie. MyServerName) & server_domain (ie. mail.domain.com). Currently, there are 4 rows in the database table to pull.
- Hand the database data from step 1 to a CFC that checks if port 25 is listening. If the CFC can reach port 25 the result is true, if not the result is false. This step needs to be threaded.
- Hand the boolean result from step 2 through a loop to print the server_name and boolean result.
Output something similar to this:
MyServerName - <up arrow>
MyServerName2 - <up arrow>
MyServerName3 - <up arrow>
MyServerName4 - <down arrow>
The code:
RetrieveEmailServers = APPLICATION.selectQueries.RetrieveEmailServers()
if (RetrieveEmailServers.recordCount) {
for(i = 1; i <= RetrieveEmailServers.recordCount(); i++) {
LOCAL.theDomains = RetrieveEmailServers.check_servers_domain[i];
LOCAL.theNames = RetrieveEmailServers.check_servers_name[i];
thread action="run" name="thread#i#" theDomains="#LOCAL.theDomains#" theNames="#LOCAL.theNames#" {
VARIABLES.theServers = APPLICATION.emailCheck.checkSMTPServer('#domains#',25,'','');
}
}
thread action="join" timeout="6000"{}
for(i = 1; i <= RetrieveEmailServers.recordCount(); i++) {
VARIABLES.theResult = cfthread["thread#i#"];
if (VARIABLES.theResult.theServers) {
LOCAL.theStatus = "<i class='fad fa-angle-double-up text-success fs-1'></i>"
}
else {
LOCAL.theStatus = "<i class='fad fa-angle-double-down text-danger fs-1'></i>"
}
writeOutput(ATTRIBUTES.theNames & " - " & LOCAL.theStatus & "<br>");
}
}
else {
writeOutput("No servers listed at this time.")
}
The error: The key [THESERVERS] does not exist, the structure is empty
For consideration:
- I know my code is not great and I know it could be written better. I'm working hard to improve.
- I'm not a full time coder but I have been coding off and on for many years. I still consider myself a newbie with CFML so a lot of the methodology goes over my head.
- The above code mostly works but I'm having difficulty understanding how to pass information outside of a CFTHREAD to be used in the rest of the page, especially when dealing with CFLOOP.
- I have read many times, but still don't completely understand, how to correctly use the thread-local scope, the Thread scope and the Attributes scope.
- The code above has a simple task, to check a port, but the end goal is to use similar code for other parts of my application. I'm aware there are better monitoring tools available; this is an exercise to help me understand and learn.
- Specific to Lucee, I'm aware that
threadData()['thread#i#'].status;or similar may be a required modification tocfthread[].
Attributes
The
Attributesscope is only for holding values passed into a thread. Therefore, the scope is short-lived and only exists within a thread. Each thread has its own "attributes" scope, which doesn't exist before that thread runs or after it completes.For example, this snippet passes in an attribute named "theDomains". The variable
Attributes.theDomainsonly exists inside the thread.Thread-local
"Thread-local" is another short-lived scope whose purpose is to hold variables used only within a thread. Each thread has its own private "local" scope, separate from all other threads. Like the
attributesscope, it only exists while a thread is executing and is wiped out when the thread completes.For example, this snippet creates a local variable named "MyLocalVar". Displaying the thread
outputdemonstrates the variable exists within the threadBut attempting to access it after the thread completes will cause an error
ThreadScopeThe
Threadscope has a longer life-span. It's designed to store "..thread-specific variables and metadata about the thread...". More importantly, this scope can be used to pass information back to the calling page (or even other threads).For example, this snippet creates a thread scoped variable that's visible to the calling page, even after the thread completes its execution:
The Problem: key [THESERVERS] does not exist
When you've been looking at an error for what feels like days, it's easy to forget the basics :) First thing to do with undefined errors is dump the object and see if actually contains what you expected before trying to use it.
A dump of
VARIABLES.theResultshows the threads are actually failing with a different errorDue to the wrong attribute name within the thread. It should be
attributes.theDomains, notdomains.Okay, I fixed it. Still getting "key [THESERVERS] does not exist", now what?
Another dump of the threads reveals the error message wasn't lying. The threads really DON'T contain a variable named
theServers, due to incorrect scoping. Use thethreadscope, not `variables.Yet another error?! variable [ATTRIBUTES] doesn't exist
Even after fixing the previous two issues, you'll still get yet another error here
Remember the
attributesscope only exists within a thread. So obviously it can't be used once the thread is completed. Either storetheNamesin thethreadscope too, or since you're looping through a query, use the query column value,RetrieveEmailServers.the_query_column_name[ i ].Joining threads
One last potential problem. The join statement isn't actually waiting for the threads you created. It's just waiting for 6000 ms. If for some reason any of the threads take longer than that, you'll get an error when trying to retrieve the thread results. To actually wait for the threads created, you must use
thread action="join" name=(list of thread names) {}. I'll leave that as an exercise for the reader (:Tbh, there are other things that could be cleaned up and/or improved, but hopefully this long rambling thread explains WHY the errors occurred in the first place AND how to avoid them when working with threads in future