tracker issue : CF-4204874

select a category, or use search below
(searches all categories and all time range)
Title:

Memory Leak- Persisted closures hold onto tag instances every time they execute

| View in Tracker

Status/Resolution/Reason: Open//

Reporter/Name(from Bugbase): Bradley W. / ()

Created: 07/19/2019

Components: Language

Versions: 2018

Failure Type: Memory Leak

Found In Build/Fixed In Build: 2018 /

Priority/Frequency: Normal / Unknown

Locale/System: / Platforms All

Vote Count: 2

This issue has been tested in CF 11, 2016, and 2018 (latest updates) and it affects all three in the same manner.  

If a closure is persisted in memory, either in an Application-scoped CFC, or just directly in the application scope and that closure executes any tag such as cflock or cfsetting, then every time the closure executes, it will retain an instance of the tag in memory.  This creates a memory leak where enough executions of the closure will eventually fill up memory with tag classes.

Steps to Reproduce:
Put this code in an index.cfm file and hit it 5 times:

<cfscript>
	// Only create the closure on the first call
    if( !structkeyExists( application, 'lock' ) ) { 
        application.lock = function(){   	
            lock name="myLockName" type="exclusive" timeout="10" {}            
        };
    }
    
    // Call the closure one million times.
    for (i = 1; i <= 1000000; i++) {    
        application.lock();
    }
</cfscript>

Now take a heap dump and analyze it. There will be 5 million instances of coldfusion.tagext.lang.LockTag in memory whose GC roots go through the closure.  See the attached image.  The path is:
CF_ANONYMOUSECLOSURE inner class > invokePage (CFDummyComponent) > udfTags (java.util.Stack) > elementData (java.lang.Object) 

These tag instances are never cleared from memory.  We noticed this after adding closure usage to the FileAppender in ColdBox/LogBox.  The file appender is a singleton and used a closure as a handy way to provide locking around chunks of code in a functional manner.  But the more times the file appender is called and more memory gets eaten up.  Here is the actual real-life use case where this bug was found:
https://github.com/ColdBox/coldbox-platform/blob/v5.4.1/system/logging/appenders/FileAppender.cfc#L97-L106

If you change the repro case above to use a UDF and not a closure, the issue does not exist!  This behavior is specific to closures.

Attachments:

Comments:

And here's a bonus bug: if I refresh my test file twice in a row quickly, I get this error from the application.lock() line: ava.util.EmptyStackException at java.util.Stack.peek(Stack.java:102) at coldfusion.runtime.NeoPageContext.popFunctionLocalScope(NeoPageContext.java:2077) at coldfusion.runtime.Closure.invoke(Closure.java:133) at coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:447) at coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:95) at coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:398) at coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:371) at coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:287) at coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:3928) at coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:3908) at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:3400) at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:3357) at cfindex2ecfm434543785.runPage(C:\sandbox\closurememtest\index.cfm:9)
Comment by Bradley W.
31050 | July 19, 2019 12:18:32 AM GMT