Подтвердить что ты не робот

Как я могу предотвратить BufferManager/PooledBufferManager в моем приложении-клиенте WCF от потери памяти?

Анализ клиентского приложения WCF (который я не писал и до сих пор не слишком много знаю), который говорит о связке сервисов через SOAP и после запуска в течение нескольких дней будет вызывать исключение OutOfMemoryException, я узнал об этом .NET PooledBufferManager никогда не будет выпускать неиспользуемые буферы, даже если в приложении заканчивается память, что приводит к ошибкам OOME.

Это, конечно, соответствует спецификации: http://msdn.microsoft.com/en-us/library/ms405814.aspx

Пул и его буферы [...] уничтожаются, когда пул буферов восстановленный сборкой мусора.

Пожалуйста, не стесняйтесь отвечать только на один из приведенных ниже вопросов, так как у меня есть куча вопросов, некоторые из них более общие, а некоторые из них применимы к нашему приложению BufferManager.

Сначала несколько общих вопросов о пуле (BufferManager по умолчанию):

1) В среде, где есть GC, зачем нам нужен BufferManager, который будет храниться в неиспользуемой памяти, даже если это приводит к OOME? Я знаю, есть BufferManager.Clear(), который вы можете использовать, чтобы вручную избавиться от всех буферов - если у вас есть доступ к BufferManager, то есть. Смотрите далее, почему у меня нет доступа.

2) Несмотря на то, что MS заявляет, что "этот процесс намного быстрее, чем создание и уничтожение буфера каждый раз, когда вам нужно его использовать". Не следует ли оставлять это до GC (и, например, LOH) и оптимизировать GC вместо этого?

3). Когда вы делаете BufferManager.Take(33 * 1024 * 1024), я получаю буфер 64M, так как PooledBufferManager будет кэшировать этот буфер для последующего повторного использования, что может... ну, в моем случае это не так, и поэтому это чистая трата памяти - быть, скажем, 34M, или 50M, или 64M. Так было ли разумно создать потенциально очень расточительный BufferManager, подобный этому, который используется (по умолчанию, я предполагаю) HttpsChannelFactory? Я не понимаю, как важна производительность для распределения памяти, особенно когда мы говорим о WCF и сетевых сервисах, которые приложение будет говорить каждые 10 секунд TOPS, обычно еще много секунд или даже минут.

Теперь еще несколько конкретных вопросов, связанных с использованием нашего приложения BufferManagers. Приложение подключается к нескольким различным службам WCF. Для каждого из них мы поддерживаем пул соединений для http-соединений, поскольку соединения могут возникать одновременно.

Проверка единственного самого большого объекта в одном дампе кучи - массив размером 64 Мбайт, который использовался только один раз в нашем приложении во время инициализации и не нужен впоследствии, так как ответ от службы такой большой, только во время инициализации, что кстати. является типичным для многих приложений, которые я использовал, хотя это может быть подвергнуто опиомизации (кэширование на диск и т.д.). Анализ GC-корня в WinDbg дает следующее (я дезинфицировал имена наших собственных классов в "MyServiceX" и т.д.):

