あれ? Tempfile に文字列が書き込まれない。。。
ある日、 Tempfile
を作成して、そのファイルに文字列を書き込み、完成した Tempfile
を元に処理を実行するという機能が正常に動作しなくなりました。
調査すると、書き込む文字列によって Tempfile
に書き込まれたり、書き込まれなかったりすることがわかりました。
原因がすぐに特定できず、解決までちょっと時間がかかってしまったので、この現象の原因と解決方法についてまとめようと思います。
これは何の記事か?
Ruby
で Tempfile
に文字列が正常に書き込まれないケースの原因と解決方法についてまとめた記事です。
対象読者
-
IO#write
したのに、文字列が書き込まれない。。。と困っている人
この記事のゴール
-
Tempfile
に文字列が書き込まれないときの原因がわかる - 書き込まれないときの対応方法を知る
不具合の発覚から対応までの流れ
不具合のあった機能について
私が所属する会社では、スプレッドシートで管理している情報をインポートして、一度処理に適した形(CSV形式)に整形した Tempfile
を作成し、そのファイルを元にして処理を実行するという機能があります。ある日、この機能が正常に使えないとの報告がありました。
ファイルによって機能が使えないときがある
機能利用者(スタッフ)にヒアリングすると、インポートするスプレッドシートによって、機能が正常に動作するケースとしないケースがあることがわかりました。
スプレッドシートの特徴
- 機能するスプレッドシート: 今まで使用してきたもの。1000行以上。
- 機能しないスプレッドシート: 新しく作成されたもの。10行程度。
Tempfile に何も書き込まれない。。。
ローカル環境で実行してみたところ再現したので、 byebug
を仕込んで調査を行いました。すると、報告のあったスプレッドシートの情報をインポートして Tempfile
に整形した情報を書き込んでも( IO#write
)、ファイルには何も書き込まれないことがわかりました。
tempfile = Tempfile.open(["spreadsheet", ".csv"])
tempfile.write(spreadsheet.export_as_string.force_encoding("UTF-8"))
# 2行目で Tempfile#write を実行しているのにファイルには書き込まれないケースがある
※ 1000行あるスプレッドシートは、正常に書き込まれます。
文字数が少ないと書き込まれないことを発見
そこで rails c
を実行し、コンソール上で試行錯誤していると、書き込む文字列の数によって、書き込まれるときと書き込まれないときがあることに気づきました。
> tempfile1 = Tempfile.open("test1.txt")
=> #<Tempfile:/file_path>
> tempfile1.write("test") # 書き込まれない
> tempfile2 = Tempfile.open("test2.txt")
=> #<Tempfile:/file_path>
> tempfile2.write("TestTest....") # 文字数を1万にして試してみると、書き込まれた。
なぜか文字列が多いときにだけ、ファイルに書き込まれることがわかりました。
バッファが関係しているかも
チームリーダに報告したところ、 「 File
に書き込むときに、文字列が一度バッファメモリに書かれ、そのバッファが満杯になっていないので書き込まれないのかもしれない」というヒントをいただきました。
調べると確かにそういった記述を見つけることができました。
PrintWriterなどで物を書き出すときには、内容が一旦バッファと呼ばれるメモリに書かれます。(論理出力動作ともいう) バッファが満杯になると自動的に蓄えられた内容はHDDなどの物理的装置に書き出されます。
参考: https://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=1445&forum=12
フラッシュすればバッファが満杯でなくても書き込める
上記の参考ページを読み進めると、以下のような記述がされていました。
このとき、バッファが満杯になっていなくてもバッファの内容を物理的記憶装置に書き込むよう指示したい場合にはバッファの内容を「フラッシュ」するわけです。
そこで Ruby
にも File
に書き込むときにフラッシュする機能がないかを調べると、公式ドキュメントで見つけることができました。
公式ドキュメント: https://docs.ruby-lang.org/ja/latest/method/IO/i/flush.html
どうやら IO#flush
を実行すれば解決するようでした。
IO#flush の実行で無事解決
IO#write
の実行後に IO#flush
を実行することで、情報の少ないスプレッドシートをインポートしても、 Tempfile
に書き込むことができました。もちろん、機能も正常に動作しました。
tempfile = Tempfile.open(["spreadsheet", ".csv"])
tempfile.write(spreadsheet.export_as_string.force_encoding("UTF-8"))
tempfile.flush
# IO#write の実行でバッファに溜まった文字列を IO#flush でファイルに書き込めた
まとめ
-
Tempfile
に文字列が書き込めない原因のひとつに、文字数が少なくて(データが小さくて)バッファメモリが満杯にならないケースがある。 - バッファメモリが満杯にならないときは
IO#flush
を実行することで、文字列をファイルに書き込むことができる。
所感
この部分は、プログラミングだけを学んでいても、身につかない部分だったのかなと思いました。コードを書いている時にバッファメモリのことを気にしたことは今まで一度もなかったからです。
「やはり、プログラミングだけではなくて、コンピュータの基礎を学ぶことも大事だな」と改めて実感した不具合対応になりました。