ほとんど他人が読む価値がないかも知れない覚え書き的文章です。
昔、GDNJのメーリングリストに書いた件をもうちょっと詳しく調べてしまいました。
.NET Framework 1.1のFileStreamクラスや、最終的にそれを呼び出してファイルに書き込む場合、書き込み中にディスクの残り容量が尽きると、そのファイルをクローズできない問題。つまり、ファイルが開いたままになり、削除もできない問題です。
ちょっと古いかも知れないけど、SSCLI(sscli2002101)のソースでFileStreamクラスのソースを追いかけてみました。これと同じ実装が行われていると仮定すると、すっきりと現象の説明が付きます。
その結果分かったこと。
- デストラクタはDisposeを呼んでいる
- CloseはDisposeを呼んでいる
- DisposeはFlush()を呼び出した後で、内部的なハンドルをクローズしている
- Flushは書き込みのバッファに残ったデータを書き込もうとする
以上のことから、Closeを呼び出したとしても、ハンドルを閉じる前にバッファの中身を書き込もうとしてそこで例外が起きてメソッドが中断され、ハンドルはクローズされないことになります。Closeのかわりにデストラクタを使っても、Disposeを使っても結果は同じになります。
また、ハンドルを取り出して、それをWin32 APIのCloseHandleで強制クローズしようとしても上手く行きません。その理由は、以下の事実から容易に説明がつきます。
- HandleプロパティはFlush()を呼び出している
これらの挙動は、通常ケースでは妥当なものだと思います。しかし、ディスク残り容量ゼロという異常ケースでは、不適切な感があります。
2004年3月17日追記 §
この問題に関する話題がC#のメーリングリストで出ました。これを回避する方法として、[CS:06118] Re: ファイル書き込みで容量オーバー時の処理方法にて内藤雅章さんが1つの方法を提案しました。
メーリングリストのアーカイブにリンクしようと思いましたが、どうも古いメッセージは呼び出せるものの、最近のメッセージが呼び出せなかったので、要旨を以下にまとめます。
- Write() の前に SetLength() する。つまり、実データを書き込む前に、それを書き込むために必要なサイズまで、ファイルのサイズを拡張する
- ディスクが溢れる場合、SetLength() で例外が発生するが、その後Close()を正常に終了でき、ファイルが閉じる (バッファ内にファイルが残っていないので、それを書き込もうとする動作が起こらないと推測)
メーリングリストでは、このあと、マルチスレッドで実行する場合など、この方法で上手く行かないケースの話題が続いています。
2004年3月20日追記 §
渋木さんが、この問題を補完する作業を開始されました。
Win32File - System.IO.FileStream 補完計画
2004年3月29日追記 §
菊池和彦さんが、別の解決策を提案されました。
System.IO.FileStream がDiskFull にぶつかると Close 不能になる問題に対する邪悪な解答
リフレクションを使ってprivateなメンバを直接書き換えることで、バッファが空であると認識させます。例外ハンドラ内だけで対処できるのですっきりする反面、ドキュメント化されていないメンバに依存するため、将来のバージョンでは動作しない可能性があります。
2005年12月2日追記 §
ホットフィックスが提供されているようです。
FIX: .NET Framework 1.1 の十分な領域があるディスクのファイルにアクセスするために、 StreamWriter.Flush ()メソッドまたは StreamWriter.Close ()メソッドを使用すると、例外エラー メッセージを表示します。