Как расширение Ruby C может хранить процесс для последующего выполнения?
Цель: разрешить расширению c получать блок /proc для отложенного выполнения при сохранении текущего контекста выполнения.
У меня есть метод в C (подвергается воздействию ruby), который принимает callback
(с помощью VALUE hash
аргумент) или block
,
// For brevity, lets assume m_CBYO is setup to make a CBYO module available to ruby
extern VALUE m_CBYO;
VALUE CBYO_add_callback(VALUE callback)
{
if (rb_block_given_p()) {
callback = rb_block_proc();
}
if (NIL_P(callback)) {
rb_raise(rb_eArgError, "either a block or callback proc is required");
}
// method is called here to add the callback proc to rb_callbacks
}
rb_define_module_function(m_CBYO, "add_callback", CBYO_add_callback, 1);
У меня есть структура, которую я использую для хранения этих дополнительных данных:
struct rb_callback
{
VALUE rb_cb;
unsigned long long lastcall;
struct rb_callback *next;
};
static struct rb_callback *rb_callbacks = NULL;
Когда наступает время (запускаемое epoll), я перебираю обратные вызовы и выполняю каждый обратный вызов:
rb_funcall(cb->rb_cb, rb_intern("call"), 0);
Когда это происходит, я вижу, что он успешно выполняет код ruby в обратном вызове, однако он избегает текущего контекста выполнения.
Пример:
# From ruby including the above extension
CBYO.add_callback do
puts "Hey now."
end
loop do
puts "Waiting for signal..."
sleep 1
end
Когда сигнал будет получен (через epoll), я увижу следующее:
$> Waiting for signal...
$> Waiting for signal...
$> Hey now.
$> // process hangs
$> // Another signal occurs
$> [BUG] vm_call_cfunc - cfp consistency error
Иногда я могу получить более одного сигнала для обработки, прежде чем ошибка снова появится.
0 ответов
Я нашел ответ, исследуя подобную проблему.
Как оказалось, я тоже пытался использовать нативные сигналы потоков (с pthread_create
), которые не поддерживаются с помощью МРТ.
TLDR; В настоящее время Ruby VM не является (на момент написания) потокобезопасным. Посмотрите эту хорошую статью о Ruby Threading, чтобы лучше понять, как работать в этих рамках.
Вы можете использовать Ruby's native_thread_create(rb_thread_t * th), который будет использовать pthread_create
за кулисами. Есть некоторые недостатки, о которых вы можете прочитать в документации выше определения метода. Затем вы можете запустить обратный вызов с помощью метода Ruby rb_thread_call_with_gvl. Кроме того, я не делал этого здесь, но было бы неплохо создать метод-обертку, чтобы вы могли использовать rb_protect
для обработки исключений может возникнуть обратный вызов (в противном случае они будут поглощены виртуальной машиной).
VALUE execute_callback(VALUE callback)
{
return rb_funcall(callback, rb_intern("call"), 0);
}
// execute the callback when the thread receives signal
rb_thread_call_with_gvl(execute_callback, data->callback);