LoginSignup
57
27

More than 5 years have passed since last update.

RubyでTempfileを使う際の注意

Posted at

一時ファイルを生成するために

tempfile = Tempfile.open(...) do |f|
  ...
  ...
  f.write
end

(...tempfileを使った処理...)

といったコードがありました。

このコード、だいたいの場合問題なく動くのですが、たまに作成したはずの一時ファイルがないと怒られることがありました。

実は Tempfile オブジェクトは自身への参照がなくなり、ガベージコレクションされた際に一時ファイルを削除する仕組みがあります。実はさきほどのコードでは Tempfile のオブジェクトへの参照が Tempfile#open のブロックを抜けるタイミングでなくなってしまっていたのです。

変数 tempfile があるじゃないか、と思うかもしれませんが実はこれ Tempfile のオブジェクトではないのです。

ドキュメントを見ると、

Tempfile オブジェクトはFileクラスへのDelegatorとして定義されており、Fileクラスのオブジェクトと同じように使うことができます。

とありますが、 File クラスで定義されているメソッドを使うと Tempfile ではなく File のオブジェクトが返ってくるようです。( #close #flush binmode などなど) Tempfile#open にブロックを渡した場合の戻り値はブロックの戻り値であるため、 tempfile には File オブジェクトがはいってしまいます。

このため、GCが走ると作成された一時ファイルは削除される状態になっているのです。
当然Fileオブジェクトを通じてその一時ファイルにアクセスしようとしても、「そんなファイルはないよ」と怒られてしまうのです。

なので、さきほどのコードは次のように直す必要があります。

tempfile = Tempfile.open(...) do |f|
  ...
  ...
  f.write
  f // <- Tempfileオブジェクトを返す
end

(...tempfileを使った処理...)

ちなみに、 Tempfile には create というメソッドがあります。こちらはブロックを渡すとブロックの中で一時ファイルを使用した処理を行い、ブロックを抜けたらすぐ一時ファイルをクリアするというものです。

Tempfile.create(...) do |tempfile|
  (tempfileを使った処理)
end

こちらを使う設計になっていたほうが、いつ起きるかわからないGCに依存せず、一時ファイルの生存期間を厳密に管理できます。新規に設計を行う場合は、こちらを積極的に使うべきでしょう。

参考

Ruby の Tempfile で発生した競合状態の原因を探る

57
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
27