Как предотвратить освобождение python обратных вызовов ctypes во время выхода?

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

#include <stdlib.h>
static void (*callback)(void);

void invoke_callback(void)
{
    callback();
}

void set_callback(void (*new_callback)(void))
{
    callback = new_callback;
}

void init(void)
{
    atexit(invoke_callback);
}

Затем предположим, что я загружаю эту библиотеку с помощью магии ctypes:

import ctypes
shared = ctypes.CDLL('./test.so')

#a callback function
def callback():
    print "callback invoked"

#register functions to run at exit
shared.init()
#set the callback function to invoke
shared.set_callback(ctypes.CFUNCTYPE(None)(callback))
#invoke the callback function
shared.invoke_callback()

#...callback also invoked here, right?

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

callback invoked
callback invoked

К сожалению для меня, это выглядело примерно так:

callback invoked
Segmentation fault

Почему это, спросите вы? Что ж, казалось бы, к моменту вызова функций atexit интерпретатор python освободил память, ранее содержащую обратные вызовы:

(gdb) backtrace
#0  0x000000000049b11d in ?? () <- uh-oh
#1  0x000000000046d245 in ?? () <- ctypes' wrapper?
#2  0x00007ffff6b554a9 in ?? () <- ctypes
   from /usr/lib/python2.7/lib-dynload/_ctypes.x86_64-linux-gnu.so
#3  0x00007ffff6944baf in ffi_closure_unix64_inner ()
   from /usr/lib/x86_64-linux-gnu/libffi.so.6
#4  0x00007ffff6944f28 in ffi_closure_unix64 ()
   from /usr/lib/x86_64-linux-gnu/libffi.so.6
#5  0x00007ffff673e71d in invoke_callback () at test.c:6 <- me
#6  0x00007ffff6f2abc9 in __run_exit_handlers (status=0, 
    listp=0x7ffff72965a8 <__exit_funcs>, 
    run_list_atexit=run_list_atexit@entry=true) at exit.c:82
#7  0x00007ffff6f2ac15 in __GI_exit (status=<optimized out>) at exit.c:104
#8  0x00007ffff6f14b4c in __libc_start_main (main=0x497d80 <main>, argc=2, 
    argv=0x7fffffffe408, init=<optimized out>, fini=<optimized out>, 
    rtld_fini=<optimized out>, stack_end=0x7fffffffe3f8) at libc-start.c:321
#9  0x0000000000497ca0 in _start ()

Теперь мой вопрос. Я на самом деле пытаюсь выполнить привязку к большой кодовой базе C (которую я не могу изменить), содержащей несколько обратных вызовов, вызываемых во время выхода. В настоящее время они вызывают ошибки сегментации при выходе из тестовой программы. Можно ли предотвратить это?


person Michael Rawson    schedule 24.07.2015    source источник
comment
Можете ли вы использовать модуль Python atexit вместо регистрации обратных вызовов в библиотеке C? Если нет, вам нужно будет скомпилировать небольшую библиотеку для обратных вызовов. Интерпретатор Python уже давно разрушен к моменту вызова функций C atexit.   -  person Eryk Sun    schedule 24.07.2015
comment
К сожалению, нет — обратные вызовы являются довольно неотъемлемой частью этого раздела библиотеки (цикла событий). Я прохожу стажировку, поэтому я просто спрошу у своего руководителя, что они хотят делать, когда этот случай произойдет (редко) в понедельник. Спасибо за вашу помощь!   -  person Michael Rawson    schedule 25.07.2015
comment
Значит, вы не заинтересованы в вызове функций при выходе? Это просто побочный эффект от их использования с циклом событий? В этом случае я бы написал адаптер на C или C++, который обертывает каждый обратный вызов Python. Библиотека вызывает оболочку, которая либо вызывает зарегистрированный обратный вызов, либо, если связанный обратный вызов не зарегистрирован, ничего не делает, кроме возврата. Предоставьте функцию для ручной отмены регистрации обратного вызова, а также функцию для отмены регистрации всех обратных вызовов. Затем используйте модуль Python atexit для вызова последнего.   -  person Eryk Sun    schedule 25.07.2015
comment
Нет, цикл событий вызывает зарегистрированные обратные вызовы как во время обычной работы (ура!), так и при завершении работы atexit (бу!). В общем, мне нужно сделать и то, и другое.   -  person Michael Rawson    schedule 25.07.2015


Ответы (1)


Я могу опоздать на вечеринку, но недавно столкнулся с похожей проблемой. По сути, обратный вызов собирает мусор Python. Если вы сделаете это:

callback_type = ctypes.CFUNCTYPE(None)
wrapped_callback = callback_type(callback)
shared.set_callback(wrapped_callback)

это должно решить segfault.

person luxun    schedule 29.04.2020