I wrote a simple ruby fiber program to see how they work and got an unexpected result.
#! /usr/bin/env ruby
#encoding: utf-8
#frozen_string_literal: true
sg = Fiber.new do
File.open(begin print "Enter filename: "; gets.chomp end).each{|l| Fiber.yield l}
end
begin
loop do
puts sg.resume
end
rescue => err
puts "Error: #{err.message}"
end
datafile
This is the first
This is the second
This is the third
This is the fourth
This is the fifth
And the output from the above program
Enter filename: datafile
This is the first
This is the second
This is the third
This is the fourth
This is the fifth
#<File:0x0000557ce26ce3c8>
Error: attempt to resume a terminated fiber
I'm not sure why its displaying #File:0x0000557ce26ce3c8 in the output.
Note: ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux-gnu]
CodePudding user response:
From the docs,
Upon yielding or termination the
Fiber
returns the value of the last executed expression
In addition to Fiber.yield
calls, a fiber (like an ordinary function) returns the result of the final expression in the fiber.
The body of your fiber is this.
File.open(begin print "Enter filename: "; gets.chomp end).each{|l| Fiber.yield l}
Inside the .each
, you yield each line of the file, which gets printed out as you've already observed. But then, when the fiber is done, it yields a final value, which is the result of File.open
. And File.open
returns the File
object itself. So your sg.resume
actually sees six results, not five.
"This is the first"
"This is the second"
"This is the third"
"This is the fourth"
"This is the fifth"
(the file object itself)
This actually points out a small issue in your program to begin with: You never close the file. You can do that either with File#close
or by passing a block to File::open
. In order to be completely safe, your fiber code should probably look like this.
sg = Fiber.new do
print "Enter filename: "
filename = gets.chomp
# By passing File.open a block, the file is closed after the block automatically.
File.open(filename) do |f|
f.each{|l| Fiber.yield l}
end
# Our fiber has to return something at the end, so let's just return nil
nil
end
begin
loop do
# Get the value. If it's nil, then we're done; we can break out of the loop.
# Otherwise, print it
value = sg.resume
break if value.nil?
puts value
end
rescue => err
puts "Error: #{err.message}"
end
Now, in addition to dealing with that pesky file handle, we have a way to detect when the fiber is done and we no longer get the "attempt to resume a terminated fiber" error.