Spec-Zone.ru › Ruby 3.4

class Fiber::Scheduler

Parent:
Object

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

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

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

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

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

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

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

Реализации Scheduler предоставляются gem-пакетами, такими как 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) → массив_строк или 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) → длина_чтения или -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) → записанная_длина или -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) → длина_чтения или -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.

Ожидается, что вернётся кортеж из 3-х Array I/O, которые готовы.

END_OF_DOCUMENT_MARKER
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) → записанная длина или -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) → результат блока
Исходный код
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, если это возможно. При превышении времени выполнения асинхронной операции, заданного 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));

    return rb_funcall(scheduler, id_unblock, 2, blocker, fiber);
}

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

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

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

Spec-Zone.ru

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