Skip to main content
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Awdhesh Kumar
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Awdhesh Kumar

Running Memory Leak Detection After Every ColdFusion Request

By
Published in Comments (6)

In the comments of a post over on LinkedIn, I was talking to Charles Robertson about how unnerving it is to have unscoped local variables leak into the variables scope of a persisted component. This type of memory leak can lead to the cross-contamination of requests; and, in a worst case scenario, will cause one user's data to be shown to another user. Inspired by that conversation, I decided to add memory leak detection to the post-processing of every ColdFusion request in my feature flags playground application.

This is not the first time that I've talked about memory leak detection in ColdFusion. My notion of a "Snooper" was first discussed in 2015; and then, years later, I start to think about "snap-shotting" memory for time-based deltas. But, in both of those cases, memory leak detection was still something that I performed as an after thought—a last step in development process.

Ideally, memory leak detection should be something that I don't have to remember to do—it's too important. So why not just have it running all the time?

To enable this, I added an .inspect() call within the onRequestEnd() event handler in my Application.cfc ColdFusion framework component. This logic will only run in my development environment as this is where I have the opportunity to find and fix the issue.

component {

	// ... truncated ...

	/**
	* I get called once to finalize the request.
	*/
	public void function onRequestEnd() {

		if ( this.config.isLive ) {

			return;

		}

		// Since the memory leak detection only runs in the development environment, I'm
		// not going to put any safe-guards around it. The memory leak detector both reads
		// from and writes to shared memory, which can be inherently unsafe. However, the
		// risks here are minimal.
		request.ioc.get( "core.lib.MemoryLeakDetector" )
			.inspect()
		;

	}

}

In this application, request.ioc is my implementation of a simple Dependency-Injection (DI) framework for ColdFusion. This Inversion of Control (IoC) container is responsible for instantiating and wiring all of my ColdFusion components together.

Since ioc has all cached CFCs in its own memory space, it turns out to be the perfect place to start looking for memory leaks. So, not only am I using the request.ioc to access my MemoryLeakDetector.cfc, you'll see below that my MemoryLeakDetector.cfc then turns around and uses the ioc internals as the queue-initializer for memory leak detection.

To get this working, I had to add a .getAll() method to my Injector.cfc (the CFC behind the request.ioc reference):

component {

	// ... truncated ...

	/**
	* I return all of the cached services.
	*/
	public struct function getAll() {

		return services.copy();

	}

}

My MemoryLeakDetector.cfc then uses this .getAll() method to populate a queue of components to inspect. As each component is inspected, any new components found in the private (variables) scope are subsequently queued-up for future inspection. It's not a perfect algorithm; but, since almost all memory leaks will be due to unscoped local variables in an IoC-persisted component, it's sufficient for my use-case.

The algorithm for leak detection is relatively simple:

  • Queue-up cached components for inspection.

  • Loop over the queue.

  • Extract the private variables scope of a given cached component. This is done by injecting a tunneling function that can return the variables scope of the current context.

  • Loop over the private keys. Skip over any ColdFusion-internals such as the this scope and the CFThread function artifacts. Also skip over any CFC that's already been inspected as part of a repeated reference.

  • Check to see if each private key maps to a CFProperty tag or a CFFunction tag.

  • If there's no corresponding mapping, log a warning to the console that includes the CFC path and the variable name.

  • If the given key represents a CFC, add it to the queue for inspection.

In the ColdFusion component below, notice that there are several CFProperty tags. And, that two of them have ioc:skip attributes. Since the memory leak detection works by checking variables keys against the collection of CFProperty tags, all private keys must have a corresponding CFProperty tag, even if they aren't there to power dependency-injection.

component
	output = false
	hint = "I help detect memory leaks in ColdFusion components."
	{

	// Define properties for dependency-injection.
	property name="ioc" ioc:type="core.lib.Injector";
	property name="magicFunctionName" ioc:skip;
	property name="magicTokenName" ioc:skip;
	property name="utilities" ioc:type="core.lib.util.Utilities";

	/**
	* I initialize the memory leak detector.
	*/
	public void function $init() {

		variables.magicTokenName = "$$MemoryLeakDetector$$Version$$";
		variables.magicFunctionName = "$$MemoryLeakDetector$$Inspect$$";

	}

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I scan the services in the Injector, looking for memory leaks.
	*/
	public void function inspect() {

		var version = createUuid();
		var queue = utilities.structValueArray( ioc.getAll() );

		// We're going to perform a breadth-first search of the components, starting with
		// the Injector services and then collecting any additional components we find
		// along the way.
		while ( queue.isDefined( 1 ) ) {

			var target = queue.shift();

			if ( ! utilities.isComponent( target ) ) {

				continue;

			}

			// If this target has already been inspected, skip it. However, since memory
			// leaks may develop over time based on the user's interaction, we need to
			// check the version number (of the current inspection). Only skip if we're
			// in the same inspection workflow and we're revisiting this component.
			// --
			// Note: In Adobe ColdFusion, CFC's don't have a .keyExists() member method.
			// As such, in this case, I have to use the built-in function.
			if ( structKeyExists( target, magicTokenName ) && ( target[ magicTokenName ] == version ) ) {

				continue;

			}

			// Make sure we don't come back to this target within the current inspection.
			target[ magicTokenName ] = version;

			var targetMetadata = getMetadata( target );
			var targetName = targetMetadata.name;
			var targetScope = getVariablesScope( target );
			var propertyIndex = utilities.indexBy( targetMetadata.properties, "name" );
			var functionIndex = utilities.indexBy( targetMetadata.functions, "name" );

			for ( var key in targetScope ) {

				// Skip the public scope - memory leaks only show up in the private scope.
				if ( key == "this" ) {

					continue;

				}

				// Skip hidden functions created by the CFThread tag.
				if ( key.reFindNoCase( "^_cffunccfthread" ) ) {

					continue;

				}

				// Treat top-level null values as suspicious.
				if ( ! targetScope.keyExists( key ) ) {

					logMessage( "Possible memory leak in [#targetName#]: [null]." );
					continue;

				}

				if (
					! propertyIndex.keyExists( key ) &&
					! functionIndex.keyExists( key )
					) {

					logMessage( "Possible memory leak in [#targetName#]: [#key#]." );

				}

				// If the value is, itself, a component, add it to the queue for
				// subsequent inspection.
				if ( utilities.isComponent( targetScope[ key ] ) ) {

					queue.append( targetScope[ key ] );

				}

			}

		}

	}

	// ---
	// PRIVATE METHODS.
	// ---

	/**
	* I return the variables scope in the current execution context.
	*/
	private any function dangerouslyAccessVariablesInCurrentContext() {

		// Caution: This method has been injected into a targeted component and is being
		// executed in the context of that targeted component.
		return variables;

	}


	/**
	* I return the variables scope for the given target.
	*/
	private struct function getVariablesScope( required any target ) {

		// Inject the spy method so that we'll be able to pierce the private scope of the
		// target and observe the internal state. It doesn't matter if we inject this
		// multiple times, we're the only consumers.
		target[ magicFunctionName ] = variables.dangerouslyAccessVariablesInCurrentContext;

		return invoke( target, magicFunctionName );

	}


	/**
	* I log the given message to the standard out (console).
	*/
	private void function logMessage( required string message ) {

		cfdump(
			var = message,
			output = "console"
		);

	}

}

