Notice: file_put_contents(): Write of 23150 bytes failed with errno=28 No space left on device in /var/www/tgoop/post.php on line 50
Алло, это отладочная?@gdb_dbg P.33
GDB_DBG Telegram 33
Люблю запах финализаторов по утрам! (часть 3)

Опускаемся еще ниже в ад, поближе к самому сатане.

Недетерминированное поведение.

Как и все связанное с GC, сами финализаторы довольно капризны, а их поведение трудно предсказать. Чаще всего про это говорят в контексте вопроса: "а позовется ли мой финализатор вообще?". Разные языки и рантаймы дают здесь разные ответы. Как я уже писал выше, C# старался дать гарантии введением CER, но теперь все в прошлом. Сейчас самые смелые гарантии из тех, что я видел, дает Python с weakref.finalize, но и там есть ограничения, связанные с daemonic threads (см. вот здесь приписочку внизу). В целом, на обязательность вызова финализатора лучше не полагаться вообще.



Но я расскажу вам куда более страшную историю про недетерминированное поведение финализаторов, которая по традиции пришла к нам из саппорта.

Развал в ntdll.dll => происходит только на нашей VM и то не всегда. Чаще всего при закрытии приложения => раскопки приводят к тому, что проблема в вызове сишной функции GlobalFree от какой-то невалидной памяти. Наш рантайм таким не занимается.

Как выяснилось после дальнейших раскопок, в приложении клиенты хотят позвать функцию WinHttpDetectAutoProxyConfigUrl из WinApi, а та в качестве аргумента принимает LPWSTR*, куда будет записан адрес на выделенную под строчку память (т.е. по факту имеем указатель на указатель). И что интересно: по контракту это уже ответственность пользователя почистить выделенную системой память, после того, как она перестанет быть нужна.

Как все это проделать из Java? Легко (ха-ха), с помощью JNI или, например, JNA. Но вам совершенно точно понадобится Java класс инкапсулирующий указатель на указатель. Ну и раз от внутреннего адреса нужно когда-нибудь (когда он будет больше не нужен) позвать GlobalFree, то где же это сделать? Конечно же в финализаторе этого класса!

Звучит то неплохо, но как это было реализовано: Java класс LPWSTRByReference. Он в свою очередь содержит ссылку на класс Memory из JNA (все верно, указатель на указатель). В финализаторе LPWSTRByReference аккуратно достаем из Memory указатель, смотрим, не null ли он, а потом вызываем от него GlobalFree. И все бы ничего, но ведь и объектам Memory тоже нужно как-то чистить свою память! Ведь это по сути контейнер для указателя, а память под этот контейнер выделяли маллоком. Поэтому что? Правильно, у Memory тоже был финализатор, в котором вызывался free уже от контейнера. Сам адрес при этом не зануляется, там остается мусор.

И тут возникает интересный вопрос: допустим, что оба объекта: и LPWSTRByReference и Memory стали недостижимы (а так и случается, единственная ссылка на Memory из LPWSTRByReference), чей финализатор позовется первее? Это важно, ведь если LPWSTRByReference, то все сработает отлично, а если Memory, то в финализаторе LPWSTRByReference позовется GlobalFree от битой уже памяти.

Так вот проблема в том, что порядок исполнения финализаторов не определен. Он может быть абсолютно произвольным для одновременно ставших недоступными объектов. Нам не везло, поэтому чаще вызывался вперед вызывался финализатор Memory, отсюда спорадичные развалы. Finita la commedia.

Как managed языки пытаются такую проблему исправить?

Тот же Golang вводит четкий порядок финализации:

Finalizers are run in dependency order: if A points at B, both have finalizers, and they are otherwise unreachable, only the finalizer for A runs; once A is freed, the finalizer for B can run.


правда как и всегда с топологической сортировкой, все портят циклы:

If a cyclic structure includes a block with a finalizer, that cycle is not guaranteed to be garbage collected and the finalizer is not guaranteed to run, because there is no ordering that respects the dependencies.


Про Python сказано, что weakref.finalize(...) вызываются в порядке обратном регистрации callbacks, что, конечно, тоже оставляет много вопросов, но хоть так порядок фиксирует.



Какие же мы из всего этого можем сделать выводы? ↓
👍6💯3🌚1



tgoop.com/gdb_dbg/33
Create:
Last Update:

Люблю запах финализаторов по утрам! (часть 3)

Опускаемся еще ниже в ад, поближе к самому сатане.

Недетерминированное поведение.

