Project

General

Profile

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. 

Back