Thursday, November 14, 2024
HomeJavaScriptOperating Reminiscence Leak Detection After Each ColdFusion Request

Operating Reminiscence Leak Detection After Each ColdFusion Request


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 the variables scope of the present context.

  • Loop over the personal keys. Skip over any ColdFusion-internals such because the this scope and the CFThread 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 a CFFunction 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.

Try the license.


https://bennadel.com/4715

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments