Spec-Zone.ru › Ruby 3.4
  1. Fiber::
  2. Scheduler

class Fiber::Scheduler

Родительский класс:
Object

Это не существующий класс, а документация интерфейса, которому должен соответствовать объект Scheduler, чтобы его можно было использовать в качестве аргумента для Fiber.scheduler и обрабатывать неблокирующие волокна. Объяснения некоторых понятий см. также в разделе «Неблокирующие волокна» документации класса Fiber.

Ожидается, что планировщик будет работать и использоваться следующим образом:

  • Когда выполнение в неблокирующем Fiber достигает блокирующей операции (например, sleep, ожидания процесса или неготового ввода-вывода), вызывается один из перечисленных ниже методов-перехватчиков планировщика.

  • Scheduler каким-либо образом регистрирует ресурс, которого ожидает текущее волокно, и передаёт управление другим волокнам с помощью Fiber.yield (чтобы волокно приостановилось в ожидании завершения операции, а другие волокна в том же потоке могли выполняться)

  • В конце выполнения текущего потока вызывается метод планировщика scheduler_close

  • Планировщик переходит в цикл ожидания, проверяет все заблокированные волокна (зарегистрированные при вызовах методов-перехватчиков) и возобновляет их, когда ожидаемый ресурс становится доступен (например, ввод-вывод готов или истекло время ожидания).

Таким образом, параллельное выполнение будет обеспечено прозрачно для кода каждого отдельного Fiber.

Реализации Scheduler предоставляются гемами, например Async.

Методы-перехватчики:

  • io_wait, io_read, io_write, io_pread, io_pwrite и io_select, io_close

  • process_wait

  • kernel_sleep

  • timeout_after

  • address_resolve

  • block и unblock

  • blocking_operation_wait

  • (список будет расширяться по мере того, как разработчики Ruby будут добавлять методы с неблокирующими вызовами)

Если не указано иное, реализация методов-перехватчиков обязательна: если они не реализованы, методы, пытающиеся вызвать перехватчик, завершатся ошибкой. Для обеспечения обратной совместимости в будущем перехватчики станут необязательными (если они не реализованы, поскольку планировщик создан для более старой версии Ruby, код, которому нужен этот перехватчик, не завершится ошибкой и просто будет работать в блокирующем режиме).

Также настоятельно рекомендуется, чтобы планировщик реализовывал метод fiber, который вызывается из Fiber.schedule.

Пример игрушечной реализации планировщика можно найти в исходном коде Ruby, в test/fiber/scheduler.rb

Открытые методы экземпляра

address_resolve(hostname) → array_of_strings or nil Показать исходный код
VALUE
rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname)
{
    VALUE arguments[] = {
        hostname
    };

    return rb_check_funcall(scheduler, id_address_resolve, 1, arguments);
}

Вызывается любым методом, выполняющим DNS-поиск без обратного разрешения. Наиболее примечательный из таких методов — Addrinfo.getaddrinfo, но есть и многие другие.

Ожидается, что метод вернёт массив строк, соответствующих IP-адресам, в которые разрешается hostname, или nil, если разрешить его не удалось.

Достаточно полный список всех возможных мест вызова:

  • Addrinfo.getaddrinfo

  • Addrinfo.tcp

  • Addrinfo.udp

  • Addrinfo.ip

  • Addrinfo.new

  • Addrinfo.marshal_load

  • SOCKSSocket.new

  • TCPServer.new

  • TCPSocket.new

  • IPSocket.getaddress

  • TCPSocket.gethostbyname

  • UDPSocket#connect

  • UDPSocket#bind

  • UDPSocket#send

  • Socket.getaddrinfo

  • Socket.gethostbyname

  • Socket.pack_sockaddr_in

  • Socket.sockaddr_in

  • Socket.unpack_sockaddr_in

block(blocker, timeout = nil) Показать исходный код
VALUE
rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout)
{
    return rb_funcall(scheduler, id_block, 2, blocker, timeout);
}

Вызывается такими методами, как Thread.join, а также Mutex, чтобы указать, что текущее Fiber заблокировано до дальнейшего уведомления (например, unblock) или до истечения timeout.