0:000:x86> !gcroot -nostacks 193e1000
DOMAIN(00B8CCD0):HANDLE(Pinned):4d1330:Root:0e5b9c50(System.Object[])->
035064f0(MyServiceManager)->
0382191c(MyHttpConnectionPool`1[[MyServiceX, MyLib]])->
03821988(System.Collections.Generic.Queue`1[[MyServiceX, MyLib]])->
038219a8(System.Object[])->
039c05b4(System.Runtime.Remoting.Proxies.__TransparentProxy)->
039c0578(System.ServiceModel.Channels.ServiceChannelProxy)->
039c0494(System.ServiceModel.Channels.ServiceChannel)->
039bee30(System.ServiceModel.Channels.ServiceChannelFactory+ServiceChannelFactoryOverRequest)->
039beea4(System.ServiceModel.Channels.HttpsChannelFactory)->
039bf2c0(System.ServiceModel.Channels.BufferManager+PooledBufferManager)->
039c02f4(System.Object[])->
039bff24(System.ServiceModel.Channels.BufferManager+PooledBufferManager+BufferPool)->
039bff44(System.ServiceModel.SynchronizedPool`1[[System.Byte[], mscorlib]])->
039bffa0(System.ServiceModel.SynchronizedPool`1+GlobalPool[[System.Byte[], mscorlib]])->
039bffb0(System.Collections.Generic.Stack`1[[System.Byte[], mscorlib]])->
12bda2bc(System.Byte[][])->
193e1000(System.Byte[])

Глядя на корни gc для других байт-массивов, управляемых BufferManager, показывает, что другие службы (а не "MyServiceX" ) имеют разные экземпляры BufferPool, поэтому каждый из них тратит впустую свою собственную память, они даже не разделяют трату.

4) Делаем ли мы что-то неправильно здесь? Я не эксперт WCF каким-либо образом, поэтому можем ли мы сделать, чтобы различные экземпляры HttpsChannelFactory использовали один и тот же BufferManager?

5) Или, может быть, даже лучше, можем ли мы просто рассказать всем экземплярам HttpsChannelFactory НЕ использовать BufferManagers вообще и попросить GC выполнить свою проклятую работу, которая является "управляющей памятью"?

6) Если вопросы 4) и 5) не могут быть отвечены, я могу получить доступ к BufferManager из всех экземпляров HttpsChannelFactory и вручную вызвать .Clear() на них - это далеко не на оптимальном решении, но это уже помогло бы в моем случае освободить не только вышеупомянутый 64M, но и 64M + 32M + 16M + 8M + 4M + 2M только в одном экземпляре службы! Таким образом, это само по себе заставит мое приложение работать дольше, не сталкиваясь с проблемами памяти (и нет, у нас нет проблемы с утечкой памяти, кроме BufferManager, хотя мы потребляем много памяти и накапливаем много данных по курсу много дней, но это не проблема здесь)

4b9b3361

Ответ 1

4) Делаем ли мы что-то не так? Я не эксперт WCF от любого значит, мы могли бы сделать различные экземпляры HttpsChannelFactory все использовать тот же BufferManager?

5) Или, может быть, даже лучше, можем ли мы просто рассказать все HttpsChannelFactory экземпляры НЕ использовать BufferManager вообще и попросить GC выполнить проклятая работа, которая "управляет памятью"?

Я предполагаю, что одним из способов решения этих двух вопросов может быть изменение TransferMode с "буферизованного" на "потоковое". Придется исследовать, поскольку режим "потокового" имеет несколько ограничений, и я, возможно, не смогу его использовать.

Обновление: На самом деле это отлично работает! Потребление памяти в буферизованном режиме при запуске приложения было 630M в пиковые времена и уменьшено до 470M при полной загрузке. После переключения в потоковый режим потребление памяти не отображает временный пик и при полной загрузке потребление составляет всего 270 М!

Btw., это было однострочное изменение кода клиента для меня. Мне просто пришлось добавить эту строку:

httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse;

Ответ 2

Я считаю, что у меня есть ответ на ваш вопрос № 5:

5) Или, может быть, даже лучше, можем ли мы просто рассказать все HttpsChannelFactory экземпляры НЕ использовать BufferManager вообще и попросить GC сделать его проклятая работа, которая "управляет памятью"?

Существует параметр привязки MaxBufferPoolSize, который управляет максимальным размером буферов в BufferManager. Установка его на 0 отключит буферизацию, а GCBufferManager будет создан вместо пула - и будет GC выделенных буферов, как только сообщение будет обработано, как в вашем вопросе.

В этой статье более подробно обсуждается управление буфером памяти WCF.

Ответ 3

Как говорит Джон, было бы легче ответить только на один вопрос, вместо написания эссе. Но вот что я думаю

1) В среде, где у нас есть GC, зачем нам нужно BufferManager

Вы, кажется, неправильно понимаете концепцию GC и буферов. GC работает со ссылочными типизированными объектами и освобождает память, если обнаруживает, что объект является вершиной (точкой или node) графа и не имеет никаких допустимых ребер (линий или соединений) для других вершин. Буферы - это всего лишь временное хранилище для временного массива необработанных данных. Например, если вам нужно отправить сообщение уровня приложения WCF, а его текущий размер больше размера сообщения об уровне транспортного уровня, WCF будет делать это в нескольких транспортных сообщениях. По размеру приемника WCF будет ждать, пока сообщение полного уровня приложения не поступит, и только тогда он передаст сообщение для обработки (если только это не привязка к потоку). Временные транспортные сообщения буферизуются - хранятся где-то в памяти на конце приемника. Поскольку создание новых буферов для любых новых сообщений в этом примере может стать очень экспансивным,.NET предоставляет вам класс управления буфером, который отвечает за объединение и совместное использование буферов.

2) Несмотря на то, что MS заявляет, что "этот процесс намного быстрее, чем создание и уничтожение буфера каждый раз, когда вам нужно его использовать"., не следует оставлять это до GC (и его LOH например) и оптимизировать GC вместо?

Нет, они не должны. Буферы и GC не имеют ничего общего (если вы не хотите уничтожать буфер каждый раз, в контексте выборки, это ошибка дизайна). Они имеют разные обязанности и решают разные проблемы.

3) Приложение подключается к нескольким различным службам WCF. Для каждого из их мы поддерживаем пул соединений для http-соединений.

HTTP-привязка не предназначена для обработки большой полезной нагрузки, такой как 64Mb, рассмотрите возможность изменения привязки к более подходящей. Если вы используете сообщение этого sie, WCF не будет передавать его, если все 64Mb не будут полностью получены. Таким образом, если у вас 10 одновременных подключений, размер вашего буфера будет 640 МБ.

По другим вопросам, пожалуйста, напишите еще один вопрос о SO с кодом и конфигурацией WCF. Будет легче найти, где проблема. Возможно, буферы не очищаются, потому что они используются ненадлежащим образом, вы должны рассмотреть объем тестирования, который был выполнен на GC и WCF, и количество тестов, которые были выполнены в рамках старого проекта, - следить за бритвой Occam.