web-dev-qa-db-ja.com

flock():ロックされたファイルを競合状態なしで削除しますか?

ミューテックスという名前のプロセス間でflock()を使用しています(つまり、いくつかのプロセスは、「some_name」という名前のファイルを一時ディレクトリにロックすることで実装される「some_name」のロックを保持することを決定できます。

lockfile = "/tmp/some_name.lock";
fd = open(lockfile, O_CREAT);
flock(fd, LOCK_EX);

do_something();

unlink(lockfile);
flock(fd, LOCK_UN);

一時ディレクトリに何百ものファイルがいっぱいになるのを避けるために、ロックファイルはいつか削除する必要があります。

ただし、このコードには明らかな競合状態があります。プロセスA、B、Cの例:

A opens file
A locks file
B opens file
A unlinks file
A unlocks file
B locks file (B holds a lock on the deleted file)
C opens file (a new file one is created)
C locks file (two processes hold the same named mutex !)

この競合状態を導入せずに、ある時点でロックファイルを削除する方法はありますか?

24
Arnaud Le Blanc

死んだ質問に返信したら申し訳ありません。

ファイルをロックした後、ファイルの別のコピーを開き、fstatで両方のコピーを作成し、次のようにiノード番号を確認します。

lockfile = "/tmp/some_name.lock";

    while(1) {
        fd = open(lockfile, O_CREAT);
        flock(fd, LOCK_EX);

        fstat(fd, &st0);
        stat(lockfile, &st1);
        if(st0.st_ino == st1.st_ino) break;

        close(fd);
    }

    do_something();

    unlink(lockfile);
    flock(fd, LOCK_UN);

これにより、プログラムがまだファイルシステム上にあるファイルをロックしている場合、残りのファイルを持つ他のすべてのプログラムのiノード番号が間違っているため、競合状態が回避されます。

次のプロパティを使用して、状態マシンモデルで実際にそれを証明しました。

P_iの記述子がファイルシステムでロックされている場合、クリティカルセクションに他のプロセスはありません。

P_iがstatの後に正しいiノードがあるか、クリティカルセクションにある場合、P_iは記述子をファイルシステムにロックしています。

28
user2769258
  1. Unixでは、開いているファイルを削除できます。ファイル記述子リストにあるすべてのプロセスが終了するまで、iノードは保持されます。
  2. Unixでは、リンク数がゼロになったときにリンク数を確認することにより、ファイルがすべてのディレクトリから削除されたことを確認できます。

したがって、古い/新しいファイルパスのino値を比較する代わりに、すでに開いているファイルのnlinkカウントを確認できます。一時的なロックファイルであり、実際のミューテックスリソースやデバイスではないことを前提としています。

lockfile = "/tmp/some_name.lock";

for(int attempt; attempt < timeout; ++attempt) {
    int fd = open(lockfile, O_CREAT, 0444);
    int done = flock(fd, LOCK_EX | LOCK_NB);
    if (done != 0) { 
        close(fd);
        sleep(1);     // lock held by another proc
        continue;
    }
    struct stat st0;
    fstat(fd, &st0);
    if(st0.st_nlink == 0) {
       close(fd);     // lockfile deleted, create a new one
       continue;
    }
    do_something();
    unlink(lockfile); // nlink :=0 before releasing the lock
    flock(fd, LOCK_UN);
    close(fd);        // release the ino if no other proc 
    return true;
}
return false;
3

これらのファイルをロックのみに使用し、実際に書き込みを行わない場合は、ディレクトリエントリ自体の存在を保持されたロックの指標として扱い、 flock の使用を完全に避けることをお勧めします。

そのためには、ディレクトリエントリを作成し、すでに存在する場合はエラーを報告する操作を作成する必要があります。 Linuxおよびほとんどのファイルシステムで、_O_EXCL_を open に渡すと、これが機能します。ただし、一部のプラットフォームおよび一部のファイルシステム(特に古いNFS)はこれをサポートしていません。したがって、 openのマニュアルページ は代替案を示唆しています。

ロックファイルを使用してアトミックファイルロックを実行し、_O_EXCL_のNFSサポートへの依存を回避する必要があるポータブルプログラムは、同じファイルシステムに一意のファイルを作成し(ホスト名とPIDを組み込むなど)、_を使用できます。 link (2)ロックファイルへのリンクを作成します。 link (2)が0を返した場合、ロックは成功しています。それ以外の場合は、一意のファイルで stat (2)を使用して、リンク数が2に増加したかどうかを確認します。この場合、ロックも成功します。

したがって、これは公式に文書化されているロック方式のように見え、したがって、一定レベルのサポートとベストプラクティスの提案を示しています。しかし、私は他のアプローチも見ました。 bzr たとえば、ほとんどの場所でシンボリックリンクの代わりにディレクトリを使用します。 そのソースコード からの引用:

ロックは、情報ファイルを含む特定の名前のディレクトリによってディスク上で表されます。ロックを取得するには、一時ディレクトリの名前を変更します。既知のすべてのトランスポートとファイルシステムについて、ロックを要求する試みは1つだけ成功し、他の試みは失敗すると信じるため、一時ディレクトリを使用します。 (一部のファイルシステムまたはトランスポートには名前の変更と上書きしかありません。誰が勝ったかわかりにくいため、ファイルは機能しません。)

上記のアプローチの1つの欠点は、それらがブロックしないことです。ロックの試行に失敗するとエラーが発生しますが、ロックが使用可能になるまで待機しません。ロックをポーリングする必要がありますが、ロックの競合を考慮すると問題になる可能性があります。その場合、ファイルシステムベースのアプローチからさらに離れて、代わりにサードパーティの実装を使用することができます。しかし、ipcミューテックスの実行方法に関する一般的な質問はすでに行われているので、 _[ipc] [mutex]_ を検索して、結果を確認することをお勧めします。特に これ です。ところで、これらのタグはあなたの投稿にも役立つかもしれません。

3
MvG