и ещё раз про буст-питон: межмодульная регистрация классов
От: Кодт Россия  
Дата: 15.06.21 09:41
Оценка:
Разгребаю переезд на третий питон.

Модуль расширения (сошка) номер один, назовём её module_a: регистрирует класс A.
BOOST_PYTHON_MODULE(module_a) {
  boost::python::class_(A, .....);
}


Сошка номер два, module_b: регистрирует класс B, унаследованный от A.
Для этого ей нужно, чтобы класс A уже был зарегистрирован в буст-питоне.
BOOST_PYTHON_MODULE(module_b) {
  boost::python::import("module_a");
  boost::python::class_(B, boost::python::bases<A>, .....);
}


Если не выполнить импорт, то буст-питон кинет исключение SystemError.
Если выполнить, то при завершении произойдёт расстрел памяти из-за жидкой связи из-за модулями, которые выгружаются в каком-то не таком порядке.
Способ загрузки-и-регистрации модуля module_a не принципиален. Можно закомментировать эту строчку и написать в скрипте
import module_a  # перед module_b
import module_b

и тоже покрешится.

Питон 3.6, буст 1.65, убунту 20.04.

Может быть, это ошибка в бусте, может, в питоне, я хз. В питоне 2.7 такое не стреляло. Вариант "кривые руки" тоже не исключён, мне самому такой дизайн не очень нравится, но он уже есть.

Есть ли какие-то кошерные способы регистрировать наследников через границы модулей? Или же как-то обойти проблему?
Перекуём баги на фичи!
Re: и ещё раз про буст-питон: межмодульная регистрация классов
От: Буравчик Россия  
Дата: 15.06.21 14:00
Оценка: 36 (1)
Здравствуйте, Кодт, Вы писали:

К>Модуль расширения (сошка) номер один, назовём её module_a: регистрирует класс A.

К>Сошка номер два, module_b: регистрирует класс B, унаследованный от A.
К>Для этого ей нужно, чтобы класс A уже был зарегистрирован в буст-питоне.
К>Если не выполнить импорт, то буст-питон кинет исключение SystemError.
К>Если выполнить, то при завершении произойдёт расстрел памяти из-за жидкой связи из-за модулями, которые выгружаются в каком-то не таком порядке.

Это не оно?

PyFinalize Safety

Currently Boost.Python has several global (or function-static) objects whose existence keeps reference counts from dropping to zero until the Boost.Python shared object is unloaded. This can cause a crash because when the reference counts do go to zero, there's no interpreter. In order to make it safe to call PyFinalize() we must register an atexit routine which destroys these objects and releases all Python reference counts so that Python can clean them up while there's still an interpreter. Dirk Gerrits has promised to do this job.


Проблема, как я понял, в неправильном порядке выгрузки so.
Может вручную выгрузить все — заргистрировшись в atexit? Или так нельзя?

https://stackoverflow.com/questions/31163520/sequence-of-object-cleanup-and-functions-called-by-atexit-in-python-module

P.S. Сразу скажу, что я про это ничего не знаю Просто пальцем в небо
Best regards, Буравчик
Re[2]: и ещё раз про буст-питон: межмодульная регистрация классов
От: Кодт Россия  
Дата: 15.06.21 23:17
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Проблема, как я понял, в неправильном порядке выгрузки so.

Б>Может вручную выгрузить все — заргистрировшись в atexit? Или так нельзя?

Проблема в том, что непонятно, как и что выгрузить! Потому что module_b неявным способом (где-то в недрах реализации boost::python::class_) ссылается на сущности из module_a.
То есть, мне тут надо и загасить module_b, и затем module_a, разрегистрировав классы.
Поэтому, даже если я добавлю обработчик в atexit, — что я там буду делать?
Перекуём баги на фичи!
Re: и ещё раз про буст-питон: межмодульная регистрация классов
От: PM  
Дата: 16.06.21 06:27
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Модуль расширения (сошка) номер один, назовём её module_a: регистрирует класс A.

К>
К>BOOST_PYTHON_MODULE(module_a) {
К>  boost::python::class_(A, .....);
К>}
К>


К>Сошка номер два, module_b: регистрирует класс B, унаследованный от A.

К>Для этого ей нужно, чтобы класс A уже был зарегистрирован в буст-питоне.
К>
К>BOOST_PYTHON_MODULE(module_b) {
К>  boost::python::import("module_a");
К>  boost::python::class_(B, boost::python::bases<A>, .....);
К>}
К>


Насколько я понимаю, boost::python::import() возвращает ссылку на импортированный модуль. Может быть, для продления жизни этой ссылки ее надо куда-то подвесить, например в module_b?
auto module_a = boost::python::import("module_a"); // import before B
BOOST_PYTHON_MODULE(module_b) {
    boost::python::class_(B, boost::python::bases<A>, .....);
}
module_b["__imported_module_a"] = module_a; // dont know how exactly set the module attribute


...

К>Есть ли какие-то кошерные способы регистрировать наследников через границы модулей? Или же как-то обойти проблему?


Если Python встраивается в вашей C++ программе, то наверно у вас есть возможность контролировать время жизни модулей. При встраивании JavaScript я делал простое кэширование загружаемых модулей по имени, и явную выгрузку в обратном порядке перед остановкой интерпретатора. Типа такого
static vector<tuple<string, so_handle, js_handle>> libraries;

