Bug #18482
Updated by jakit (Jakit Liang) almost 3 years ago
class Fiber can not disable scheduler with it's parameter. When parameter is false: ``` require 'fiber' require 'io/nonblock' class SimpleScheduler def initialize @readable = {} @writable = {} @waiting = {} @ready = [] @blocking = 0 @urgent = IO.pipe end def run while @readable.any? or @writable.any? or @waiting.any? or @blocking.positive? or @ready.any? 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) 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) block(:sleep, duration) return true end def block(blocker, timeout = nil) 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) @ready << fiber io = @urgent.last io.write_nonblock('.') end def close run @urgent.each(&:close) @urgent = nil end private def current_time Process.clock_gettime(Process::CLOCK_MONOTONIC) end end scheduler = SimpleScheduler.new Fiber.set_scheduler(scheduler) puts "Go to sleep!" f = Fiber.new(false) do puts "Going to sleep" sleep(1) puts "I slept well" end f.resume puts "Wakey-wakey, sleepyhead" ``` Result: ``` Go to sleep! Going to sleep Wakey-wakey, sleepyhead I slept well ``` And when parameter is true: ``` require 'fiber' require 'io/nonblock' class SimpleScheduler def initialize @readable = {} @writable = {} @waiting = {} @ready = [] @blocking = 0 @urgent = IO.pipe end def run while @readable.any? or @writable.any? or @waiting.any? or @blocking.positive? or @ready.any? 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) 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) block(:sleep, duration) return true end def block(blocker, timeout = nil) 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) @ready << fiber io = @urgent.last io.write_nonblock('.') end def close run @urgent.each(&:close) @urgent = nil end private def current_time Process.clock_gettime(Process::CLOCK_MONOTONIC) end end scheduler = SimpleScheduler.new Fiber.set_scheduler(scheduler) puts "Go to sleep!" f = Fiber.new(true) do puts "Going to sleep" sleep(1) puts "I slept well" end f.resume puts "Wakey-wakey, sleepyhead" ``` Result (was still the same): ``` Go to sleep! Going to sleep Wakey-wakey, sleepyhead I slept well ``` While make the set_scheduler line commented: ``` scheduler = SimpleScheduler.new # Fiber.set_scheduler(scheduler) // Here is commented puts "Go to sleep!" f = Fiber.new(false) do puts "Going to sleep" sleep(1) puts "I slept well" end ``` Result is right: ``` Go to sleep! Going to sleep I slept well Wakey-wakey, sleepyhead ``` Maybe in some situation. I wrote my gem without Scheduler. But user defined its Scheduler for his or her logic code. It will break the sequence of Fiber which was needed for my gem. Also, using Fiber in the Enumerator situation will be broke down too: ``` db.with_each_row_of_result(sql_stmt) do |row| yield row end ``` [[https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html]] It will break the sequence of db rows when doing enum such like python's generator. ======================== Also, another question is that I saw something was talk in: https://bugs.ruby-lang.org/issues/16786 I think there would be a better way to improve this. You can see, in c++, std::thread is easy to create and join a new thread. If someone make a std::thread::scheduler into STL of C++. And let user to implement its std::thread::handler to implement the virtual methods (interface or callback) to use it. And std::thread::scheduler holds an independent thread pool which is not separated. What do you think about this std::thread::scheduler? What about make a golang's GMP into std::thread or std::coroutine. Why not STL do that? Why not STL let std::thread become a self-scheduled module? Otherwise, the sense of implement Scheduler as async await may be a good idea, but there has module named Ractor can solve it. Maybe: ``` IO.async do |readable, writeble| if readable # code end end ``` ====================== In other programming language, like Python. Python never let it's Generator mixed with async IO but add `async` syntax: ``` syntax to do not only async def coro(): # a coroutine function await smth() IO but also running. async def asyncgen(): # an asynchronous generator function await smth() yield 42 ``` Python goes in a right way. Methods can run async and something like IO.write() can put in it. ``` async def async_write(data): # a coroutine function IO.write(data) async def send(message): # an asynchronous generator function await async_write("hello " + message) yield 1 ``` https://www.python.org/dev/peps/pep-0525/#id10 Also, JavaScript use `async` syntax to identify the `async` procedure. Above that, I think the Fiber.scheduler may not be a good idea. Because Ractor is here. Ractor can run methods async and we can put IO.write in it and make it "async".