GAミント至上主義

Web monomaniacal developer@株式会社ビズオーシャン

Pythonのos.chdir()はスレッドセーフではないので注意

Python3+Django内でZIPファイルを生成する処理で、複数ユーザーが同時に実行すると、一人が成功し、他が失敗するよくわからない状態になり、更にまずいことに成功したユーザーのZIPには他のユーザーのファイルが入ってしまっていた。

処理は下記のようになっていた。
最初はtempfile.TemporaryDirectory()が原因かと思ったけど、そんな雑な作りをしているわけはなく、原因はos.chdirだった。


一部抜粋(動かない)

with TemporaryDirectory() as temp_dir:
    os.chdir(temp_dir)
    subprocess.run(['zip', '-r', file_name, "./"],
                   stdout=subprocess.PIPE,
                   stderr=subprocess.PIPE,
                   check=True)
    temp_zip = File(open(zip_name, "rb"))
    temp_zip.seek(0)
    return {'name': zip_name,
            'content': temp_zip.read()}

os.chdirで変更するカレントディレクトリはスレッドで共有しているため、同時に処理すると順不同だが下記のように処理されてしまっていたと推測

os.chdir('/tmp/aaaa') #一人目
os.chdir('/tmp/bbbb') #二人目 ここで上書きされる
subprocess.run(['zip', '-r', file_name, "./"],  #一人目 ./は'/tmp/bbbb'
subprocess.run(['zip', '-r', file_name, "./"],  #二人目 ./は同じく'/tmp/bbbb'に もうファイルがなくなっておりエラー

zipコマンドの対象ディレクトリに./と、カレントディレクトリを使ってしまったのが諸悪の根源だと思われたので、全部temp_dir込のフルパスを使うように修正すればいいと思った。
が、できたzipファイルにtmpディレクトリが入ってしまうので素直に標準ライブラリのzipfileを使う方向に考え中。

subprocess.run(['zip', '-r', file_name, temp_dir],

そもそもなぜわざわざpython標準ライブラリのzipfileを使わずに、subprocess.runでOSのコマンドを叩いているのかと思ったけど、そういえばzipfileはパスワード付きのzipの作成ができなかったから。展開はもちろんできる。

スレッドとか、並列実行はテストが難しいなと実感。
あとなるべくos系は使わないことと、どうしても使うときはスレッドセーフかどうかを確認が必要だと思った。

13.5. zipfile — ZIP アーカイブの処理 — Python 3.6.5 ドキュメント


独学プログラマー Python言語の基本から仕事のやり方まで

独学プログラマー Python言語の基本から仕事のやり方まで