blocker — это то, чего мы ожидаем; значение носит исключительно информационный характер (для отладки и ведения журнала). Его значение не гарантируется.

Ожидается, что метод вернёт логическое значение, указывающее, успешно ли выполнена операция блокировки.

blocking_operation_wait(work) Показать исходный код
VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state)
{
    struct rb_blocking_operation_wait_arguments arguments = {
        .function = function,
        .data = data,
        .unblock_function = unblock_function,
        .data2 = data2,
        .flags = flags,
        .state = state
    };

    VALUE proc = rb_proc_new(rb_fiber_scheduler_blocking_operation_wait_proc, (VALUE)&arguments);

    return rb_check_funcall(scheduler, id_blocking_operation_wait, 1, &proc);
}

Вызывается основными методами Ruby для выполнения блокирующей операции неблокирующим способом.

Минимальная рекомендуемая реализация:

def blocking_operation_wait(work)
  Thread.new(&work).join
end
close () Показать исходный код
VALUE
rb_fiber_scheduler_close(VALUE scheduler)
{
    RUBY_ASSERT(ruby_thread_has_gvl_p());

    VALUE result;

    // The reason for calling `scheduler_close` before calling `close` is for
    // legacy schedulers which implement `close` and expect the user to call
    // it. Subsequently, that method would call `Fiber.set_scheduler(nil)`
    // which should call `scheduler_close`. If it were to call `close`, it
    // would create an infinite loop.

    result = rb_check_funcall(scheduler, id_scheduler_close, 0, NULL);
    if (!UNDEF_P(result)) return result;

    result = rb_check_funcall(scheduler, id_close, 0, NULL);
    if (!UNDEF_P(result)) return result;

    return Qnil;
}

Вызывается при завершении текущего потока. Ожидается, что планировщик реализует этот метод, чтобы все ожидающие волокна могли завершить выполнение.

Рекомендуется реализовать основной цикл обработки событий в методе close.

fiber(&block)

Реализация метода Fiber.schedule. Ожидается, что этот метод немедленно выполнит переданный блок кода в отдельном неблокирующем волокне и вернёт это Fiber.

Минимальная рекомендуемая реализация:

def fiber(&block)
  fiber = Fiber.new(blocking: false, &block)
  fiber.resume
  fiber
end
io_pread(io, buffer, from, length, offset) → read length or -errno Показать исходный код
VALUE
rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset)
{
    VALUE arguments[] = {
        io, buffer, OFFT2NUM(from), SIZET2NUM(length), SIZET2NUM(offset)
    };

    return rb_check_funcall(scheduler, id_io_pread, 5, arguments);
}

Вызывается методами IO#pread или IO::Buffer#pread для чтения length байт из io со смещения from в указанный buffer (см. IO::Buffer) по заданному offset.

Семантически этот метод совпадает с io_read, но позволяет задать смещение для чтения и часто лучше подходит для асинхронного IO из одного и того же файла.

Метод следует считать экспериментальным.

io_pwrite(io, buffer, from, length, offset) → written length or -errno Показать исходный код
VALUE
rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset)
{
    VALUE arguments[] = {
        io, buffer, OFFT2NUM(from), SIZET2NUM(length), SIZET2NUM(offset)
    };

    return rb_check_funcall(scheduler, id_io_pwrite, 5, arguments);
}

Вызывается методами IO#pwrite или IO::Buffer#pwrite для записи length байт в io со смещения from в указанный buffer (см. IO::Buffer) по заданному offset.

Семантически этот метод совпадает с io_write, но позволяет задать смещение для записи и часто лучше подходит для асинхронного IO в один и тот же файл.

Метод следует считать экспериментальным.

io_read(io, buffer, length, offset) → read length or -errno Показать исходный код
VALUE
rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset)
{
    VALUE arguments[] = {
        io, buffer, SIZET2NUM(length), SIZET2NUM(offset)
    };

    return rb_check_funcall(scheduler, id_io_read, 4, arguments);
}

Вызывается методами IO#read или IO#Buffer.read для чтения length байт из io в указанный buffer (см. IO::Buffer) по заданному offset.

