RubyのTempfileオブジェクトがGCによって消えるのを観察する

たまーにTempfileを使って作ったファイルが消えるのでなんでだろなーと調べていたのだけど、どうやらGCによって消えてしまう可能性があるようなコードになっていたことがわかった(参考:RubyでTempfileを使う際の注意)。原因はおそらくこれだろう、ということで、コードの直し方もいろいろ思いつくわけだが、本当にそれが原因なのかな?とか、修正したコードで発生しなくなるのを確認するにはどうすればいいかな?と思い、社内メンバーの力を借りて実際にTempfileが消えるのを観察した。

さて、GCによって消えるのを観察するためにはGCが発生しなければならない。GCを開始する方法として、RubyではGC.startを使うことができる。コードとしてはこのようなものを使う。

変数fooにはTempfile.openで作成した一時ファイルのパス名(文字列)が代入される。つまり、Tempfileオブジェクトはどこからも参照されておらず、GC時に削除される対象となりえる。また、GC.startの前後で、Tempfileオブジェクトの一覧を表示し、GCされたかどうかが見えるようにした。このコードを実行すると、2回目のTempfileオブジェクト一覧表示では空配列が出力され、File.read(foo)の実行時にNo such file or directoryエラーとなるはずである。さっそく実行してみよう。

あれ?GCしたはずなのに、GCされずに読み込めている…これはどういうことだろうか。いろいろ試した結果、Rubyのバージョンによって挙動が違うようだということがわかった。上記の結果となった時、Rubyのバージョンは2.5.1を使っていたのだが、古いバージョンのRuby(v2.3系?)だとGCされているようだ。

2.3.7を使った場合、GCされてNo such file or directoryエラーとなる。GC.startの後ではTempfileオブジェクト一覧が空配列となっていることからもわかる。

なお、2.4.4ではGCされない模様。

また、Tempfileオブジェクトが参照されている状態となるようにコードを修正すれば、2.3.7でもちゃんとGCされないことも確認しておいた。

2.3.7で実行してもGCされず、読み込める。

たまに発生するというやっかいな問題だが、意図的にGCを実行することで再現させられることがわかった。また、修正して発生しなくなることも確認できて便利。