まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2014/08/28(木)Windows上での flock の不具合?

これだからWindowsは……。

ロック状態を変更できない

use Fcntl;

my $fh;
sysopen($fh, "test.txt", O_CREAT | O_RDWR) or die;
flock($fh, Fcntl::LOCK_SH());
flock($fh, Fcntl::LOCK_EX());
close($fh);

Linuxなどでは、共有ロックが排他ロックに切り替わるのですが、Windows上では切り替えることができず*1、そのままブロッキングされてしまいます。

*1 : 自分自身で獲得した共有ロックが、排他ロックを獲得する際の障害になっている

共有ロック状態でtruncateすると、ファイルサイズが固定される

use Fcntl;

my $fh;
sysopen($fh, "test.txt", O_CREAT | O_RDWR) or die;
flock($fh, Fcntl::LOCK_SH());

truncate($fh, 0);		# ファイルサイズを 0 に
seek($fh, 0, 0);		# ファイルポインタを先頭へ
print $fh "test!!\n";
close($fh);

こんなテストプログラムを走らせます。LOCK_SH以外は、ロックを伴うファイル操作ではよく行う処理です。

LOCK_SH(共有ロック)は本来ならLOCK_EX(排他ロック)ですが、前項で述べたとおりWindows環境ではとりあえず共有ロックで開いておいて後から排他ロックに変更できないので、そのような状況を想定してLOCK_SHで記述してあります。

さてこのプログラムを実行すると、Windows上では0byteのファイルが作成されます。ActivePerl(5.14)の問題かもしれませんが、truncateでのファイルサイズ指定がそのままclose()後のファイルサイズになります。

不思議なことに、LOCK_EX(排他ロック)に変更するときちんと中身のあるファイルが作られます。

解決策

use Fcntl;

my $fh;
sysopen($fh, "test.txt", O_CREAT | O_RDWR) or die;
flock($fh, Fcntl::LOCK_SH());

seek($fh, 0, 0);		# ファイルポインタを先頭へ
print $fh "test!!\n";

truncate($fh, tell($fh));	# 現在の位置でファイル切り詰め
close($fh);

挙動まとめ 2017/02/26

  • Windowsでは一度ロックしたものに対し、再度ロックすることができない。
    • 同じファイルハンドルを使用しても「別のロック」とみなされてしまう。
  • Linux等では同じファイルハンドルを使えば、lockは何度でもできる(状態変更できる)。

別の問題 2015/01/16

そもそもLOCK_SH(共有ロック)状態で書き換えようとすると、以前のサイズよりも大きくならず、そもそも書き変えられないという謎のバグに当たりました。

詳細な発生条件は不明ですが、めんどうくさいので、Windowsの場合共有ロックではなく最初から排他ロックをかけることにしました。