ブロック形式の File.open で IOError: Closed stream が発生する場合がある
概要
独自の Marshal.dump をディスクに保存できるオブジェクトがあります。縮小された形式で:
class MyObject
# while the rest of the class is removed for brevity, this is the actual
# save method
def save
File.open("/some/path", "wb") { |f| f << Marshal.dump(self) }
end
end
.save は、オブジェクト インスタンスの存続期間中、繰り返し呼び出すことができます。手動テストでは、これは正常に動作します。現在の状態がディスク上の以前の状態を上書きします。
ただし、運用環境では、上記の処理が IOError: Closed stream で失敗し、作成されたファイルの長さが 0 バイトになることがあります。
私が理解しているところによると、すでに閉じられているIOストリームにアクセスしようとすると、閉じられたストリームが発生します。これまでのところ、それは賢明です。
私が理解できないのは、上記の場合にどのようにしてこれが発生するのかということです。私が見たところ、#save を呼び出すたびに、私は
上記のシナリオのどこで、どのように IO ストリームが途中で閉じられる可能性がありますか?
解決策
MyObject インスタンスはインスタンス変数内の閉じたストリームを保持していますか?ドキュメントによると、Marshal は File などのシステムに関連するオブジェクトをダンプできません。したがって、MyObject が保持しているものに応じて、Marshal.dump がエラーをスローしている可能性があります。
class MyObject
def initialize(value = nil)
@some_attribute = value
end
def dump
Marshal.dump(self)
end
end
# Works OK with string value in attribute
obj = MyObject.new('abc')
=> #<MyObject:0x00007f961766ae18 @some_attribute="abc">
obj.dump
=> "\x04\bo:\rMyObject\x06:\x14@some_attributeI\"\babc\x06:\x06ET"
# Fails with open File
file = File.open('Test')
obj = MyObject.new(file)
=> #<MyObject:0x00007f9619ca34e0 @some_attribute=#<File:Test>>
obj.dump
# (irb):47:in `dump': can't dump File (TypeError)
# Fails with closed File
file.close
obj = MyObject.new(file)
=> #<MyObject:0x00007f9611d3d7c8 @some_attribute=#<File:Test (closed)>>
obj.dump
# (irb):47:in `internal_encoding': closed stream (IOError)
これを回避するには、オブジェクトをシリアル化する方法を明示的に記述する必要があるかもしれません。