Memory Leaks: Part III – sessions and cfcs
In part III of my memory leak posts, I will attempt to show an example of how variable references can increase within in the heap on every request. If you would like to know more about the tools I used to identify the problem, or would like to follow along with the example you should take a look at my previous posts,
"ColdFusion Memory Leaks: Part I – profiler introduction"
"ColdFusion Memory Leaks: Part II – variables scope"
Note: The example that I will demonstrate is 90% complete. It is similar to the behavior that I experienced in my application, but not as severe. This only works in specific circumstances, where my problem appeared 100% of the time. Unfortunately, this is the closest I could come without including hundreds of thousands of lines of code.
For this example, we will expand the case that I demonstrated in the variables scope example. We will use the same test.cfc as the previous example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<!--- file: test.cfc ---> <cfcomponent name="test"> <cffunction name="init"> <cfreturn this > </cffunction> <cffunction name="doSomething"> <cfset var idx = "" /> <cfset var arrayToReturn = arrayNew(1) /> <cfloop from="1" to="100" index="idx"> <cfset arrayAppend(arrayToReturn,createObject("component","test").init()) /> </cfloop> <cfreturn arrayToReturn > </cffunction> </cfcomponent>
Next we will need to expand the cfm page, we’ll make the following modifications.
- add session timeout of 10 seconds (to see results quickly)
- only load test.cfc into application on the first request
- create an array on the session
- create a new instance of test.cfc locally, and append it to the array on the session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<!--- file: test.cfm ---> <cfapplication name="memoryTest" sessionmanagement="true" sessiontimeout="#createTimeSpan(0,0,0,10)#" > <!--- create an instance of test.cfc in application, adding reset to the URL, will cleanup all references ---> <cfif not structKeyExists(application,"test") OR isDefined("URL.reset")> <cfset application.test = createObject("component","test").init() /> <cflocation url="#cgi.SCRIPT_NAME#" addtoken="false"> </cfif> <cfset foo = application.test.doSomething() /> <cfif not structKeyExists(session,"sessionArray")> <cfset session.sessionArray = arrayNew(1)> creating a new session<br /> </cfif> <cfset objectToPutInSession = createObject("component","test") > <cfset arrayAppend(session.sessionArray,objectToPutInSession)> completed request - session size = <cfoutput>#arrayLen(session.sessionArray)#</cfoutput> <br /><br /> <cfoutput><a href="#cgi.SCRIPT_NAME#?reset=true">reset application.test (this clears all memory references)</a></cfoutput>
What should we expect in this example? (feel free to correct me if you think this should be different)
- 1 instance of test.cfc should be loaded into application
- foo exists in variables scope and creates a reference to an array of 100 instances of test.cfc (this is the example from Part II and should be destroyed at the end of the request)
- an instance of test.cfc will be added to the session on every request. (5 request in a session = 5 test.cfc)
- when the session expires, the array should be cleaned from the heap.
Now for a flash movie that will show the results of what actually happens (my apologies if this is rushed, there’s quite a bit going on in a short timeframe)
What happened??(also, how you can follow along if you execute the example)
- On the first request, test.cfc is loaded into the application at the same time a new session is created
- Every request loads an extra 100 references to test.cfc (which should only exist in variables scope)
- The session that loads the cfc into application scope (and only this session) will persist all the references for the life of the application
- All additional sessions will hold their references for the life of the session
- Adding a structure to session instead of a cfc will create the proper behavior
Why does this happen? I can’t tell you, I’m hoping the community, or adobe will be able to figure that out. Some other notes that I would like to add…. The behavior occurs if it is a struct OR an array on the session. The behavior also occurs if all the cfcs are different (I used test.cfc for all references to keep the example simple). If you reload application.test, the references will be cleaned up, but the behavior will occur for the session associated with the request that created application.test.
How do I fix this in my application???
I am not going to recommend a solution for this because I haven’t found a "magic bullet" yet. I am also not going to recommend a solution because every application is unique, and every memory leak is different. I will however tell you that my problem was very similar to this. We were keeping a history stack on the session that consisted of an array of simple beans. Converting the beans to structures, solved a large part of the problem. This isn’t always an option if you are storing complex objects on the session. I’m very interested to see if anyone else having a similar problem is storing cfcs on a session, as the root cause seems to be a wacky combination of instantiating cfcs on multiple scopes within a request.
Mike Schierberl is a software engineer located in San Francisco, CA. He specializes in Cloud Computing, E-Commerce, and Continuous Integration.