Финализатор заблокирован проблема

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

В моем приложении API-запросы поступают из веб-службы в службу NT. Я работаю в тестовой среде и могу воспроизводить этот "эффект" снова и снова. В моем тесте я буду следовать этой общей процедуре:

  1. Запустите нагрузочный тест в Visual Studio (10 пользователей), чтобы создать шаблон вызовов API. (Мое время для нагрузочного теста установлено на 99 часов, так что я могу выполнять его непрерывно и просто прервать тест, когда хочу остановить нагрузку, и проверить условия процесса.)
  2. Запустите Perfmon и отслеживайте ключевую статистику, частные байты, виртуальные байты, потоки, дескрипторы, #time в GC и т. Д.
  3. Остановите нагрузочный тест.
  4. Присоедините Windbg к процессу. Проверьте, если что-то идет не так. (FinalizeQueue и т. Д.)
  5. Если все в порядке, отсоедините и перезапустите нагрузочный тест.

Я буду следовать этой общей процедуре и изменю тест, чтобы проверить различные вызовы API, чтобы увидеть, что эти API делают с профилем памяти приложения.

Когда я проходил через это, я заметил, что иногда, когда я прекращаю нагрузочный тест, я получаю исключение "поток был прерван". Это также редко будет видно в журналах из среды клиента. Похоже, это происходит, когда выполняется вызов API, а затем клиент отключается по каким-либо причинам.

Однако после получения этого исключения я заметил, что финализатор моего процесса завис. (Сервис класса NT, а не w3wp.)

Независимо от того, как долго процесс остается не обремененным и необработанным, это верхняя часть вывода! Finalizequeue:

    0:002> !finalizequeue
    SyncBlocks to be cleaned up: 0
    Free-Threaded Interfaces to be released: 0
    MTA Interfaces to be released: 0
    STA Interfaces to be released: 0
    ----------------------------------
    generation 0 has 15 finalizable objects (0c4b3db8->0c4b3df4)
    generation 1 has 9 finalizable objects (0c4b3d94->0c4b3db8)
    generation 2 has 771 finalizable objects (0c4b3188->0c4b3d94)
    Ready for finalization 178 objects (0c4b3df4->0c4b40bc)

Я могу добавить к объектам "Ready For Finalization", выполняя вызовы API, но кажется, что поток Finalizer никогда не перемещается и не очищает список.