Как и все связанное с GC, сами финализаторы довольно капризны, а их поведение трудно предсказать. Чаще всего про это говорят в контексте вопроса: "а позовется ли мой финализатор вообще?". Разные языки и рантаймы дают здесь разные ответы. Как я уже писал выше, C# старался дать гарантии введением CER, но теперь все в прошлом. Сейчас самые смелые гарантии из тех, что я видел, дает Python с weakref.finalize, но и там есть ограничения, связанные с daemonic threads (см. вот здесь приписочку внизу). В целом, на обязательность вызова финализатора лучше не полагаться вообще.



Но я расскажу вам куда более страшную историю про недетерминированное поведение финализаторов, которая по традиции пришла к нам из саппорта.

Развал в ntdll.dll => происходит только на нашей VM и то не всегда. Чаще всего при закрытии приложения => раскопки приводят к тому, что проблема в вызове сишной функции GlobalFree от какой-то невалидной памяти. Наш рантайм таким не занимается.

Как выяснилось после дальнейших раскопок, в приложении клиенты хотят позвать функцию WinHttpDetectAutoProxyConfigUrl из WinApi, а та в качестве аргумента принимает LPWSTR*, куда будет записан адрес на выделенную под строчку память (т.е. по факту имеем указатель на указатель). И что интересно: по контракту это уже ответственность пользователя почистить выделенную системой память, после того, как она перестанет быть нужна.

Как все это проделать из Java? Легко (ха-ха), с помощью JNI или, например, JNA. Но вам совершенно точно понадобится Java класс инкапсулирующий указатель на указатель. Ну и раз от внутреннего адреса нужно когда-нибудь (когда он будет больше не нужен) позвать GlobalFree, то где же это сделать? Конечно же в финализаторе этого класса!

Звучит то неплохо, но как это было реализовано: Java класс LPWSTRByReference. Он в свою очередь содержит ссылку на класс Memory из JNA (все верно, указатель на указатель). В финализаторе LPWSTRByReference аккуратно достаем из Memory указатель, смотрим, не null ли он, а потом вызываем от него GlobalFree. И все бы ничего, но ведь и объектам Memory тоже нужно как-то чистить свою память! Ведь это по сути контейнер для указателя, а память под этот контейнер выделяли маллоком. Поэтому что? Правильно, у Memory тоже был финализатор, в котором вызывался free уже от контейнера. Сам адрес при этом не зануляется, там остается мусор.

И тут возникает интересный вопрос: допустим, что оба объекта: и LPWSTRByReference и Memory стали недостижимы (а так и случается, единственная ссылка на Memory из LPWSTRByReference), чей финализатор позовется первее? Это важно, ведь если LPWSTRByReference, то все сработает отлично, а если Memory, то в финализаторе LPWSTRByReference позовется GlobalFree от битой уже памяти.

Так вот проблема в том, что порядок исполнения финализаторов не определен. Он может быть абсолютно произвольным для одновременно ставших недоступными объектов. Нам не везло, поэтому чаще вызывался вперед вызывался финализатор Memory, отсюда спорадичные развалы. Finita la commedia.

Как managed языки пытаются такую проблему исправить?

Тот же Golang вводит четкий порядок финализации:

Finalizers are run in dependency order: if A points at B, both have finalizers, and they are otherwise unreachable, only the finalizer for A runs; once A is freed, the finalizer for B can run.


правда как и всегда с топологической сортировкой, все портят циклы:

If a cyclic structure includes a block with a finalizer, that cycle is not guaranteed to be garbage collected and the finalizer is not guaranteed to run, because there is no ordering that respects the dependencies.


Про Python сказано, что weakref.finalize(...) вызываются в порядке обратном регистрации callbacks, что, конечно, тоже оставляет много вопросов, но хоть так порядок фиксирует.



Какие же мы из всего этого можем сделать выводы? ↓

BY Алло, это отладочная?




Share with your friend now:
tgoop.com/gdb_dbg/33

View MORE
Open in Telegram


Telegram News

Date: |

Activate up to 20 bots Your posting frequency depends on the topic of your channel. If you have a news channel, it’s OK to publish new content every day (or even every hour). For other industries, stick with 2-3 large posts a week. Telegram channels enable users to broadcast messages to multiple users simultaneously. Like on social media, users need to subscribe to your channel to get access to your content published by one or more administrators. Some Telegram Channels content management tips 2How to set up a Telegram channel? (A step-by-step tutorial)
from us


Telegram Алло, это отладочная?
FROM American