アカウント名:
パスワード:
・C言語の高水準入出力 printf(3) とか fputs(3) で出力した内容はキャッシュされるので fclose(3) とか fflush(3) とかで吐き出して戻り値を確認しないと正常に出力されたことは保証されない。・exit(3) なり main() からの return で暗黙にフラッシュした場合はエラーチェックできないので、失敗してもわからない。
というのは基本仕様なので知っとく必要がある。/dev/full とかだけでなく、write(2)で発生するあらゆるエラーの可能性がある。
C言語の仕様と離れるけど補足しておくと、書き込み先が NFS のような場合にはカーネルが遅延書き込みをするので fflush(3) が成功したとしてもまだ十分ではない。あとから遅延してエラーが帰ってくるかもしれないので fclose(3) の戻り値を確認するまでは安心できない。
別件としてカーネル内にもページキャッシュがあるので、ディスクドライブへ書き込みされたことを保証したいなら fsync() なり sync() なりを検討する必要もある。
> 別件としてカーネル内にもページキャッシュがあるので、ディスクドライブへ書き込みされたことを保証したいなら fsync() なり sync() なりを検討する必要もある。
つまり書き込み保証を移植性のある方法で行うことは不可能
どっかで「syncは最低でも3回やれ」って記述を見た気がする。
1回目のsyncシステムコールは、syncシステムコールは非同期処理なため、バッファのフラッシュを要求するが、書き出し完了前に返ってくる。
2回目のsyncシステムコールは、1回目の処理が終わるまでブロックされるので、返ってきた時には、1回目の要求によるバッファのフラッシュができたことが保証できる
3回目は何の意味も無いおまじない
syncなのにasyncなのかややこしい
3回目は前のどちらかでtypoしたときのために
それは大昔の sync() の実行が非同期だった頃の風習。(BSD 系だと今でも現役?)Linux だと kernel 1.3.21 (1995年8月)以降は同期で終了を待つので1回で十分。
単発の発行は非同期だがシリアライズするためのロックは取る、という挙動に由来していたはず…
ちなみに、coreutilsのechoは正しくハンドルしていたのでその実装を確認してみると、atexit()で登録した関数close_stdout()とそこから呼ばれるclose_stream()にて、fclose()他のstdout周りで起こったエラーがないかをチェックしている。https://github.com/coreutils/gnulib/blob/master/lib/closeout.c#L117 [github.com]
それはCの話なのかUnixの話なのかオーエス全般の話なのか…ショリケイイゾンテクソダナ
バッファキャッシュの問題はC言語の実装の話。正確には libc にある C言語の標準関数の実装の話。
Cの規格では(1)バッファされる(2)バッファされない(3)ラインバッファ(テキストのみ)の3種類が挙げられていて、そのどれかは処理系に依存する。エラー処理的には(1)を想定したほうがいいので、元コメントは完全に正しい。
stderrはバッファされないと書いてあったかな。
これC言語にフォーカスしているからおかしい(のとC言語もバグ扱いにしているのがおかしい)。JavaとかNode.jsみたいに例外処理がある処理系でも発生しているのは確かに不思議。Javaは調べてみると、PrintStreamはIOExceptionを投げない [oracle.com]となっているから、そもそもそういう仕様なのね。# バグを仕様と言い張るのと逆で、仕様をバグと言い張っているのか
Javaでもmainメソッドの外で暗黙に閉じられたstreamが仮に例外を投げるとしてもcatchのしようがないのでは?
Javaも例外が拾われずに終了する場合は1(エラー)を返すので、問題はキャッシュしたstreamがflushされるタイミングですね。普通に考えて処理最後にはflushされるはずなので、仮にstreamが例外をthrowするならエラーで終わる気がします。C#はエラーになっているので、そのあたりは作りしだいですが。
c# (net6)は挙動とソースをみると、Console.Writen系では毎回呼び出し毎にflushしているので、呼んだ時点で例外が起きる。CはターミナルならLine Bufferingだが、リダイレクトされているとFull Bufferingになるのでfwriteした時点では気づかないとかの話になる。まあこれもパフォーマンスの話で気になるならsetlinebufなりsetvbuf(_IONBF)なり呼べば、というのもわかる話。API設計の時代背景(かWindows文化的にリダイレクトをバッファリングするとデメリットがでるのでやらない)だろうね。
>普通に考えて処理最後にはflushされるはずこれな、Streamクラスによっては仕様で担保されてなくて、まあほとんどはSun由来のJRE使うから問題ないんだけど、IBMの組み込み向けJ2MEでflushされないのをライブラリのソース読んで確認した。大昔の話。
PCみたいな富豪環境以外は終了処理をリソース食わずに素早く終わらせることを優先するのが普通だよ。Cまでいくと仕様上は振る舞いを説明するだけでなんの保証もなく、ただホストに投げると明記される程度だし。ioはホスト/プラットフォーム依存の塊だし。
本当の問題は富豪環境でもRAIIやファイナライザのコールバックがioのcloseの際のエラーを握りつぶすしかないことだよ。きれいにやりたいなら手動でやるしかない。
より多くのコメントがこの議論にあるかもしれませんが、JavaScriptが有効ではない環境を使用している場合、クラシックなコメントシステム(D1)に設定を変更する必要があります。
アレゲは一日にしてならず -- アレゲ研究家
/dev/full 関係ないじゃん。 (スコア:5, すばらしい洞察)
・C言語の高水準入出力 printf(3) とか fputs(3) で出力した内容はキャッシュされるので fclose(3) とか fflush(3) とかで吐き出して戻り値を確認しないと正常に出力されたことは保証されない。
・exit(3) なり main() からの return で暗黙にフラッシュした場合はエラーチェックできないので、失敗してもわからない。
というのは基本仕様なので知っとく必要がある。
/dev/full とかだけでなく、write(2)で発生するあらゆるエラーの可能性がある。
Re:/dev/full 関係ないじゃん。 (スコア:2, 興味深い)
C言語の仕様と離れるけど補足しておくと、書き込み先が NFS のような場合にはカーネルが遅延書き込みをするので fflush(3) が成功したとしてもまだ十分ではない。
あとから遅延してエラーが帰ってくるかもしれないので fclose(3) の戻り値を確認するまでは安心できない。
別件としてカーネル内にもページキャッシュがあるので、ディスクドライブへ書き込みされたことを保証したいなら fsync() なり sync() なりを検討する必要もある。
Re: (スコア:0)
> 別件としてカーネル内にもページキャッシュがあるので、ディスクドライブへ書き込みされたことを保証したいなら fsync() なり sync() なりを検討する必要もある。
つまり書き込み保証を移植性のある方法で行うことは不可能
Re: (スコア:0)
どっかで「syncは最低でも3回やれ」って記述を見た気がする。
Re:/dev/full 関係ないじゃん。 (スコア:2)
1回目のsyncシステムコールは、syncシステムコールは非同期処理なため、
バッファのフラッシュを要求するが、書き出し完了前に返ってくる。
2回目のsyncシステムコールは、1回目の処理が終わるまでブロックされるので、
返ってきた時には、1回目の要求によるバッファのフラッシュができたことが保証できる
3回目は何の意味も無いおまじない
Re:/dev/full 関係ないじゃん。 (スコア:1)
syncなのにasyncなのかややこしい
Re: (スコア:0)
3回目は前のどちらかでtypoしたときのために
Re:/dev/full 関係ないじゃん。 (スコア:1)
それは大昔の sync() の実行が非同期だった頃の風習。(BSD 系だと今でも現役?)
Linux だと kernel 1.3.21 (1995年8月)以降は同期で終了を待つので1回で十分。
Re: (スコア:0)
単発の発行は非同期だがシリアライズするためのロックは取る、という挙動に由来していたはず…
Re:/dev/full 関係ないじゃん。 (スコア:2, 興味深い)
ちなみに、coreutilsのechoは正しくハンドルしていたのでその実装を確認してみると、
atexit()で登録した関数close_stdout()とそこから呼ばれるclose_stream()にて、
fclose()他のstdout周りで起こったエラーがないかをチェックしている。
https://github.com/coreutils/gnulib/blob/master/lib/closeout.c#L117 [github.com]
Re: (スコア:0)
それはCの話なのかUnixの話なのかオーエス全般の話なのか…
ショリケイイゾンテクソダナ
Re:/dev/full 関係ないじゃん。 (スコア:1)
バッファキャッシュの問題はC言語の実装の話。正確には libc にある C言語の標準関数の実装の話。
Re: (スコア:0)
Cの規格では(1)バッファされる(2)バッファされない(3)ラインバッファ(テキストのみ)の3種類が挙げられていて、そのどれかは処理系に依存する。
エラー処理的には(1)を想定したほうがいいので、元コメントは完全に正しい。
stderrはバッファされないと書いてあったかな。
Re: (スコア:0)
これC言語にフォーカスしているからおかしい(のとC言語もバグ扱いにしているのがおかしい)。
JavaとかNode.jsみたいに例外処理がある処理系でも発生しているのは確かに不思議。
Javaは調べてみると、PrintStreamはIOExceptionを投げない [oracle.com]となっているから、そもそもそういう仕様なのね。
# バグを仕様と言い張るのと逆で、仕様をバグと言い張っているのか
Re: (スコア:0)
Javaでもmainメソッドの外で暗黙に閉じられたstreamが仮に例外を投げるとしてもcatchのしようがないのでは?
Re: (スコア:0)
Javaも例外が拾われずに終了する場合は1(エラー)を返すので、問題はキャッシュしたstreamがflushされるタイミングですね。
普通に考えて処理最後にはflushされるはずなので、仮にstreamが例外をthrowするならエラーで終わる気がします。
C#はエラーになっているので、そのあたりは作りしだいですが。
Re:/dev/full 関係ないじゃん。 (スコア:3)
c# (net6)は挙動とソースをみると、Console.Writen系では毎回呼び出し毎にflushしているので、呼んだ時点で例外が起きる。
CはターミナルならLine Bufferingだが、リダイレクトされているとFull Bufferingになるのでfwriteした時点では気づかないとかの話になる。まあこれもパフォーマンスの話で気になるならsetlinebufなりsetvbuf(_IONBF)なり呼べば、というのもわかる話。
API設計の時代背景(かWindows文化的にリダイレクトをバッファリングするとデメリットがでるのでやらない)だろうね。
Re: (スコア:0)
>普通に考えて処理最後にはflushされるはず
これな、Streamクラスによっては仕様で担保されてなくて、まあほとんどはSun由来のJRE使うから問題ないんだけど、IBMの組み込み向けJ2MEでflushされないのをライブラリのソース読んで確認した。
大昔の話。
Re: (スコア:0)
PCみたいな富豪環境以外は終了処理をリソース食わずに素早く終わらせることを優先するのが普通だよ。Cまでいくと仕様上は振る舞いを説明するだけでなんの保証もなく、ただホストに投げると明記される程度だし。ioはホスト/プラットフォーム依存の塊だし。
本当の問題は富豪環境でもRAIIやファイナライザのコールバックがioのcloseの際のエラーを握りつぶすしかないことだよ。きれいにやりたいなら手動でやるしかない。