AzureのAppService上でファイルを追記したいだけの人生だった。
using System.IO;
/**
* file_put_contents(FILE_APPEND)
* @param string ファイル名
* @param string 書き込み内容
*/
public static void file_put_contents(string FileName, string SaveText){
// 実際はフォーマットとか追加があるけど本質的な違いは無いので略
File.AppendAllText(FileName, SaveText);
}
という素晴らしく手抜きな関数を使っていたところ、リクエストが重なったときに競合であっさり死亡。
// わりと頻繁に発生
System.IO.IOException: The process cannot access the file 'hoge.txt' because it is being used by another process.
PHP脳はマルチスレッドに弱いのだ。
で、これをどうにかしたいと思ったのだが、キューに投げておけば後で勝手に書き込んでくれる、みたいな機能はどうやら用意されていないみたいだ。
ちなみにAppendAllTextのサンプルは、そもそもcatchすらしてないものが大半だった。
しかたないのでTextWriter.Synchronized()
を使うことにする。
public static void file_put_contents(string FileName, string SaveText){
using(StreamWriter writer = new StreamWriter(FileName, true)){
// まだ何も書いてない
}
}
// 希に発生
System.IO.IOException: The process cannot access the file 'hoge.txt' because it is being used by another process.
どうやらファイルをStreamWriterで開くだけで死ぬらしい。
Synchronizedとかする以前の問題だった。
ドキュメントには「ファイル名、ディレクトリ名が正しくないときに出る」としか書いてないのだが、どう見てもロック失敗である。
えっこれどうすんの?
newしただけでExceptionとか聞いてないよ。
WriteをcatchするとかSynchronizedするとかのソリューションはよく見かけるけど、実は解決になっていないということがわかった。
その後もなんか色々調べたりしたんだけどそのあたりは省略して、最終的にできあがったのがこんな。
public static void file_put_contents(string FileName, string SaveText){
using(FileStream fs = new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)){
using(StreamWriter sw = new StreamWriter(fs)){
using(TextWriter tw = TextWriter.Synchronized(sw)){
tw.Write(SaveText);
tw.Flush();
}
}
}
}
例外は発生せず、動作も安定しているように見えるのだが、本当にこれで合ってるのか???