Within the feedback of a publish over on LinkedIn, I used to be speaking to Charles Robertson about how unnerving it’s to have unscoped native variables leak into the variables
scope of a persevered element. Any such reminiscence leak can result in the cross-contamination of requests; and, in a worst case situation, will trigger one person’s information to be proven to a different person. Impressed by that dialog, I made a decision so as to add reminiscence leak detection to the post-processing of each ColdFusion request in my characteristic flags playground software.
This isn’t the primary time that I’ve talked about reminiscence leak detection in ColdFusion. My notion of a “Snooper” was first mentioned in 2015; after which, years later, I begin to consider “snap-shotting” reminiscence for time-based deltas. However, in each of these instances, reminiscence leak detection was nonetheless one thing that I carried out as an after thought—a final step in improvement course of.
Ideally, reminiscence leak detection ought to be one thing that I haven’t got to keep in mind to do—it is too necessary. So why not simply have it working on a regular basis?
To allow this, I added an .examine()
name throughout the onRequestEnd()
occasion handler in my Software.cfc
ColdFusion framework element. This logic will solely run in my improvement surroundings as that is the place I’ve the chance to seek out and repair the problem.
element {
// ... truncated ...
/**
* I get referred to as as soon as to finalize the request.
*/
public void operate onRequestEnd() {
if ( this.config.isLive ) {
return;
}
// For the reason that reminiscence leak detection solely runs within the improvement surroundings, I am
// not going to place any safe-guards round it. The reminiscence leak detector each reads
// from and writes to shared reminiscence, which may be inherently unsafe. Nevertheless, the
// dangers listed here are minimal.
request.ioc.get( "core.lib.MemoryLeakDetector" )
.examine()
;
}
}
On this software, request.ioc
is my implementation of a easy Dependency-Injection (DI) framework for ColdFusion. This Inversion of Management (IoC) container is liable for instantiating and wiring all of my ColdFusion elements collectively.
Since ioc
has all cached CFCs in its personal reminiscence area, it seems to be the proper place to start out on the lookout for reminiscence leaks. So, not solely am I utilizing the request.ioc
to entry my MemoryLeakDetector.cfc
, you may see beneath that my MemoryLeakDetector.cfc
then turns round and makes use of the ioc
internals because the queue-initializer for reminiscence leak detection.
To get this working, I had so as to add a .getAll()
technique to my Injector.cfc
(the CFC behind the request.ioc
reference):
element {
// ... truncated ...
/**
* I return all the cached companies.
*/
public struct operate getAll() {
return companies.copy();
}
}
My MemoryLeakDetector.cfc
then makes use of this .getAll()
technique to populate a queue of elements to examine. As every element is inspected, any new elements discovered within the personal (variables
) scope are subsequently queued-up for future inspection. It isn’t an ideal algorithm; however, since virtually all reminiscence leaks will probably be resulting from unscoped native variables in an IoC-persisted element, it is enough for my use-case.
The algorithm for leak detection is comparatively easy:
-
Queue-up cached elements for inspection.
-
Loop over the queue.
-
Extract the personal
variables
scope of a given cached element. That is completed by injecting a tunneling operate that may return thevariables
scope of the present context. -
Loop over the personal keys. Skip over any ColdFusion-internals such because the
this
scope and theCFThread
operate artifacts. Additionally skip over any CFC that is already been inspected as a part of a repeated reference. -
Test to see if every personal key maps to a
CFProperty
tag or aCFFunction
tag. -
If there is not any corresponding mapping, log a warning to the
console
that features the CFC path and the variable title. -
If the given key represents a CFC, add it to the queue for inspection.
Within the ColdFusion element beneath, discover that there are a number of CFProperty
tags. And, that two of them have ioc:skip
attributes. For the reason that reminiscence leak detection works by checking variables
keys towards the gathering of CFProperty
tags, all personal keys will need to have a corresponding CFProperty
tag, even when they don’t seem to be there to energy dependency-injection.
element
output = false
trace = "I assist detect reminiscence leaks in ColdFusion elements."
{
// Outline properties for dependency-injection.
property title="ioc" ioc:sort="core.lib.Injector";
property title="magicFunctionName" ioc:skip;
property title="magicTokenName" ioc:skip;
property title="utilities" ioc:sort="core.lib.util.Utilities";
/**
* I initialize the reminiscence leak detector.
*/
public void operate $init() {
variables.magicTokenName = "$$MemoryLeakDetector$$Model$$";
variables.magicFunctionName = "$$MemoryLeakDetector$$Examine$$";
}
// ---
// PUBLIC METHODS.
// ---
/**
* I scan the companies within the Injector, on the lookout for reminiscence leaks.
*/
public void operate examine() {
var model = createUuid();
var queue = utilities.structValueArray( ioc.getAll() );
// We will carry out a breadth-first search of the elements, beginning with
// the Injector companies after which amassing any extra elements we discover
// alongside the way in which.
whereas ( queue.isDefined( 1 ) ) {
var goal = queue.shift();
if ( ! utilities.isComponent( goal ) ) {
proceed;
}
// If this goal has already been inspected, skip it. Nevertheless, since reminiscence
// leaks could develop over time primarily based on the person's interplay, we have to
// examine the model quantity (of the present inspection). Solely skip if we're
// in the identical inspection workflow and we're revisiting this element.
// --
// Word: In Adobe ColdFusion, CFC's haven't got a .keyExists() member technique.
// As such, on this case, I've to make use of the built-in operate.
if ( structKeyExists( goal, magicTokenName ) && ( goal[ magicTokenName ] == model ) ) {
proceed;
}
// Be certain we do not come again to this goal throughout the present inspection.
goal[ magicTokenName ] = model;
var targetMetadata = getMetadata( goal );
var targetName = targetMetadata.title;
var targetScope = getVariablesScope( goal );
var propertyIndex = utilities.indexBy( targetMetadata.properties, "title" );
var functionIndex = utilities.indexBy( targetMetadata.capabilities, "title" );
for ( var key in targetScope ) {
// Skip the general public scope - reminiscence leaks solely present up within the personal scope.
if ( key == "this" ) {
proceed;
}
// Skip hidden capabilities created by the CFThread tag.
if ( key.reFindNoCase( "^_cffunccfthread" ) ) {
proceed;
}
// Deal with top-level null values as suspicious.
if ( ! targetScope.keyExists( key ) ) {
logMessage( "Potential reminiscence leak in [#targetName#]: [null]." );
proceed;
}
if (
! propertyIndex.keyExists( key ) &&
! functionIndex.keyExists( key )
) {
logMessage( "Potential reminiscence leak in [#targetName#]: [#key#]." );
}
// If the worth is, itself, a element, add it to the queue for
// subsequent inspection.
if ( utilities.isComponent( targetScope[ key ] ) ) {
queue.append( targetScope[ key ] );
}
}
}
}
// ---
// PRIVATE METHODS.
// ---
/**
* I return the variables scope within the present execution context.
*/
personal any operate dangerouslyAccessVariablesInCurrentContext() {
// Warning: This technique has been injected right into a focused element and is being
// executed within the context of that focused element.
return variables;
}
/**
* I return the variables scope for the given goal.
*/
personal struct operate getVariablesScope( required any goal ) {
// Inject the spy technique in order that we'll be capable to pierce the personal scope of the
// goal and observe the interior state. It does not matter if we inject this
// a number of occasions, we're the one shoppers.
goal[ magicFunctionName ] = variables.dangerouslyAccessVariablesInCurrentContext;
return invoke( goal, magicFunctionName );
}
/**
* I log the given message to the usual out (console).
*/
personal void operate logMessage( required string message ) {
cfdump(
var = message,
output = "console"
);
}
}
Now that we’ve got the MemoryLeakDetector.cfc
in place; and the .examine()
technique is being referred to as on the finish of each request; let’s go in and create a reminiscence leak. One place that reminiscence leaks typically come up are in for
-loop index variables. So, let’s go into my Utilities.cfc
and take away the var
throughout the .indexBy()
technique:
element {
// ... truncated ...
/**
* I index the given assortment utilizing the given key because the associative entry.
*/
public struct operate indexBy(
required array assortment,
required string key
) {
var index = {};
// !!!! CAUTION: No VAR key phrase. !!!!
for ( factor in assortment ) {
index[ element[ key ] ] = factor;
}
return index;
}
}
Discover that there isn’t any var
earlier than the factor
variable declaration within the for
loop. This may trigger the factor
variable to be saved into the Utilities.cfc
variables
scope. And, if we now run my software and take a look at the Docker console logging, we will see this output:
As you may see, the MemoryLeakDetector.cfc
was capable of find and determine this variable which has proven up within the incorrect place.
Proper now, this runs on the finish of each single request. In a small software (like my characteristic flags playground), this should not pose any efficiency challenge (improvement computer systems are so freaking quick lately). Nevertheless, if this does pose an issue for bigger purposes (or slower machines), it ought to be straightforward sufficient to restrict the inspection to solely sure requests or to throttle the inspection to a sure time interval.
Generic vs. Dwelling Grown Resolution
As I used to be constructing this out, I used to be pressured to consider the variations in complexity between a generic answer and a house grown answer. In my case, by utilizing a house grown answer, I used to be capable of leverage my present Injector.cfc
and my present Utilities.cfc
; and, I used to be capable of lean on the ioc:skip
attribute within the CFProperty
tag with the intention to mandate that every one variables
-scope keys should have a corresponding CFProperty
tag.
If I had been to construct this out as a generic answer, it might’ve been way more complicated. I would should re-implement utilities and I would most likely have to supply configuration choices for options like an allow-list of keys.
Usually occasions, the house grown strategy (or the generic answer copy-pasted and modified) is the only answer.
Wish to use code from this publish?
Try the license.
https://bennadel.com/4715