Аргумент length означает «минимальное количество байт для чтения». Если размер буфера IO составляет 8 КиБ, а length равен 1024 (1 КиБ), может быть прочитано до 8 КиБ, но не менее 1 КиБ. Как правило, единственный случай, когда будет прочитано меньше данных, чем length, — это ошибка при чтении данных.

Допустимо указать length равным 0; это означает, что следует хотя бы один раз попытаться прочитать данные и вернуть все доступные данные.

Рекомендуемая реализация должна пытаться читать из io в неблокирующем режиме и вызывать io_wait, если io не готов (при этом управление будет передано другим волокнам).

Описание интерфейса для возврата данных см. в разделе IO::Buffer.

Ожидается, что метод вернёт количество прочитанных байт или, в случае ошибки, -errno (отрицательное значение, соответствующее коду системной ошибки).

Метод следует считать экспериментальным.

io_select(readables, writables, exceptables, timeout) Показать исходный код
VALUE rb_fiber_scheduler_io_select(VALUE scheduler, VALUE readables, VALUE writables, VALUE exceptables, VALUE timeout)
{
    VALUE arguments[] = {
        readables, writables, exceptables, timeout
    };

    return rb_fiber_scheduler_io_selectv(scheduler, 4, arguments);
}

Вызывается методом IO.select, чтобы проверить, готовы ли указанные дескрипторы к заданным событиям в течение указанного timeout.

Ожидается, что метод вернёт кортеж из трёх Array объектов IO, готовых к работе.

io_wait(io, events, timeout) Показать исходный код
VALUE
rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout)
{
    return rb_funcall(scheduler, id_io_wait, 3, io, events, timeout);
}

Вызывается методами IO#wait, IO#wait_readable, IO#wait_writable, чтобы проверить, готов ли указанный дескриптор к заданным событиям в течение указанного timeout.

events — это битовая маска из IO::READABLE, IO::WRITABLE и IO::PRIORITY.

Рекомендуемая реализация должна зарегистрировать, какое Fiber ожидает какие ресурсы, и немедленно вызвать Fiber.yield, чтобы передать управление другим волокнам. Затем в методе close планировщик может распределить все ресурсы ввода-вывода между ожидающими их волокнами.

Ожидается, что метод вернёт подмножество событий, готовых немедленно.

io_write(io, buffer, length, offset) → written length or -errno Показать исходный код
VALUE
rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset)
{
    VALUE arguments[] = {
        io, buffer, SIZET2NUM(length), SIZET2NUM(offset)
    };

    return rb_check_funcall(scheduler, id_io_write, 4, arguments);
}

Вызывается методами IO#write или IO::Buffer#write для записи length байт в io из указанного buffer (см. IO::Buffer) по заданному offset.

Аргумент length означает «минимальное количество байт для записи». Если размер буфера IO составляет 8 КиБ, а заданное значение length равно 1024 (1 КиБ), будет записано не более 8 КиБ, но не менее 1 КиБ. Как правило, единственный случай, когда будет записано меньше данных, чем length, — это ошибка при записи данных.

Допустимо указать length равным 0; это означает, что следует хотя бы один раз попытаться записать как можно больше данных.

Рекомендуемая реализация должна пытаться записывать в io в неблокирующем режиме и вызывать io_wait, если io не готов (при этом управление будет передано другим волокнам).

Описание интерфейса для эффективного получения данных из буфера см. в разделе IO::Buffer.

Ожидается, что метод вернёт количество записанных байт или, в случае ошибки, -errno (отрицательное значение, соответствующее коду системной ошибки).

Метод следует считать экспериментальным.

kernel_sleep(duration = nil) Показать исходный код
VALUE
rb_fiber_scheduler_kernel_sleep(VALUE scheduler, VALUE timeout)
{
    return rb_funcall(scheduler, id_kernel_sleep, 1, timeout);
}

Вызывается методами Kernel#sleep и Mutex#sleep. Ожидается, что метод обеспечит неблокирующую реализацию ожидания. Реализация может зарегистрировать текущее волокно в списке «какое волокно и до какого момента ожидает», вызвать Fiber.yield, чтобы передать управление, а затем в методе close возобновить волокна, время ожидания которых истекло.

