How to check if a Ruby destructor will be called?

I created a class that I want to bind to a file descriptor, and close it when the GC-ed instance.

I created a class that looks something like this:

class DataWriter
  def initialize(file)
    # open file
    @file = File.open(file, 'wb')
    # create destructor
    ObjectSpace.define_finalizer(self, self.class.finalize(@file))
  end

  # write
  def write(line)
    @file.puts(line)
    @file.flush
  end

  # close file descriptor, note, important that it is a class method
  def self.finalize(file)
    proc { file.close; p "file closed"; p file.inspect}
  end
end

Then I tried to test the destructor method as follows:

RSpec.describe DataWriter do
  context 'it should call its destructor' do
    it 'calls the destructor' do
      data_writer = DataWriter.new('/tmp/example.txt')
      expect(DataWriter).to receive(:finalize)
      data_writer = nil
      GC.start
    end
  end
end

When performing this test, even though the "file is closed" is printed with the file. inspect, the test fails with the following output:

1) DataWriter it should call its destructor calls the destructor
     Failure/Error: expect(DataWriter).to receive(:finalize)

       (DataWriter (class)).finalize(*(any args))
           expected: 1 time with any arguments
           received: 0 times with any arguments
     # ./spec/utils/data_writer_spec.rb:23:in `block (3 levels) in <top (required)>'
+4
source share
4 answers

finalize initialize, proc , , . proc, , . , proc . :

class DataWriter
  # initialize and write same as above

  def self.finalize(file)
    proc { actually_finalize file }
  end

  def self.actually_finalize(file)
    file.close
  end

end

RSpec.describe DataWriter do
  context 'it should call its destructor' do
    it 'calls the destructor' do
      data_writer = DataWriter.new('/tmp/example.txt')
      expect(DataWriter).to receive(:actually_finalize)
      data_writer = nil
      GC.start
    end
  end
end
+4

, " " . inspect,

. , , rspec , :

Failures:
F

  1) DataWriter it should call its destructor calls the destructor
     Failure/Error: expect(DataWriter).to receive(:finalize)

       (DataWriter (class)).finalize(*(any args))
           expected: 1 time with any arguments
           received: 0 times with any arguments
     # /scratch/data_writer.rb:27:in `block (3 levels) in <top (required)>'

Finished in 0.01066 seconds (files took 0.16847 seconds to load)
1 example, 1 failure

Failed examples:

rspec /scratch/data_writer.rb:25 # DataWriter it should call its destructor calls the destructor

"file closed"
"#<File:/tmp/example.txt (closed)>"

, . , , , . , :

 it 'calls the destructor' do
   expect(DataWriter).to receive(:finalize).and_call_original
   data_writer = DataWriter.new('/tmp/example.txt')
   data_writer = nil
   GC.start
 end
+1

, , , GC . . , , , . , Ruby GC. 1.8 , 1.9+, Rubinius JRuby .

, , , , , .

API- Ruby:

File.open('thing.txt', 'wb') do |file| # file is passed to block
                                       # do something with file
end                                    # file will be closed when block ends

( )

(1..100_000).each do |i|
  File.open(filename, 'ab') do |file|
    file.puts "line: #{i}"
  end
end

:

File.open(filename, 'wb') do |file|
  (1..100_000).each do |i|
    file.puts "line: #{i}"
  end
end
+1

, , .

RSpec.describe DataWriter do
  context 'it should call its destructor' do
    it 'calls the destructor' do

      # creating pipe for IPC to get result from child process
      # after it garbaged
      # http://ruby-doc.org/core-2.0.0/IO.html#method-c-pipe
      rd, wr = IO.pipe

      # forking 
      # https://ruby-doc.org/core-2.1.2/Process.html#method-c-fork
      if fork      
        wr.close
        called = rd.read
        Process.wait
        expect(called).to eq('/tmp/example.txt')
        rd.close
      else
        rd.close
      # overriding DataWriter.actually_finalize(file)
        DataWriter.singleton_class.class_eval do
          define_method(:actually_finalize) do |arg|
            wr.write arg
            wr.close
          end
        end

        data_writer = DataWriter.new('/tmp/example.txt')
        data_writer = nil
        GC.start
      end
    end
  end
end

, , GC.start . , (ruby 2.2.4p230 @Ubuntu x86_64) .

, , (IPC).

rspec expect(DataWriter).to receive(:actually_finalize).with('/tmp/example.txt') - , , , Rspec, .

, !

0

Source: https://habr.com/ru/post/1652934/


All Articles