Now that we have the MemoryLeakDetector.cfc in place; and the .inspect() method is being called at the end of every request; let's go in and create a memory leak. One place that memory leaks often come up are in for-loop index variables. So, let's go into my Utilities.cfc and remove the var within the .indexBy() method:

component {

	// ... truncated ...

	/**
	* I index the given collection using the given key as the associative entry.
	*/
	public struct function indexBy(
		required array collection,
		required string key
		) {

		var index = {};

		// !!!! CAUTION: No VAR keyword. !!!!
		for ( element in collection ) {

			index[ element[ key ] ] = element;

		}

		return index;

	}

}

Notice that there is no var before the element variable declaration in the for loop. This will cause the element variable to be stored into the Utilities.cfc variables scope. And, if we now run my application and look at the Docker console logging, we can see this output:

As you can see, the MemoryLeakDetector.cfc was able to locate and identify this variable which has shown up in the wrong place.

Right now, this runs at the end of every single request. In a small application (like my feature flags playground), this shouldn't pose any performance issue (development computers are so freaking fast these days). However, if this does pose a problem for larger applications (or slower machines), it should be easy enough to limit the inspection to only certain requests or to throttle the inspection to a certain time interval.

Generic vs. Home Grown Solution

As I was building this out, I was forced to think about the differences in complexity between a generic solution and a home grown solution. In my case, by using a home grown solution, I was able to leverage my existing Injector.cfc and my existing Utilities.cfc; and, I was able to lean on the ioc:skip attribute in the CFProperty tag in order to mandate that all variables-scope keys have to have a corresponding CFProperty tag.

If I were to build this out as a generic solution, it would've been much more complex. I'd have to re-implement utilities and I'd probably have to provide configuration options for features like an allow-list of keys.

Often times, the home grown approach (or the generic solution copy-pasted and modified) is the simplest solution.

Want to use code from this post? Check out the license.

Reader Comments

443 Comments

Great stuff!

I might see if I can rework this for FW1.
I know FW1 uses DI, so there should be quite a lot of commonality here.

Also like you, I have built a custom DI, using an XML config file. I was inspired by Coldspring.
I reckon every CF Dev worth anything, should have a go at building a DI. It is a great way of learning about how Dependency Injection really works, under the hood.
So, I might start with this first.

15,832 Comments

@Charles,

That would be cool to see. I've dabbled a bit with the internals of FW/1 before; and I remember having do some "funky" stuff in order to get access to some of the cache components. I think the Controllers, especially, where harder to get through. Step 1 might be updating DI/1 or FW/1 to make the internal state more accessible without hacks.

Other than that, though, yeah there should be a lot of overlap in how the two systems works. I think DI/1 might allow for nested injectors (or maybe I'm getting confused with Angular now). But, more or less, it's all just cached values.

Looking forward to hearing about your adventure!

443 Comments

@Ben Nadel,

So essentially in FW1, you just do:

// controllers/user.cfc

component accessors = true{

    property userService; // model.services.user

    …

}

And then call the service like:

variables.userService

In the controller.

Not entirely sure how everything works behind the scenes.
When I created my own custom DI, I actually wrote out the explicit getters/setters, but I hear that cfproperty creates implicit getters/setters alongside the accessors=true, which is a little easier to manage.
Anyway, I will have a rummage around in the FW1 core and see how everything works?

Or perhaps, Sean Corfield reads this blog and he can tell us, himself 🙂

15,832 Comments

@Charles,

Sorry, what I meant was that if you wanted to do meta-programming against the cached instances, in your example, it's hard to get at the cached instance of controllers/user.cfc. At least it was the last time I checked; though, maybe I just missed it.

Essentially, in the way that I added the .getAll() method to my inject, you would need something similar in FW/1.

443 Comments

@Ben Nadel,

The one thing I do remember is that in FW1, controllers/services are singletons, so are probably accessible via the application scope.
I presume that means they are cached, in some way?

I think you can do something like:

userController = application[ variables.framework.applicationKey ].cache.controllers.user

I think this drags the controller out of the application scope? Then I could access the components metadata?
Maybe I could use something like this to emulate your methodology?

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel