класс Fiber::SchedulerInterface
Это не существующий класс, а документация интерфейса, которому должен соответствовать объект Scheduler, чтобы использоваться в качестве аргумента для Fiber.scheduler и обрабатывать неблокирующие волокна. См. также раздел «Неблокирующие волокна» в документации класса Fiber для объяснений некоторых концепций.
Ожидается, что поведение и использование планировщика будут следующими:
-
Когда выполнение в неблокирующем
Fiberдостигает какой-либо блокирующей операции (например, sleep, ожидание процесса или неподготовленного ввода-вывода), оно вызывает некоторые из методов-обработчиков планировщика, перечисленные ниже. -
Планировщик каким-то образом регистрирует то, на чём ожидает текущее волокно, и уступает управление другим волокнам с помощью
Fiber.yield(тем самым волокно будет приостановлено, ожидая завершения ожидания, и другие волокна в том же потоке смогут выполнить свои задачи). -
В конце выполнения текущего потока вызывается метод планировщика
close -
Планировщик попадает в цикл ожидания, проверяя все заблокированные волокна (которые были зарегистрированы при вызовах обработчиков) и возобновляя их, когда ожидаемый ресурс становится доступным (например, ввод-вывод готов или истекло время ожидания).
Типичная реализация, вероятно, будет опираться на такой жемчужину, как EventMachine или Async.
Таким образом, одновременное выполнение будет достигнуто прозрачно для каждого отдельного кода волокна.
Методы-обработчики:
-
(список расширяется по мере того, как разработчики Ruby создают больше методов с неблокирующими вызовами)
Если не указано иное, реализации обработчиков обязательны: если они не реализованы, методы, пытающиеся вызвать обработчик, завершатся с ошибкой. Для обеспечения обратной совместимости в будущем обработчики будут необязательными (если они не реализованы из-за того, что планировщик создан для более старой версии Ruby, код, которому нужен этот обработчик, не завершится с ошибкой, а просто будет работать в блокирующем режиме).
Также настоятельно рекомендуется, чтобы планировщик реализовывал метод fiber, которому делегирует Fiber.schedule.
Пример игрушечной реализации планировщика можно найти в коде Ruby в test/fiber/scheduler.rb
Общедоступные методы экземпляра
static VALUE
rb_fiber_scheduler_interface_address_resolve(VALUE self)
{
} Вызывается любым методом, выполняющим необратный поиск DNS. Наиболее заметный метод — Addrinfo.getaddrinfo, но их много других.
Метод должен вернуть массив строк, соответствующих IP-адресам, к которым разрешается hostname, или nil, если его нельзя разрешить.
Достаточно исчерпывающий список всех возможных мест вызова:
-
Addrinfo.marshal_load
static VALUE
rb_fiber_scheduler_interface_block(VALUE self)
{
} Вызывается методами, такими как Thread.join, и Mutex, чтобы указать, что текущее Fiber заблокировано до дальнейшего уведомления (например, unblock) или до истечения timeout.
blocker — то, на что мы ждём, только информативно (для отладки и ведения журнала). Нет гарантий относительно его значения.
Ожидается вернуть булево значение, указывающее, был ли успешным блокирующий процесс или нет.
static VALUE
rb_fiber_scheduler_interface_close(VALUE self)
{
} Вызывается при завершении текущей потоковой задачи. Планировщик должен реализовать этот метод, чтобы позволить всем ожидающим волокнам завершить выполнение.
Рекомендуемый шаблон заключается в реализации основного цикла событий в методе close.
static VALUE
rb_fiber_scheduler_interface_fiber(VALUE self)
{
} Реализация Fiber.schedule. Метод должен немедленно выполнить заданный блок кода в отдельном неблокирующем волокне и вернуть это Fiber.
Минимальная рекомендуемая реализация:
def fiber(&block) fiber = Fiber.new(blocking: false, &block) fiber.resume fiber end
static VALUE
rb_fiber_scheduler_interface_io_read(VALUE self)
{
} Вызывается IO#read для чтения length байтов из io в указанный buffer (см. IO::Buffer).
Аргумент length — это «минимальная длина для чтения». Если размер буфера IO составляет 8 Кбайт, но length составляет 1024 (1 Кбайт), может быть прочитано до 8 Кбайт, но не менее 1 Кбайт. Как правило, единственный случай, когда будет прочитано меньше данных, чем length, — это наличие ошибки чтения данных.
Указание length 0 допустимо и означает попытку чтения хотя бы один раз и возвращение всех доступных данных.
Рекомендуется реализация должна пытаться прочитать из io в неблокирующем режиме и вызвать io_wait, если io не готов (что передаст управление другим волокнам).
См. IO::Buffer для интерфейса, доступного для эффективного возврата данных.
Ожидается, что метод вернёт количество прочитанных байтов или, в случае ошибки, -errno (отрицательное число, соответствующее системному коду ошибки).
Метод следует считать экспериментальным.
static VALUE
rb_fiber_scheduler_interface_io_wait(VALUE self)
{
} Вызывается IO#wait, IO#wait_readable, IO#wait_writable для запроса о готовности указанного дескриптора для указанных событий в течение указанного timeout.
events — это битовая маска IO::READABLE, IO::WRITABLE, и IO::PRIORITY.
Рекомендуемая реализация должна зарегистрировать, какие Fiber ожидают каких ресурсов, и немедленно вызвать Fiber.yield для передачи управления другим волокнам. Затем в методе close планировщик может распределить все ресурсы ввода-вывода ожидающим их волокнам.
Ожидается, что метод вернёт подмножество событий, которые готовы немедленно.
static VALUE
rb_fiber_scheduler_interface_io_write(VALUE self)
{
} Вызывается IO#write для записи length байтов в io из указанного buffer (см. IO::Buffer).
Аргумент length — это «(минимальная) длина для записи». Если размер буфера IO составляет 8 Кбайт, но указанный length 1024 (1 Кбайт), будет записано не более 8 Кбайт, но не менее 1 Кбайт. Как правило, единственный случай, когда будет записано меньше данных, чем length, — это наличие ошибки записи данных.
Указание length 0 допустимо и означает попытку записи хотя бы один раз и записи максимального возможного объёма данных.
Рекомендуемая реализация должна пытаться записать в io в неблокирующем режиме и вызвать io_wait, если io не готов (что передаст управление другим волокнам).
См. IO::Buffer для интерфейса, доступного для получения данных из буфера эффективно.
Ожидается, что метод вернёт количество записанных байтов или, в случае ошибки, -errno (отрицательное число, соответствующее системному коду ошибки).
Метод следует считать экспериментальным.
static VALUE
rb_fiber_scheduler_interface_kernel_sleep(VALUE self)
{
} Вызывается Kernel#sleep и Mutex#sleep и должен обеспечить реализацию ожидания в неблокирующем режиме. Реализация может зарегистрировать текущее волокно в некотором списке «какое волокно ждёт до какого момента», вызвать Fiber.yield для передачи управления, а затем в close возобновить волокна, чей период ожидания истек.
static VALUE
rb_fiber_scheduler_interface_process_wait(VALUE self)
{
} Вызывается Process::Status.wait для ожидания указанного процесса. См. описание этого метода для описания аргументов.
Рекомендуемая минимальная реализация:
Thread.new do Process::Status.wait(pid, flags) end.value
Этот хук необязателен: если его нет в текущем планировщике, Process::Status.wait будет вести себя как блокирующий метод.
Ожидается, что метод вернёт экземпляр Process::Status.
static VALUE
rb_fiber_scheduler_interface_timeout_after(VALUE self)
{
} Вызывается методом Timeout.timeout для выполнения заданного block в течение заданного duration. Также может быть вызван напрямую планировщиком или пользовательским кодом.
Попытка ограничить время выполнения данного block заданным duration, если это возможно. Когда время выполнения block превышает указанное duration, эта неблокирующая операция должна быть прервана путём поднятия указанного exception_class с заданными exception_arguments.
Общие таймауты выполнения часто считаются рискованными. Эта реализация будет прерывать только неблокирующие операции. Это сделано по умолчанию, потому что ожидается, что неблокирующие операции могут завершиться по различным непредсказуемым причинам, поэтому приложения должны уже быть устойчивыми к обработке этих условий и, по аналогии, таймаутов.
Однако, в результате этого дизайна, если block не вызывает никаких неблокирующих операций, прервать его будет невозможно. Если вам нужно обеспечить предсказуемые моменты для таймаутов, рассмотрите добавление +sleep(0)+.
Если блок выполняется успешно, его результат будет возвращён.
Исключение обычно поднимается с помощью Fiber#raise.
static VALUE
rb_fiber_scheduler_interface_unblock(VALUE self)
{
} Вызывается для разблокировки Fiber, ранее заблокированного с помощью block (например, вызовы Mutex#lock вызывают block, а Mutex#unlock вызывают unblock). Планировщик должен использовать параметр fiber для понимания, какая волокна разблокирована.
blocker — то, что ожидалось, но это только информационно (для отладки и протоколирования), и это не гарантируется, что будет таким же значением, как blocker для block.
Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.