Как расширение 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);
Другие вопросы по тегам