process_wait(pid, flags) Показать исходный код
VALUE
rb_fiber_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags)
{
    VALUE arguments[] = {
        PIDT2NUM(pid), RB_INT2NUM(flags)
    };

    return rb_check_funcall(scheduler, id_process_wait, 2, arguments);
}

Вызывается методом Process::Status.wait для ожидания указанного процесса. Описание аргументов см. в описании этого метода.

Рекомендуемая минимальная реализация:

Thread.new do
  Process::Status.wait(pid, flags)
end.value

Этот перехватчик необязателен: если в текущем планировщике он отсутствует, Process::Status.wait будет работать как блокирующий метод.

Ожидается, что метод вернёт экземпляр Process::Status.

timeout_after(duration, exception_class, *exception_arguments, &block) → result of block Показать исходный код
VALUE
rb_fiber_scheduler_timeout_after(VALUE scheduler, VALUE timeout, VALUE exception, VALUE message)
{
    VALUE arguments[] = {
        timeout, exception, message
    };

    return rb_check_funcall(scheduler, id_timeout_after, 3, arguments);
}

Вызывается методом Timeout.timeout для выполнения переданного block в течение заданного duration. Его также может напрямую вызвать планировщик или пользовательский код.

По возможности пытается ограничить время выполнения заданного block указанным duration. Если неблокирующая операция приводит к тому, что время выполнения block превышает заданное duration, эта неблокирующая операция должна быть прервана путём возбуждения указанного exception_class, созданного с переданными exception_arguments.

Общие тайм-ауты выполнения часто считают рискованными. Эта реализация прерывает только неблокирующие операции. Это сделано намеренно: неблокирующие операции могут завершаться сбоем по разным непредсказуемым причинам, поэтому приложения уже должны быть устойчивы к таким ситуациям и, следовательно, к тайм-аутам.

Однако вследствие такого подхода, если block не вызывает неблокирующих операций, прервать его будет невозможно. Если требуется обеспечить предсказуемые точки срабатывания тайм-аутов, добавьте +sleep(0)+.

Если блок выполнится успешно, будет возвращён его результат.

Исключение обычно возбуждается с помощью Fiber#raise.

unblock(blocker, fiber) Показать исходный код
VALUE
rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber)
{
    RUBY_ASSERT(rb_obj_is_fiber(fiber));

    VALUE result;
    enum ruby_tag_type state;

    // `rb_fiber_scheduler_unblock` can be called from points where `errno` is expected to be preserved. Therefore, we should save and restore it. For example `io_binwrite` calls `rb_fiber_scheduler_unblock` and if `errno` is reset to 0 by user code, it will break the error handling in `io_write`.
    //
    // If we explicitly preserve `errno` in `io_binwrite` and other similar functions (e.g. by returning it), this code is no longer needed. I hope in the future we will be able to remove it.
    int saved_errno = errno;

    // We must prevent interrupts while invoking the unblock method, because otherwise fibers can be left permanently blocked if an interrupt occurs during the execution of user code.
    rb_execution_context_t *ec = GET_EC();
    int saved_interrupt_mask = ec->interrupt_mask;
    ec->interrupt_mask |= PENDING_INTERRUPT_MASK;

    EC_PUSH_TAG(ec);
    if ((state = EC_EXEC_TAG()) == TAG_NONE) {
        result = rb_funcall(scheduler, id_unblock, 2, blocker, fiber);
    }
    EC_POP_TAG();

    ec->interrupt_mask = saved_interrupt_mask;

    if (state) {
        EC_JUMP_TAG(ec, state);
    }

    RUBY_VM_CHECK_INTS(ec);

    errno = saved_errno;

    return result;
}

Вызывается для пробуждения Fiber, ранее заблокированного с помощью block (например, Mutex#lock вызывает block, а Mutex#unlock вызывает unblock). Планировщик должен использовать параметр fiber, чтобы определить, какое волокно разблокируется.

blocker — это то, чего ожидали, но значение носит исключительно информационный характер (для отладки и ведения журнала), и не гарантируется, что оно совпадает со значением blocker для block.

Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API