// visible in all dynamic modules
[[dllexport]]
js_handle load_library(string name)
{
    js_handle = find(libraries, name);
    if (!js_handle)
    {
        so_handle = ldopen(name);
        js_handle = my_init(so_handle);
        libraries.push_back(name, so_handle, js_handle);
    }
    return js_handle
}

static void unload_all()
{
    auto unload_library = [](tuple<name, so_handle, js_handle> lib)
    {
        my_fini(js_handle);
        js_handle.reset();
        ldclose(so_handle);
    };

    std::for_each(libraries.rbegin() libraries.rend(), unload_library);
    libraries.clear();
}

int main()
{
   init_js_engine();
   js = load_library("main"); // may invoke other load_libray() calls
   run(js);
   unload_all();
}
Re[2]: и ещё раз про буст-питон: межмодульная регистрация классов
От: Кодт Россия  
Дата: 16.06.21 08:41
Оценка:
Здравствуйте, PM, Вы писали:

PM>Насколько я понимаю, boost::python::import() возвращает ссылку на импортированный модуль. Может быть, для продления жизни этой ссылки ее надо куда-то подвесить, например в module_b?


Для продления жизни этот модуль попадает в глобальный sys.modules, так что с жизнью там всё в порядке
Я обвешал всё логами, включая питоньи atexit.register в обоих модулях.

К>>Есть ли какие-то кошерные способы регистрировать наследников через границы модулей? Или же как-то обойти проблему?


PM>Если Python встраивается в вашей C++ программе, то наверно у вас есть возможность контролировать время жизни модулей.


У меня плюсы встраиваются в питон.

Кстати, выгрузка сошек — штука недетерминированная. Просто обнулить счётчик ссылок по dlclose не означает, что сошку прямо сейчас будут выгружать, вызывая её предсмертные колбеки.
Перекуём баги на фичи!
Re[3]: и ещё раз про буст-питон: межмодульная регистрация классов
От: PM  
Дата: 16.06.21 09:39
Оценка:
Здравствуйте, Кодт, Вы писали:

PM>>Насколько я понимаю, boost::python::import() возвращает ссылку на импортированный модуль. Может быть, для продления жизни этой ссылки ее надо куда-то подвесить, например в module_b?


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

К>Я обвешал всё логами, включая питоньи atexit.register в обоих модулях.

Хм, еще одни трудности перехода Python 2 -> 3. А может быть дело в Boost.python. Я вообще думал, она не развивается после появления pybind11.

К>>>Есть ли какие-то кошерные способы регистрировать наследников через границы модулей? Или же как-то обойти проблему?


PM>>Если Python встраивается в вашей C++ программе, то наверно у вас есть возможность контролировать время жизни модулей.


К>У меня плюсы встраиваются в питон.


Ну я бы все-таки попробовал копать в сторону явного дополнительного хранения ссылок, как питонячих, так и на динамическую библиотеку. Кто его знает, где был забыт какой-нибудь Py_INCREF

К>Кстати, выгрузка сошек — штука недетерминированная. Просто обнулить счётчик ссылок по dlclose не означает, что сошку прямо сейчас будут выгружать, вызывая её предсмертные колбеки.


Да обычно вообще динамические библиотеки не стоит выгружать пока работает программа
Такую принудительную выгрузку с явным вызовом своих аналогов init/fini я использовал только для тестирования утечек памяти, перед принудительной же сборкой мусора.
Re[4]: и ещё раз про буст-питон: межмодульная регистрация классов
От: Кодт Россия  
Дата: 16.06.21 10:35
Оценка:
Здравствуйте, PM, Вы писали:

PM>Хм, еще одни трудности перехода Python 2 -> 3. А может быть дело в Boost.python. Я вообще думал, она не развивается после появления pybind11.


Видимо, дело в буст-питоне. Крешится-то явно в обвязке биндинга к классу.
Причём, что интересно, — когда я начал играться с сохранением ссылки на импорт и обнулением её в атекзите, — сценарий креша усложнился. Просто
python3 -c 'import module_b'
перестал валиться, а запуск большого скрипта — продолжил.

PM>Ну я бы все-таки попробовал копать в сторону явного дополнительного хранения ссылок, как питонячих, так и на динамическую библиотеку. Кто его знает, где был забыт какой-нибудь Py_INCREF


К>>Кстати, выгрузка сошек — штука недетерминированная. Просто обнулить счётчик ссылок по dlclose не означает, что сошку прямо сейчас будут выгружать, вызывая её предсмертные колбеки.


PM>Да обычно вообще динамические библиотеки не стоит выгружать пока работает программа

PM>Такую принудительную выгрузку с явным вызовом своих аналогов init/fini я использовал только для тестирования утечек памяти, перед принудительной же сборкой мусора.

Стоит — не стоит, но я недавно разгребал такую проблему, не с питоном, правда, а с плагинами ROS.

Там у менеджера плагинов есть счётчик ссылок. Поэтому удаляешь объект плагина — он делает в конце концов dlclose — и сошка либо остаётся в процессе, либо выгружается, по желанию операционной системы.
А потом создаёшь плагин по второму разу — dlopen — и если сошка была выгружена, то сюрприз, все её статические объекты сконструируются по-новой. Google protobuf из-за этого крешился.
Вылечил тупо, добывал из плагина путь к сошке и делал ей контрольный dlopen, чтобы жила до самой смерти.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.