Утечка памяти в ColdFusion при непосредственном вызове cfc без аргумента метода

У нас были проблемы с памятью в течение длительного времени. Я наконец разыскал, как повторить проблему, но я не знаю, что вызывает это или как решить это.

У нас есть несколько cfc в каталоге, доступном через Интернет /controller, которые обрабатывают отправку и обработку. Когда cfc вызывается напрямую без аргумента метода, сервер начинает проверять память.

Например, URL-адрес, такой как http://www.domain.com/controller/LoginController.cfc будет работать в браузере до истечения времени ожидания. /CFIDE был заблокирован и не является общедоступным

поэтому cfexplorer недоступен (или не должен) быть доступным.

Мы используем FusionReactor для мониторинга наших экземпляров. Наши серверы настроены на 20 ГБ пространства кучи. При новой перезагрузке после загрузки приложения объем памяти увеличится до 800 МБ.

При нормальном трафике память будет колебаться от 5 до 10 ГБ при регулярной сборке мусора. Через некоторое время сервер достигает 98% емкости. Это имеет тенденцию бежать туда

штраф в течение нескольких часов или даже дней, иногда, пока какой-то всплеск трафика не заставит его преуспеть и не произойдет ошибка. Сборка мусора не восстанавливает память и нет активных

FusionReactor сообщает о длительных потоках. Только перезапуск сервера восстановит его.

Используя FusionReactor (который мы только что установили, и именно так я наконец-то получил представление об этой проблеме), я проверил пространство памяти PermGen и обнаружил, что на него приходится

85% кучи. Это не казалось правильным вообще. Я выполнил дамп памяти и загрузил его в MAP через Eclipse, чтобы проанализировать его. Я обнаружил, что в памяти было 10 объектов

размер 1.7GB (1.7x10 - это примерно 85% от общей кучи). Эти объекты выглядят так:

Class Name |  Shallow Heap | Retained Heap | Percentage
byte[1769628928] @ 0x4d963b198  ...128.................POST......../controller/LoginController.cfc......../controller/LoginController.cfc........173.14.93.66........173.14.93.66........www.domain.com........443........HTTP/1.1.......;D:\websites\domain\system\controller\Lo...| 1,769,628,944 | 1,769,628,944 |   8.60%

Поэтому я перезапустил CF на одном из наших серверов. Проверял FusionReactor и не видел использования памяти. Затем пошел в браузер и сначала вызвал cfc следующим образом:

http://www.domain.com/controller/LoginController.cfc?method=foo

Это привело к тому, что обработчик onMissingMethod правильно пинал и перенаправлял на соответствующую страницу ошибки без эффекта сервера.

Но потом звоню так:

http://www.domain.com/controller/LoginController.cfc

В результате страницы зависают. FusionReactor reports there are no active request even though one is running one which is why we couldn't identify the problem while it was happening. Worse, refreshing the memory sees it slowly increase by tenths of a percentage with no reported activity. The timeout on the server is set to 5 minutes. I'm assuming that eventually it gets killed and then orphaned at 1.7GB. This didn't bring down the server, just spiked the memory where it was now running at a flat 3GB usage where garbage collection recovers nothing. This seems to explain why over time, random calls to these URLs slowly chew up and hold onto memory.

Next I called the URL from multiple browser tabs. This spiked the memory almost instantaneously to 98%. FusionReactor now showed two long running requests 10 seconds and climbing even though there were over 15 browser tabs running. Force killing the thread seemed to do nothing. Only a server restart solved the problem.

So now I've identified the issue specifically (phantom threads creating huge orphaned objects in PermGen heap) and how to replicate the issue.

How or why requests are made directly to the cfc I have no idea. Possibly bots or occasional weird browser behavior.

All the huge objects are instances of jrun.servlet.jrpp.ProxyEndpoint.

What specifically is causing this issue and how do I fix it.

This is CF9.01 Standard on Win2003 Server running Java 1.7.0_25.

Спасибо!

Скриншот MAP-анализа дампа кучи

3 ответа

Я считаю, что это допустимая ошибка в ColdFusion, и я сообщил об этом через их систему ошибок. Проблема частично повторяется в других системах. Например, на моем MBP, выполняющем CF на Apache, прямой вызов CFC не вызывает проблемы с памятью, но приводит к немедленной странице JRun "Internal Server Error". Так что что-то не так, и системы по-разному решают проблему. Anway...

Я нашел обходной путь благодаря @iKnowKungFoo и большому количеству экспериментов.

