Project

General

Profile

Actions

Bug #18485

closed

Even though init a blocking Fiber with Fiber.new(blocking: true) but scheduler is invoked

Added by jakit (Jakit Liang) almost 3 years ago. Updated almost 3 years ago.

Status:
Closed
Target version:
-
ruby -v:
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin20]
[ruby-core:107098]

Description

For example:

require 'fiber'
require 'io/nonblock'

class SimpleScheduler
  def initialize
    @readable = {}
    @writable = {}
    @waiting = {}
    @ready = []
    @blocking = 0
    @urgent = IO.pipe
  end

  def run
    p 'aaaaaaa'
    while @readable.any? or @writable.any? or @waiting.any? or @blocking.positive? or @ready.any?
      p 'bbbbbbb'
      readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], 0)

      readable&.each do |io|
        if fiber = @readable.delete(io)
          fiber.resume
        end
      end

      writable&.each do |io|
        if fiber = @writable.delete(io)
          fiber.resume
        end
      end

      @waiting.keys.each do |fiber|
        if current_time > @waiting[fiber]
          @waiting.delete(fiber)
          fiber.resume
        end
      end

      ready, @ready = @ready, []
      ready.each do |fiber|
        fiber.resume
      end
    end
  end

  def io_wait(io, events, timeout)
    p 'ccccccc'
    unless (events & IO::READABLE).zero?
      @readable[io] = Fiber.current
    end
    unless (events & IO::WRITABLE).zero?
      @writable[io] = Fiber.current
    end

    Fiber.yield
    return events
  end

  def kernel_sleep(duration = nil)
    p 'ddddddd'
    block(:sleep, duration)
    return true
  end

  def block(blocker, timeout = nil)
    p 'eeeeeeee'
    if timeout
      @waiting[Fiber.current] = current_time + timeout
      begin
        Fiber.yield
      ensure
        @waiting.delete(Fiber.current)
      end
    else
      @blocking += 1
      begin
        Fiber.yield
      ensure
        @blocking -= 1
      end
    end
  end

  def unblock(blocker, fiber)
    p 'ffffffffff'
    @ready << fiber
    io = @urgent.last
    io.write_nonblock('.')
  end

  def close
    p 'ggggggggg'
    run
    @urgent.each(&:close)
    @urgent = nil
  end

  private
  def current_time
    p 'hhhhhhhh'
    Process.clock_gettime(Process::CLOCK_MONOTONIC)
  end
end

scheduler = SimpleScheduler.new
Fiber.set_scheduler(scheduler)

f = Fiber.new(blocking: true) do
  p '1111111'
  sleep(1)
  p '2222222'
end

f.resume

Output:

"1111111"
"2222222"
"ggggggggg"
"aaaaaaa"

You can see the SimpleScheduler.run and SimpleScheduler.close is invoked.

If I write my own gem using IO.read and IO.write. User will track in my gem and my code.

I can not avoid it but to Fiber.set_scheduler(nil). By this way, I would break users' code clearing their scheduler.

How strong intrusive it is!

Here's the running result I expected:

"1111111"
"2222222"

I don't want any Fiber.scheduler hooking into the origin implementation as IO.read and IO.write did before.

Composition over inheritance.

I hope Fiber.scheduler could be a standalone class like Dispatcher, which can check readable and writable with interface IDispatcher, and user can adapt event with Channel.read instead of IO.read.

Actions

Also available in: Atom PDF

Like0
Like0Like0