メモの日々


2005年11月07日(月) [長年日記]

[unix] Linuxのflock(2)でNFS上にあるファイルをロックする

flock(2)を使う

flock(2)のマニュアルには

flock(2) は NFS 上のファイルのロックをしない。代わりに fcntl(2) を使用すること。これにより、十分に新しいバージョンの Linux と、ロック機能をサポートした NFS サーバを使用することにより、NFS 上でロックができる。

とある。が、

Linux NFS faqのD.10に、

The NFS client in 2.6.12 provides support for flock()/BSD locks on NFS files by emulating the BSD-style locks in terms of POSIX byte range locks. Other NFS clients that use the same emulation mechanism, or that use fcntl()/POSIX locks, will then see the same locks that the Linux NFS client sees.

とあり、よく分からんがLinuxのカーネル2.6.12からはflock(2)を使ってもNFS上のファイルをロックできるように読めたのでFedora Core 3のカーネル2.6.12-1.1381_FC3で試してみた。プログラムは次の通り。

#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

void lock_flock(int fd)
{
	if (flock(fd, LOCK_EX)) {
		perror("flock");
		exit(1);
	}
}

void unlock_flock(fd)
{
	if (flock(fd, LOCK_UN)) {
		perror("flock");
		exit(1);
	}
}

void lock_fcntl(int fd)
{
	struct flock lock;

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	if (fcntl(fd, F_SETLKW, &lock)) {
		perror("fcntl");
		exit(1);
	}
}

void unlock_fcntl(int fd)
{
	struct flock lock;

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	if (fcntl(fd, F_UNLCK, &lock)) {
		perror("fcntl");
		exit(1);
	}
}

int main(int argc, char *argv[])
{
	FILE *fp;
	int fd;
	pid_t pid;
	time_t start;

	if (argc < 2) {
		exit(1);
	}

	/* ファイルをオープンして */
	fp = fopen(argv[1], "r+");
	if (fp == NULL) {
		perror("fopen");
		exit(1);
	}

	start = time(NULL);
	pid = getpid();
	fd = fileno(fp);

	/* ロックして */
	printf("[%d] %ld: before locking.\n", pid, time(NULL) - start);
	lock_flock(fd);
//	lock_fcntl(fd);
	printf("[%d] %ld: file was locked. sleeping...\n", pid, time(NULL) - start);

	/* 2秒スリープして */
	sleep(2);

	/* アンロックする */
	unlock_flock(fd);
//	unlock_fcntl(fd);
	printf("[%d] %ld: file was unlocked\n", pid, time(NULL) - start);

	fclose(fp);
	return 0;
}

これをコンパイルしてできたプログラム「locker」を、次のシェルスクリプト

#!/bin/sh
./locker $1 &
./locker $1 &
./locker $1 &

を使って2台のNFSクライアントで同時に実行する($1にNFS上のファイルを指定)と、次のように出力された。

PC1

[3886] 0: before locking.
[3888] 0: before locking.
[3887] 0: before locking.
[3886] 0: file was locked. sleeping...
[3886] 2: file was unlocked
[3887] 2: file was locked. sleeping...
[3887] 4: file was unlocked
[3888] 4: file was locked. sleeping...
[3888] 6: file was unlocked

PC2

[3892] 0: before locking.
[3894] 0: before locking.
[3893] 0: before locking.
[3892] 6: file was locked. sleeping...
[3892] 8: file was unlocked
[3893] 8: file was locked. sleeping...
[3893] 10: file was unlocked
[3894] 10: file was locked. sleeping...
[3894] 12: file was unlocked

うん、うまくロックできているようだ。

が、同じプログラムをNFSクライアントとNFSサーバの2台で同時に実行したら、それぞれ独立にロックを獲得してしまった。flock(2)を使った場合、NFSサーバ上のプロセスは特別扱いされてしまうようだ(もっと新しいカーネルなら改善されているのかもしれないけど)。

また、カーネルを2.6.11-1.35_FC3にして試すと、NFSクライアント間でも独立にロックを獲得してしまった(単一PC内のプロセス間では正しくロックできていた)。確かに2.6.12から挙動が変わっているようだ。

fcntl(2)を使う

上のプログラムでコメントアウトしている所を生かし、fcntlだとどうなるかも試した。

結果、こちらだとNFSクライアント間、NFSクライアント〜NFSサーバ間共に、きちんとロックできているようだった。なので、NFSサーバ上でもプログラムが動く場合はfcntlを使う方がよさそう。

また、flockを使うプログラムとfcntlを使うプログラムを同時に起動してみると、NFSクライアント間であればきちんとロックできていた。

[unix] fcntl(2)がアンロックを検出しない

上のプログラムを最初に試したときは思惑通りに動かず、

[4154] 0: before locking.
[4155] 0: before locking.
[4156] 0: before locking.
[4154] 0: file was locked. sleeping...
[4154] 2: file was unlocked
[4155] 30: file was locked. sleeping...
[4155] 32: file was unlocked
[4156] 60: file was locked. sleeping...
[4156] 62: file was unlocked

のような出力になっていた。最初のプロセスがロックを解放しても次のプロセスがなかなかロックを獲得してくれなかった(獲得するのは30秒後)。

iptablesをまず疑い、確かにフィルタリングが掛かっていたのでiptablesを止めてみたが改善せず。その後何をどうしても原因がわからず、もうそういう仕様なのだと結論付けていたのだが、ふと気付き、NFSサーバのhostsファイルにNFSクライアントのエントリを追加したら治った。

tcpdumpの出力を眺めていてふと気付いたので、困ったらパケットキャプチャしろ、というのが教訓。

やること

  • 蛍光灯を捨てる
  • 請書
  • 宛名シール
  • 収入印紙