Поток финализатора, показанный выше, является потоком 002. Вот стек вызовов:

    0:002> !clrstack
    OS Thread Id: 0x29bc (2)
    Child SP       IP Call Site
    03eaf790 77b4f29c [DebuggerU2MCatchHandlerFrame: 03eaf790] 
    0:002> kb 2000
     # ChildEBP RetAddr  Args to Child              
    00 03eae910 773a2080 00000001 03eaead8 00000001 ntdll!NtWaitForMultipleObjects+0xc
    01 03eaeaa4 77047845 00000001 03eaead8 00000000 KERNELBASE!WaitForMultipleObjectsEx+0xf0
    02 03eaeaf0 770475f5 05b72fb8 00000000 ffffffff combase!MTAThreadWaitForCall+0xd5 [d:\rs1\onecore\com\combase\dcomrem\channelb.cxx @ 7290] 
    03 03eaeb24 77018457 03eaee5c 042c15b8 05b72fb8 combase!MTAThreadDispatchCrossApartmentCall+0xb5 [d:\rs1\onecore\com\combase\dcomrem\chancont.cxx @ 227] 
    04 (Inline) -------- -------- -------- -------- combase!CSyncClientCall::SwitchAptAndDispatchCall+0x38a [d:\rs1\onecore\com\combase\dcomrem\channelb.cxx @ 6050] 
    05 03eaec40 76fbe16b 03eaee5c 03eaee34 03eaee5c combase!CSyncClientCall::SendReceive2+0x457 [d:\rs1\onecore\com\combase\dcomrem\channelb.cxx @ 5764] 
    06 (Inline) -------- -------- -------- -------- combase!SyncClientCallRetryContext::SendReceiveWithRetry+0x29 [d:\rs1\onecore\com\combase\dcomrem\callctrl.cxx @ 1734] 
    07 (Inline) -------- -------- -------- -------- combase!CSyncClientCall::SendReceiveInRetryContext+0x29 [d:\rs1\onecore\com\combase\dcomrem\callctrl.cxx @ 632] 
    08 03eaec9c 77017daa 05b72fb8 03eaee5c 03eaee34 combase!DefaultSendReceive+0x8b [d:\rs1\onecore\com\combase\dcomrem\callctrl.cxx @ 590] 
    09 03eaee10 76f72fa5 03eaee5c 03eaee34 03eaf3d0 combase!CSyncClientCall::SendReceive+0x68a [d:\rs1\onecore\com\combase\dcomrem\ctxchnl.cxx @ 767] 
    0a (Inline) -------- -------- -------- -------- combase!CClientChannel::SendReceive+0x7c [d:\rs1\onecore\com\combase\dcomrem\ctxchnl.cxx @ 702] 
    0b 03eaee3c 76e15eea 05b59e04 03eaef48 17147876 combase!NdrExtpProxySendReceive+0xd5 [d:\rs1\onecore\com\combase\ndr\ndrole\proxy.cxx @ 1965] 
    0c 03eaf358 76f73b30 76f5bd70 76f84096 03eaf390 rpcrt4!NdrClientCall2+0x53a
    0d 03eaf378 7706313f 03eaf390 00000008 03eaf420 combase!ObjectStublessClient+0x70 [d:\rs1\onecore\com\combase\ndr\ndrole\i386\stblsclt.cxx @ 217] 
    0e 03eaf388 77026d85 05b59e04 03eaf3d0 011d0940 combase!ObjectStubless+0xf [d:\rs1\onecore\com\combase\ndr\ndrole\i386\stubless.asm @ 171] 
    0f 03eaf420 77026f30 011d0930 73b1a9e0 03eaf4e4 combase!CObjectContext::InternalContextCallback+0x255 [d:\rs1\onecore\com\combase\dcomrem\context.cxx @ 4401] 
    10 03eaf474 73b1a88b 011d0940 73b1a9e0 03eaf4e4 combase!CObjectContext::ContextCallback+0xc0 [d:\rs1\onecore\com\combase\dcomrem\context.cxx @ 4305] 
    11 03eaf574 73b1a962 73b689d0 03eaf60c b42b9326 clr!CtxEntry::EnterContext+0x252
    12 03eaf5ac 73b1a9a3 73b689d0 03eaf60c 00000000 clr!RCW::EnterContext+0x3a
    13 03eaf5d0 73b1eed3 03eaf60c b42b936a 740ecf60 clr!RCWCleanupList::ReleaseRCWListInCorrectCtx+0xbc
    14 03eaf62c 73b6118f b42b90f6 03eaf790 00000000 clr!RCWCleanupList::CleanupAllWrappers+0x119
    15 03eaf67c 73b61568 03eaf790 73b60f00 00000001 clr!SyncBlockCache::CleanupSyncBlocks+0xd0
    16 03eaf68c 73b60fa9 b42b9012 03eaf790 73b60f00 clr!Thread::DoExtraWorkForFinalizer+0x75
    17 03eaf6bc 73a7b4c9 03eaf7dc 011a6248 03eaf7dc clr!FinalizerThread::FinalizerThreadWorker+0xba
    18 03eaf6d0 73a7b533 b42b91fe 03eaf7dc 00000000 clr!ManagedThreadBase_DispatchInner+0x71
    19 03eaf774 73a7b600 b42b915a 73b7a760 73b60f00 clr!ManagedThreadBase_DispatchMiddle+0x7e
    1a 03eaf7d0 73b7a758 00000001 00000000 011b3120 clr!ManagedThreadBase_DispatchOuter+0x5b
    1b 03eaf7f8 73b7a81f b42b9ebe 73b7a760 00000000 clr!ManagedThreadBase::FinalizerBase+0x33
    1c 03eaf834 73af15a1 00000000 00000000 00000000 clr!FinalizerThread::FinalizerThreadStart+0xd4
    1d 03eaf8d0 753c62c4 011ae320 753c62a0 c455bdb0 clr!Thread::intermediateThreadProc+0x55
    1e 03eaf8e4 77b41f69 011ae320 9d323ee5 00000000 kernel32!BaseThreadInitThunk+0x24
    1f 03eaf92c 77b41f34 ffffffff 77b6361e 00000000 ntdll!__RtlUserThreadStart+0x2f
    20 03eaf93c 00000000 73af1550 011ae320 00000000 ntdll!_RtlUserThreadStart+0x1b

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

Спасибо? Не стесняйтесь задавать вопросы, если вам нужна дополнительная информация.

Отредактированные дополнения следуют: Мой руководитель прислал мне код для System.ComponentModel.Component после прочтения анализа Гансом, и мне нужно попытаться получить разъяснение по определенному вопросу. (Даже если код, который мне прислали, был неправильной версией.)

