Skip to content

Instantly share code, notes, and snippets.

@bennadel
Last active February 19, 2020 13:21
Show Gist options
  • Save bennadel/5a9fa02eaca51b16f5b5969427e7a469 to your computer and use it in GitHub Desktop.
Save bennadel/5a9fa02eaca51b16f5b5969427e7a469 to your computer and use it in GitHub Desktop.
Experiment: Wrapping CFThread Execution In A FusionReactor Tracked Transaction In Lucee CFML 5.2.9.40
<cfscript>
segment = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
.getInstance()
.createTrackedTransaction( "MyCodeSegment" )
;
try {
// ... the code you are instrumenting ... //
} finally {
segment.close();
}
</cfscript>
<cfscript>
// Instead of just going directly to a CFThread for asynchronous processing, I'm
// going to see if I can wrap the CFThread interaction inside a FusionReactor
// "tracked transaction" such that I can see how this CFThread is operating and
// how it correlates with system resource utilization.
// --
// This takes the following arguments:
// 1. Name of the FusionReactor transaction.
// 2. Arguments to be passed to closure.
// 3. Closure to execute asynchronously.
// 4. [OPTIONAL] Closure to handle errors.
runSegmentAsync(
"SendFriendMessagesAsync",
{
friends: [ "Sarah", "Max", "Annie", "Timmy" ]
},
( friends ) => {
var messages = friends.map(
( friend ) => {
return( formatMessage( friend ) );
}
);
// Simulating some latency to make the graphs more interesting.
sleep( randRange( 10, 50 ) );
// Simulate "sending" messages.
systemOutput( serializeJson( messages ), true, true );
}
);
/**
* I format a message for the given friend.
*
* @name I am the friend's name.
*/
public string function formatMessage( required string name ) {
return( "Hello, #name#, how goes it?" );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I run the given callback asynchronously inside a CFThread and wrapped in a
* FusionReactor tracked transaction with the given name.
*
* @segmentName I am the name of the FusionReactor tracked transaction.
* @callbackArguments I am the argumentCollection passed to the thread and callback.
* @callback I am the callback to invoke asynchronously.
* @errorCallback [OPTIONAL] I am the error handler callback.
*/
public void function runSegmentAsync(
required string segmentName,
required any callbackArguments,
required function callback,
function errorCallback
) {
// Since the execution of a CFThread can be a bit of a mystery (from an
// debugging standpoint), let's include the StackTrace in the FusionReactor
// transaction description.
// --
// NOTE: In a production context, I would probably just rely on "SegmentName" to
// aid in debugging since I would be afraid that the callstackGet() call would
// end-up being a performance hit? Possible just unnecessary worry there, though.
var callstackItems = callstackGet().map(
( item, i ) => {
// The first item in the callstack will always be "THIS" line, which
// doesn't add any insight into the processing. As such, let's replace
// the first callstack item with a descriptive value.
if ( i == 1 ) {
return( "Async segment tracking for CFThread." );
}
return( item.template & ":" & item.lineNumber );
}
);
// Inside a CFThread, FusionReactor seems to have trouble figuring out which
// "app" the spawned thread is supposed to be associated with. As such, let's get
// the app name from the current transaction and pass it through to the thread
// where it can be programmatically propagated in the tracked transaction.
// --
// CAUTION: In a production setting, you'd have to check to make sure this class
// is available AND that the .getInstance() call returns a non-null value.
// However, for the sake of simplicity, I'm keeping this demo naive.
var transactionAppName = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
.getInstance()
.getActiveTransaction()
.getApplicationName()
;
thread
name = "runSegmentAsync-#createUniqueId()#"
action = "run"
segmentAppName = transactionAppName
segmentName = segmentName
segmentDescription = callstackItems.toList( chr( 10 ) )
callback = callback
callbackArguments = callbackArguments
errorCallback = ( errorCallback ?: nullValue() )
{
var segment = createObject( "java", "com.intergral.fusionreactor.api.FRAPI" )
.getInstance()
.createTrackedTransaction( segmentName )
;
try {
// Propagate application name and execution context.
segment.setApplicationName( segmentAppName );
segment.setDescription( segmentDescription );
// Invoking the callback that is being wrapped in a tracked transaction.
callback( argumentCollection = callbackArguments );
} catch ( any error ) {
if ( structKeyExists( attributes, "errorCallback" ) ) {
errorCallback( error );
} else {
rethrow;
}
} finally {
segment.close();
}
} // END: Thread.
}
// -- For debugging the demo --.
thread action = "join";
dump( cfthread );
</cfscript>
<script type="text/javascript">
// Refresh page to generate traffic (the Cloud dashboard seems to have trouble
// syncing from my local system unless there is a steady stream of traffic).
setTimeout(
function() {
window.location.reload();
},
1000
);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment