TensorFlowのAPIで、MakeDirsという再帰的にディレクトリを作成するものがあるのですが、そのメソッドがバグっていたときの話。
すでに修正済みなので現在のリポジトリのコードでこの現象は発生しませんが、ある意味レアなのでどういうことになったか残しておきます。
MNIST for ML Beginnersが終わらない
発端はTensorFlowのソースコードからのインストールとセットアップをして、確認用にMNIST for ML Beginnersを実行したところ、終わらない…
そのうちにメモリを食いつぶして落ちるということになりました。
最初は自分のコンパイルの仕方や、実行環境(GPGPUを使おうとしていたので、そのドライバなど)を疑ったのですが…地道に追いかけていくと、ディレクトリを再帰的に作ろうとしているTensorFlowのMakeDirsが、「相対パスのディレクトリパスを指定したときに」無限ループしておった。
そしてMNIST for ML Beginnersではデータダウンロードディレクトリを相対パスで作成していたのでここで現象発生となっていたよう。
対処法
コードを見て、対処法としては二つ
あらかじめディレクトリを作っておく
ディレクトリがあれば、作成ルーチンは通らないのでこのバグを踏みません。
カレントディレクトリにMNIST_dataというディレクトリを作ってやればOK。
ソースコードを修正する
せっかくだから修正してみたログを残しておく。
まず、適当なpyファイルで、
from tensorflow.python.platform import gfile gfile.MakeDirs("test")
だけのコードでも再現
それでそのgfile.pyを見てみると、
from tensorflow.python.lib.io import file_io
となっていて、file_ioに本体があるよう。
じゃあということで /usr/local/lib/python2.7/dist-packages/tensorflow/python/lib/io/file_io.py を開けると、
from tensorflow.python import pywrap_tensorflow def recursive_create_dir(dirname): """Create a directory and all parent/intermediate directories. Args: dirname: string, name of the directory to be created Raises: errors.OpError: If the operation fails. """ with errors.raise_exception_on_not_ok_status() as status: pywrap_tensorflow.RecursivelyCreateDir(compat.as_bytes(dirname), status) /usr/local/lib/python2.7/dist-packages/tensorflow/python/pywrap_tensorflow.py RecursivelyCreateDir = _pywrap_tensorflow.RecursivelyCreateDir
となっていて、_pywrap_tensorflow.soのメソッドを呼んでいるよう
今度はgithubからダウンロードしてきたソースコードの出番ですね。
bazel-bin/tensorflow/python/pywrap_tensorflow.cc
のところに、RecursivelyCreateDirのコードがありそうなので開いてみると、そこも別のクラスのほうを呼んでいる…
void RecursivelyCreateDir(const string& dirname, TF_Status* out_status) { tensorflow::Status status = tensorflow::Env::Default()->RecursivelyCreateDir( dirname); if (!status.ok()) { Set_TF_Status_from_Status(out_status, status); } }
また探す旅に。core/platform/env.ccにこれがあった。
Status Env::RecursivelyCreateDir(const string& dirname) { FileSystem* fs; TF_RETURN_IF_ERROR(GetFileSystemForFile(dirname, &fs)); std::vector<StringPiece> sub_dirs; StringPiece remaining_dir(dirname); while (!fs->FileExists(remaining_dir.ToString())) { // Basename returns "" for / ending dirs. if (!remaining_dir.ends_with("/")) { sub_dirs.push_back(io::Basename(remaining_dir)); } remaining_dir = io::Dirname(remaining_dir); } // sub_dirs contains all the dirs to be created but in reverse order. std::reverse(sub_dirs.begin(), sub_dirs.end()); // Now create the directories. string built_path = remaining_dir.ToString(); for (const StringPiece sub_dir : sub_dirs) { built_path = io::JoinPath(built_path, sub_dir); TF_RETURN_IF_ERROR(fs->CreateDir(built_path)); } return Status::OK(); }
そしてこのwhileの判定が曲者っぽく、while (!fs->FileExists(remaining_dir.ToString())) { でremaining_dirが空白でも終わらない→空白をパースしても空白になる、という無限ループっぽい。
/tmp/test、./test のように絶対パスで指定すると成功するし、ここにprintをはさんでremaining_dirを出力してやると、空文字列でループする。
でも、こんな最初のチュートリアルでバグるようなの、ずっと前から入ってるはずないよなあ…って思っていたら、
commit 915b02917db21de5a0ae304a067aedb0b5dd759d
Author: Rohan Jain <rohanj@google.com>
Date: Fri Sep 9 11:43:44 2016 -0800Adding a RecursivelyCreateDir function to the base environment. Removing functionality from the python library.
Change: 132704774commit 79a7e7a27d018573921e0aa4318088837bfeb95c
Author: A. Unique TensorFlower <gardener@tensorflow.org>
Date: Mon Aug 8 14:48:46 2016 -0800Fix prototype mismatch of ByteCount in env.cc
Change: 129683111
gitのログを見ると、915b02917db21de5a0ae304a067aedb0b5dd759dのコミットで追加されたよう。
git diff 56ad910f5957d3aa2d96eb840e6da36ac4105236 915b02917db21de5a0ae304a067aedb0b5dd759d file_io.py
diff --git a/tensorflow/python/lib/io/file_io.py b/tensorflow/python/lib/io/file_io.py
index 7a19ae2..fc107d5 100644
--- a/tensorflow/python/lib/io/file_io.py
+++ b/tensorflow/python/lib/io/file_io.py
@@ -257,11 +257,7 @@ def recursive_create_dir(dirname):
errors.OpError: If the operation fails.
"""
with errors.raise_exception_on_not_ok_status() as status:
- dirs = compat.as_str_any(dirname).split("/")
- for i in range(len(dirs)):
- partial_dir = "/".join(dirs[0:i + 1])
- if partial_dir and not file_exists(partial_dir):
- pywrap_tensorflow.CreateDir(compat.as_bytes(partial_dir), status)
+ pywrap_tensorflow.RecursivelyCreateDir(compat.as_bytes(dirname), status)def copy(oldpath, newpath, overwrite=False):
って感じで、file_io.pyに修正が入ってる。
それはそれとして、そもそもremaining_dirが空白になったら終了すればいいんで、
あそこのwhile文を
while (!remaining_dir.ToString().empty() && !fs->FileExists(remaining_dir.ToString())) {
としてやったら大丈夫でした。やったね。
そしてこの修正を終えてpullしてみたらばおんなじ修正がコミットされてた時の顔。
ソースコードはちょっと前にリポジトリから落としてきていたので、それが入っていなかったんですよね。ほんと。ちょっと前に。
教訓
この話の教訓としてはとりあえずpullしよう。
いや結構頻繁に更新されているようなので、なんかおかしいなと思っても思わなくても、ソースコードからのコンパイル組はこまめに取得しておいたほうがいいかもしれません。