I'm working on creating a simple email server status page that calls two different CFCs.
The status page requirements:
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:
threadData()['thread#i#'].status;
or similar may be a required modification to cfthread[]
.The Attributes
scope 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.theDomains
only exists inside the thread.
thread action="run" name="thread1" theDomains="example.com" {
writeDump( attributes.theDomains );
}
thread action="join" name="thread1" {};
writeOutput( thread1.output );
"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 attributes
scope, 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 output
demonstrates the variable exists within the thread
thread action="run" name="thread1" {
// Un-scoped v
myLocalVar = "foo";
writeOutput( "myLocalVar ="& myLocalVar );
}
thread action="join" name="thread1" {};
writeOutput( thread1.output );
But attempting to access it after the thread completes will cause an error
// fails with error "key [MYLOCALVAR] doesn't exist"
writeOutput( "myLocalVar ="& thread1.myLocalVar );
Thread
ScopeThe Thread
scope 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:
thread action="run" name="thread1" {
// use scope prefix "thread."
thread.myThreadVar = "foo";
}
thread action="join" name="thread1" {};
writeOutput( "thread1.myThreadVar="& thread1.myThreadVar );
writeDump( thread1 );
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.
for(i = 1; i <= RetrieveEmailServers.recordCount(); i++) {
VARIABLES.theResult = cfthread["thread#i#"];
writeDump( variables.theResult );
/* temporarily comment out rest of code
...
*/
}
A dump of VARIABLES.theResult
shows the threads are actually failing with a different error
Due to the wrong attribute name within the thread. It should be attributes.theDomains
, not domains
.
thread ...{
APPLICATION.emailCheck.checkSMTPServer( attributes.theDomains, ... );
}
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 the thread
scope, not `variables.
thread ...{
thread.theServers = ....;
}
Even after fixing the previous two issues, you'll still get yet another error here
for(i = 1; i <= RetrieveEmailServers.recordCount(); i++) {
...
writeOutput(ATTRIBUTES.theNames & " - " & LOCAL.theStatus & "<br>");
}
Remember the attributes
scope only exists within a thread. So obviously it can't be used once the thread is completed. Either store theNames
in the thread
scope too, or since you're looping through a query, use the query column value, RetrieveEmailServers.the_query_column_name[ i ]
.
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