95091


Linux の bash シェルで、複数プロセスで同時に実行されるシェルスクリプトから1つのファイルにログとして1行ずつ追記します。

順序は厳密である必要はなく、出力が欠けたり echo 文がエラーにならなければ良いです。

今は以下のようにログファイル自身で排他ロックを獲得して追記を実行することで、期待どおり動作します。

`flock -x logfile -c "echo hello >> logfile"`

ここで疑問なのは、このような場合でも flock は必要でしょうか？例えば

`echo hello >> logfile`

だけの場合、同時に複数のプロセスで実行されても、エラーになったり、出力が欠けるなど発生する可能性はありますでしょうか。


CC BY-SA 4.0
2023-06-03T11:25:16.747
95090
2023-06-03T14:27:41.457
54949
question
linux bash
同じファイルに対するリダイレクション追記は排他が必要ですか？


## 回答

排他処理は不要だと思います。  
Linuxでのechoなど標準出力への書き込みはシステムコールwriteを使っていますので、他のプロセスからの出力が重なっても結果が失われることはありません。

bashは標準出力をファイルにリダイレクトするとき、アペンドモードでファイルをopenしているのでファイルの末尾に追加されます。出力が混じることもないと思います。

* * *

以下調査結果です。

## スクリプト(q.sh)

[code]

    #!/bin/bash
    echo AAAAAAAAAAAAAAAAAAAAAAA >> q.txt
    
[/code]

## straceの実行

[code]

    strace bash ./q.sh
    
[/code]

## 実行結果抜粋

[code]

    openat(AT_FDCWD, "q.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
    fcntl(1, F_GETFD)                       = 0
    fcntl(1, F_DUPFD, 10)                   = 10
    fcntl(1, F_GETFD)                       = 0
    fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
    dup2(3, 1)                              = 1
    close(3)                                = 0
    newfstatat(1, "", {st_mode=S_IFREG|0664, st_size=5, ...}, AT_EMPTY_PATH) = 0
    write(1, "AAAAAAAAAAAAAAAAAAAAAAA\n", 24) = 24
    dup2(10, 1)                             = 1
    fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
    close(10)                               = 0
    
    
[/code]


CC BY-SA 4.0
2023-06-03T11:51:45.773
95091
2023-06-03T11:57:46.833
2023-06-03T11:57:46.833
35558
35558
95090
answer


以下の記事が参考になるかと思います。

[Appending to a File from Multiple
Processes](https://nullprogram.com/blog/2016/08/03/)

> Appending
>
> The first concern is solved by the operating system, with one caveat. On
> POSIX systems, opening a file with the `O_APPEND` flag will guarantee that
> [writes always safely
> append](https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html).
>

>> If the `O_APPEND` flag of the file status flags is set, the file offset
shall be set to the end of the file prior to each write and no intervening
file modification operation shall occur between changing the file offset and
the write operation.

>
> However, this says nothing about interleaving. **Two processes successfully
> appending to the same file will result in all their bytes in the file in
> order, but not necessarily contiguously**.

ただし、NFS や HDFS では注意が必要とのことです。

> The caveat is that not all filesystems are POSIX-compatible. Two famous
> examples are NFS and the Hadoop Distributed File System (HDFS). On these
> networked filesystems, appends are simulated and **subject to race
> conditions**.


CC BY-SA 4.0
2023-06-03T12:10:04.507
95092
2023-06-03T12:10:04.507
47127
95090
answer


出力が欠けることはありませんが、出力元のプログラムの動作によっては、行の途中でログが混じる可能性があります。ただし、条件やタイミングがかなりシビアなため、発生は極めて希だと考えられます。

* * *

以下は割り込みを確認するためのテスト用のプログラムです。(Rubyで書いていますが、ログファイルに書き込むところの`system`メソッドはシェルのリダイレクトが使用されます)

slow_print.rb

[code]

    str = ('0'..'9').to_a.join + ('A'..'Z').to_a.join + ('a'..'z').to_a.join
    str.each_char do |c|
      print c
      $stdout.flush
      sleep 0.1
    end
    puts
    
[/code]

multi_rediret.rb

[code]

    10.times.map do
      fork do
        system('ruby slow_print.rb >> test.log')
      end
    end.each do |pid|
      Process.waitpid(pid)
    end
    
[/code]

上記二つのファイルを置き、Rubyがインストールされた環境で、`ruby
multi_redirect.rb`と実行するとtest.logが書き込まれます。slow_print.rbのみ実行した場合は

[code]

    0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
    
[/code]

の一行になります。`>>`によるリダイレクトションが排他処理を行うのであれば、上の行が10個書き込まれるはずですが、実際のtest.logは

[code]

    000000000011111111112222...
    
[/code]

というように、それぞれの間に挟み込まれた物になります。つまり、排他処理はされず、それぞれのプログラムは、それぞれ勝手なタイミングに書き込みを実行し、行が混じる事があります。

* * *

上のプログラムは一文字ずつ打つのに0.1秒待っており、その度フラッシュまでしています。これはわざと割り込みさせるためにしており、実際にはあまりないプログラムです(プログレスバーを表示する場合などでは使われていますが)。実際のプログラムは一行単位でフラッシュすることが多いです。そのためあ、一行を一回のシステムコールで書き込もうとします。その場合は、その処理の途中に割り込んで別のプロセスの書き込むことはありません。しかし、一回のシステムコールではバッファサイズ(Linuxの場合の標準は4096)までしか書き込めないため、書き込み量が多い場合は、一回で書き込まれず、他のプロセスにわりこまれる可能性はあります。(一行一行が短くても、複数行をまとめてフラッシュすると行の途中でシステムコールがわかれるといこともあり得ます。)

ということで、実際の所、行の途中で割り込まれるようなログになることは極めて希だと考えられます。また、ログは、基本的には、問題があったときの調査に使用するぐらいですが、ログの行が混じっていた場合でも、前後から本来の行を取り出すことは可能だと考えられます。しかし、ログ内容を分析集計する場合は、そのようなログが邪魔になってしまうことも確かです。そういった分析が前提であれば、別の方法(ログ出力は一つのプロセスだけがするようにするとか、syslog経由にするとか)を考える必要があります。

* * *

行の途中でログが混ざる例を見たことが無い人がほとんどかと思いますが、私は、一つだけそのようなログを吐き出す商用ソフトウェアを知っています。ただ、それはWebアプリケーションで、CGI(クローズドソースのバイナリ)で動くものでした。そのWebアプリケーションには認証などの機能があり、認証ログや動作ログ等が独自のログファイルに書き込むようになっていました。アクセス元の統計や分析のために認証ログを解析しようとしたときに、異常な行が希に現れる事があって、詳しく見ると行の途中でログが混じっているという物でした。ただ、発生は非常に希であり、統計的な分析には影響はほぼ無く、また、インシデント発生時の調査にも支障を来さなかったため、サポートへの不具合報告はしませんでした。

多数の利用者がいるWebアプリケーションのCGIであるため、同時実行はかなりの頻度で発生します。ソースコードが公開されていない商用ソフトウェアと言うことで詳細は不明ではありますが、行の途中で別のプロセスが割り込んだ結果だと考えられます。


CC BY-SA 4.0
2023-06-03T13:17:46.677
95093
2023-06-03T14:27:41.457
2023-06-03T14:27:41.457
7347
7347
95090
answer