Вставка ключа / значения метода в область действия URL, похоже, решает проблему. Предостережение заключается в том, что это должно быть сделано в методе onRequestStart, а не в методе onCFCRequest. Из документов видно, что вызов CFC будет идти непосредственно к onCFCRequest, но, похоже, это не так. Все запросы сначала проходят через метод onRequestStart. Когда onRequestStart возвращает только тогда, onCFCRequest вызывается AND, только если существует обязательный аргумент 'method'.

Так что в этом случае onCFCRequest никогда не вызывался, потому что аргумент 'method' никогда не существовал. Итак, вот код, который сразу запускается в onRequestStart:

<cfif Right(arguments.targetPage,4) IS ".cfc"
      AND NOT StructKeyExists(URL,"WSDL")
      AND NOT StructKeyExists(URL,"method")
      AND NOT StructKeyExists(FORM,"method")>
    <cfset StructInsert(FORM,"method","")>
    <cfset StructInsert(URL,"method","")>
</cfif>

Этот бит кода проверяет расширение на запрашиваемой странице и, если аргумент метода не существует в обеих областях URL-адреса и FORM, он вставляет пустую пару ключ / значение для обеих целей. Проверка аргумента 'WSDL' здесь, поскольку я обнаружил, что, хотя этот код работал отлично, внезапно несколько вызовов cfc веб-сервисов мы прервали. Если вызовом cfc является WebService.cfc?WSDL, то аргумент метода не требуется, и CF обрабатывает все по-другому.

Таким образом, вставка пустого значения 'method' приводит к правильному вызову onCFCRequest при завершении onRequsetStart. Когда cfc вызывается с недопустимым пустым именем метода, onMissingMethod теперь правильно запускается. Этот метод оперативно обрабатывает неверный запрос страницы и перенаправляет на пользовательскую страницу ошибки.

С момента внедрения этого исправления мы наблюдаем снижение использования памяти на всех серверах с согласованных 98% до 15%. Графики памяти показывают ожидаемую пилообразность используемой и собираемой памяти. Общая производительность возросла со среднего времени запроса страницы от 1200 мс до 54 мс, при этом все эти запросы не зацикливались.

Тем не менее, я надеюсь, что Adobe сможет определить и устранить проблему.

Возможно, вы могли бы использовать onCFCRequest в вашем Application.cfc для мониторинга этой проблемы.

Это все равно создаст объект, но вы можете зарегистрировать запрос, затем CFABORT должен остановить запрос мертвым в своих треках.

<cffunction name="oncfcRequest" returnType="void"> 
    <cfargument type="string" name="cfcname"> 
    <cfargument type="string" name="method"> 
    <cfargument type="struct" name="args"> 
    <cfif arguments.method IS "">
        <cflog .... />
        <cfabort />
    </cfif>
</cffunction>

Я знаю, что это повлечет за собой большие изменения в том, как вы делаете вещи, но я всегда избегал позволять CF без необходимости создавать CFC. Если они не изменили то, как они работают (я в последний раз играл с этой версией назад), попадание в CFC напрямую приводит к созданию нового экземпляра.

Если вы готовы к небольшому тесту, возможно, попробуйте настроить простую страницу.cfm фронт-контроллера / делегата и переместить CFC в пределах "контроллера" в область приложения. Конечно, есть более элегантные архитектуры для этого (если не переходить на полноценную среду), но вы можете:

Используйте Application.cfc, чтобы установить экземпляр чего-либо (например, LoginController) в область приложения, а затем используйте простую страницу "invoke.cfm", которая в основном ожидает, что имя одного из этих CFCs области приложения будет вызываться вместе с параметрами. Что-то вроде (только для примера):

<cfsilent>
<cfset ctlName = url.controllerName />
<cfset methodName = url.methodName />
<cfset response = "" />

<!--- Look up the desired single-cfc controller --->
<cfif len(methodName) and structKeyExists( application.controllers, ctlName ) >
  <cfset ctl = application.controllers.ctlName />

  <!--- Now ask it do to something - note that i'm not validating the method... --->
  <cfinvoke component="#ctl#" method="#methodName#" argumentCollection="#form#" returnVariable="response" />
</cfif>
</cfsilent><cfoutput>#response#</cfoutput>  

Обратите внимание, что это приведет к тому, что ваши "контроллеры" будут иметь состояние и потокобезопасность должна быть рассмотрена (но это уже должно быть, во всяком случае).

Другие вопросы по тегам