我创建了一个类,我希望将其挂在文件描述符上,并在实例进行GC编辑时将其关闭。
我创建了一个看起来像这样的类:
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
然后我尝试像这样测试析构函数方法:
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
运行此测试时,即使"文件已关闭"与file.inspect一起打印,测试失败,输出如下:
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)>'
答案 0 :(得分:4)
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
答案 1 :(得分:1)
即使“file closed”与file.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)>"
关于它的原因,我现在无法分辨。 Dave is right你在已经发生的事情上断言,所以你的测试永远不会过去。您可以通过将测试更改为:
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
答案 2 :(得分:1)
恕我直言,你不应该依赖终结器在GC运行时完全运行。他们最终会跑。但也许只有在流程结束时。据我所知,这也取决于Ruby实现和GC实现。 1.8具有与1.9+不同的行为,Rubinius和JRuby也可能不同。
确保资源的释放可以通过一个块来实现,该块也会在不再需要的时候尽快释放资源。
多个API在Ruby中具有相同的样式:
(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
我这样做:
LASSSST = IP.Cells(Rows.Count, "B").End(xlUp).Rows.Count
答案 3 :(得分:0)
我在下面重写了我的工作解决方案,我没有运行此代码。
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)。
我没有得到像在expect(DataWriter).to receive(:actually_finalize).with('/tmp/example.txt')
这样的形式构建对析构函数方法调用的rspec期望的结果 - 我不知道为什么,但我想由Rspec创建的包装器之前已经被包含或侵犯了调用类的析构函数。
希望这有帮助!