Вот финализатор для System.ComponentModel.Component:

    ~Component() {
        Dispose(false);
    }

Вот Dispose(bool):

    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            lock(this) {
                if (site != null && site.Container != null) {
                    site.Container.Remove(this);
                }
                if (events != null) {
                    EventHandler handler = (EventHandler)events[EventDisposed];
                    if (handler != null) handler(this, EventArgs.Empty);
                }
            }
        }
    }

Надеюсь, я не буду делать глупую ошибку здесь, но если поток финализатора только вызывает Dispose(false), то он не может блокировать что-либо. Если это правда, то я лаю не на том дереве? Должен ли я искать какой-то другой класс?

Как я могу использовать файл дампа, который мне нужен, чтобы определить фактический тип объекта, на котором висит финализатор?

2-е редактирование: я запускал свой нагрузочный тест и следил за выжившими после финализации, пока не начал видеть, как он поднимается вверх. Затем я остановил нагрузочный тест. В результате выжившие после финализации "застряли" на 88, даже если я несколько раз вызывал GC, и никогда не прогрессировали вниз. Я перезапустил и перезапустил SQL Server, но эта статистика осталась на уровне 88.

Я взял дамп процесса и запечатлел вывод finalizequeue, и пока я ломал голову над этим, я заметил, что perfmon внезапно обнаружил падение числа выживших после финализации примерно через 15 минут после того, как я взял дамп.

Я взял еще один дамп, перехватил вывод finalizequeue и сравнил его с предыдущим. В основном это было то же самое со следующими отличиями:

    B   A   Diff    
    4   0   -4  System.Transactions.SafeIUnknown
    6   0   -6  OurCompany.Xpedite.LogOutResponseContent
    20  0   -20 System.Net.Sockets.OverlappedCache
    8   2   -6  System.Security.SafeBSTRHandle
    21  3   -18 System.Net.SafeNativeOverlapped
    6   4   -2  Microsoft.Win32.SafeHandles.SafeFileHandle
    8   9   1   System.Threading.ThreadPoolWorkQueueThreadLocals
    19  1   -18 System.Net.Sockets.OverlappedAsyncResult
    7   2   -5  System.Data.SqlClient.SqlDataAdapter
    7   2   -5  System.Data.DataSet
    13  7   -6  OurCompany.Services.Messaging.Message
    79  13  -66 Microsoft.Win32.SafeHandles.SafeAccessTokenHandle
    6   4   -2  System.IO.FileStream
    24  3   -21 System.Data.SqlClient.SqlConnection
    40  22  -18 Microsoft.Win32.SafeHandles.SafeWaitHandle
    17  3   -14 System.Data.SqlClient.SqlCommand
    14  4   -10 System.Data.DataColumn
    7   2   -5  System.Data.DataTable
    21  20  -1  System.Threading.Thread
    73  68  -5  System.Threading.ReaderWriterLock

1 ответ

Теперь эта проблема решена.

В конечном счете, источником этой проблемы является то, что в одной из наших клиентских библиотек Win32 выполнялись вызовы CoInitialize с использованием модели STA. Эта потенциальная проблема была обнаружена Microsoft до 2006 года и подробно описана в сообщении в блоге главного разработчика Microsoft Тесс Феррандес по этому адресу: https://www.tessferrandez.com/blog/2006/03/26/net-memory- утечка-разблокировать-мой-финализатор.html

Как подробно описано в сообщении, COM может переключать рабочий поток ThreadPool на модель STA, но потоки пула потоков не предназначены для поддержки этой модели потоков.

Приложение работало большую часть времени, потому что во всех случаях COM-объекты принудительно удалялись в потоке API, который их создал, и финализатор не участвовал в очистке. Редким исключением был случай, когда клиентская библиотека WSE2 применила Thread.Abort к одному из наших потоков API, и это случайно помешало выполнению потока как раз в такой момент, что сделало принудительное удаление невозможным, и в этих случаях финализатор был вовлеченный.

Исправление этой проблемы заключалось в поиске в нашей клиентской библиотеке вызовов CoInitialize(nil) и преобразовании этих вызовов и базового кода для использования многопоточной модели. По существу измените их на CoInitializeEx(nil, COINIT_MULTITHREADED);

После инициализации версии COM для MTA финализатору больше не нужно было выполнять какую-либо сортировку в этих случаях Thread.Abort, и проблема была решена.

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