flock(LOCK_EX) で EBADF

ちょっとハマったのでメモ。

ファイルを flock() で排他的にロックするために次のようにすると成功します。

% ruby -e 'File.open("hoge").flock(File::LOCK_EX); puts "OK"'
OK

が、NFS 上で同じことをやると失敗します。

% ruby -e 'File.open("hoge").flock(File::LOCK_EX); puts "OK"'
-e:1:in `flock': Bad file descriptor @ rb_file_flock - hoge (Errno::EBADF)
    from -e:1:in `<main>'

7年前に書いたんですが、Linux は NFS ファイルシステムに対して flock() すると fcntl(F_SETLK) を使います。

で、fcntl(F_SETLK)flock() と異なり、排他ロックをするにはファイルを書き込み可のモードでオープンしておかないといけません。

通常のローカルファイルシステムでも読み込み専用でオープンしたファイルに fcntl(F_SETLK) を使って排他ロックを行えば同じエラーが発生します。

% ruby -rfcntl -e 'File.open("hoge").fcntl(Fcntl::F_SETLK, [Fcntl::F_WRLCK, 0].pack("ss"))'
-e:1:in `fcntl': Bad file descriptor @ rb_fcntl - hoge (Errno::EBADF)
    from -e:1:in `<main>'

書き込みできるモードでオープンしておけばエラーにはなりません。

% ruby -rfcntl -e 'File.open("hoge", "r+").fcntl(Fcntl::F_SETLK, [Fcntl::F_WRLCK, 0].pack("ss")); puts "OK"'
OK

つまり、NFS 上でも書き込みできるモードでファイルをオープンしておけば flock() で排他制御できます。

% ruby -e 'File.open("hoge", "r+").flock(File::LOCK_EX); puts "OK"'
OK

ということで、flock(LOCK_EX) する場合は書き込みできるモードでファイルをオープンしておいた方が無難というお話でした。

誰も掴んでない TCP ポートを使うことができない

たとえば、次の例では 12345 ポートは netstat や lsof でも出てこないし、クライアントから接続することもできません。

# netstat -a | grep 12345
# lsof -i :12345
# telnet localhost 12345
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused

が、12345 ポートで LISTEN するサーバーを起動すると Address already in use になってしまいます。

# ruby -rsocket -e 'TCPServer.new(12345)'
-e:1:in `initialize': Address already in use - bind(2) for nil port 12345 (Errno::EADDRINUSE)
        from -e:1:in `new'
        from -e:1:in `<main>'

どうやら bind(2) した後 listen(2) しなければ、このようなプロセスを作ることができるようです。

# ruby -rsocket -e 'Socket.new(:INET, :STREAM).bind(Addrinfo.tcp("0.0.0.0", 12345)); sleep'

ちなみに lsof でこのプロセスを見ると、問題の Socket ファイルは can't identify protocol になってました。

# lsof -p 11210
〜(中略)〜
ruby    11210 tommy    7u  sock    0,8      0t0 1105451 can't identify protocol

このような状況になったら、どうやればこのプロセスを探し出すことができるんでしょうねぇ…。

Ruby, MySQL のうるう秒の扱い

2015/7/1 にうるう秒が挿入されるということで、うるう秒の話題が盛り上がってるようなので自分も書いてみます。

Linux 上のプログラムが時刻で60秒を刻むには、うるう秒対応のタイムゾーンを使う必要があります。

通常はうるう秒を考慮していないタイムゾーンが使用されているので、60秒を含む時刻になることはありません。 60秒を含む時刻を扱うには、right/Japan のように right/ を前につけたタイムゾーンを指定します。

前回のうるう秒は 2012/7/1 08:59:60 (JST) だったので、これで試してみます。

% TZ=Japan date --date='2012-07-01 08:59:60'
date: `2012-07-01 08:59:60' は無効な日付です
% TZ=right/Japan date --date='2012-07-01 08:59:60'
2012年  7月  1日 日曜日 08:59:60 JST

Ruby

うるう秒なしのタイムゾーン:

% TZ=Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:59").to_i)'
1341100799
2012-07-01 08:59:59 +0900
% TZ=Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:60").to_i)'
1341100800
2012-07-01 09:00:00 +0900
% TZ=Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 09:00:00").to_i)'
1341100800
2012-07-01 09:00:00 +0900

08:59:60 を指定しても 09:00:00 として扱われています。

うるう秒ありのタイムゾーン:

% TZ=right/Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:59").to_i)'
1341100823
2012-07-01 08:59:59 +0900
% TZ=right/Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:60").to_i)'
1341100824
2012-07-01 08:59:60 +0900
% TZ=right/Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 09:00:00").to_i)'
1341100825
2012-07-01 09:00:00 +0900

ちゃんと 08:59:60 を扱うことができています。

うるう秒を扱えるかどうかで時刻の内部表現(1970-01-01 00:00:00 UTC からの経過秒数)が25秒ずれていますが、これは今までに25回うるう秒が挿入されたためです。

MySQL

うるう秒なしのタイムゾーン(TZ=Japan で mysqld を起動):

mysql> select from_unixtime(1341100799), unix_timestamp('2012-07-01 08:59:59');
+---------------------------+---------------------------------------+
| from_unixtime(1341100799) | unix_timestamp('2012-07-01 08:59:59') |
+---------------------------+---------------------------------------+
| 2012-07-01 08:59:59       |                            1341100799 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100800), unix_timestamp('2012-07-01 08:59:60');
+---------------------------+---------------------------------------+
| from_unixtime(1341100800) | unix_timestamp('2012-07-01 08:59:60') |
+---------------------------+---------------------------------------+
| 2012-07-01 09:00:00       |                                     0 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100800), unix_timestamp('2012-07-01 09:00:00');
+---------------------------+---------------------------------------+
| from_unixtime(1341100800) | unix_timestamp('2012-07-01 09:00:00') |
+---------------------------+---------------------------------------+
| 2012-07-01 09:00:00       |                            1341100800 |
+---------------------------+---------------------------------------+
1 row in set (0.00 sec)

08:59:60 はパースできずに 0 を返しています。

うるう秒ありのタイムゾーン(TZ=right/Japan で mysqld を起動):

mysql> select from_unixtime(1341100823), unix_timestamp('2012-07-01 08:59:59');
+---------------------------+---------------------------------------+
| from_unixtime(1341100823) | unix_timestamp('2012-07-01 08:59:59') |
+---------------------------+---------------------------------------+
| 2012-07-01 08:59:59       |                            1341100823 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100824), unix_timestamp('2012-07-01 08:59:60');
+---------------------------+---------------------------------------+
| from_unixtime(1341100824) | unix_timestamp('2012-07-01 08:59:60') |
+---------------------------+---------------------------------------+
| 2012-07-01 08:59:59       |                                     0 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100825), unix_timestamp('2012-07-01 09:00:00');
+---------------------------+---------------------------------------+
| from_unixtime(1341100825) | unix_timestamp('2012-07-01 09:00:00') |
+---------------------------+---------------------------------------+
| 2012-07-01 09:00:00       |                            1341100825 |
+---------------------------+---------------------------------------+

08:59:60 は 08:59:59 として扱われます。やはりパースはできません。

まとめ

  • Linux の場合は特別な設定をしない限り、通常は 60秒を含む時刻を返すことはありません。
  • 60秒を含む時刻をどのように扱うかはプログラム次第です。
  • Ruby は 60秒を含む時刻文字列をパースできますが、MySQL はできません。
  • うるう秒を扱えるタイムゾーンの場合、Ruby は 60秒を含む時刻を返すことがありますが、MySQL は 60秒になることはありません。

相手がいないのに ESTABLISHED になってる TCP ポート

最近 ParallelServer というライブラリを作ったのですが、その最中に奇妙な状態になってる TCP ポートを見つけたので、メモっておきます。

Ruby では TCP サーバーは次のような感じで作ることができます。お手軽ですね。

require 'socket'

Socket.tcp_server_loop(12345) do |socket, client_addr|
  socket.puts "Your IP address: #{client_addr.ip_address}"
  name = socket.gets
  socket.puts "Hello, #{name}"
  socket.close
end

これは 12345 ポートでクライアントからの接続を待ち、接続されたらクライアントのIPアドレスとクライアントからの入力をクライアントに送信して切断するだけの簡単なプログラムです。

~% nc -v localhost 12345
Connection to localhost 12345 port [tcp/*] succeeded!
Your IP address: 127.0.0.1
hogehoge
Hello, hogehoge
~% 

サーバープログラム起動直後の 12345 ポートの状態は次のようになります。

~% netstat -an | grep 12345
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN     
tcp6       0      0 :::12345                :::*                    LISTEN     

IPv4 と IPv6 の両方で LISTEN 状態です。

クライアントから接続すると次のように ESTABLISHED になります。

~% netstat -an | grep 12345
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:12345         127.0.0.1:56863         ESTABLISHED
tcp        0      0 127.0.0.1:56863         127.0.0.1:12345         ESTABLISHED
tcp6       0      0 :::12345                :::*                    LISTEN     

サーバーの 12345 とクライアントの 56863 が接続されている状態です。 同じサーバー内で接続しているため、1つの接続について、サーバーからみた接続とクライアントから見た接続の2行出力されています。

このように普通は接続が確立したらサーバーとクライアントの両方が ESTABLISHED になります。

ここで次のクライアントプログラムを動かしてみます。

require 'socket'

sockets = []
200.times do |i|
  p i
  sockets.push TCPSocket.new("localhost", 12345)
end
sleep

サーバーに 200接続したまま何もしないプログラムです。

サーバーは最初の接続からのクライアントの入力を待つので、2個め以降の接続を処理できません。

普通のサーバープログラムは、そんなことにならないように、クライアントからの接続を受け付けた時に fork したりスレッドを作ったりするのですが、ここでは意図的にこのようにしてます。

この状態で netstat を見ると、サーバー側にいくつか SYN_RECV 状態のポートがあります。

tcp        0      0 127.0.0.1:12345         127.0.0.1:36807         ESTABLISHED
tcp        0      0 127.0.0.1:12345         127.0.0.1:36930         SYN_RECV       ← これ
tcp        0      0 127.0.0.1:12345         127.0.0.1:36953         SYN_RECV       ← これ
tcp        0      0 127.0.0.1:36807         127.0.0.1:12345         ESTABLISHED
tcp        0      0 127.0.0.1:36930         127.0.0.1:12345         ESTABLISHED    ← これ
tcp        0      0 127.0.0.1:36953         127.0.0.1:12345         ESTABLISHED    ← これ

クライアントポート 36807 はちゃんと対向するサーバーが ESTABLISHED になっているのですが、36930, 36953 ポートは SYN_RECV になっています。

しばらくすると、この SYN_RECV 状態のポートはなくなります。といっても ESTABLISHED になったわけではなく、消えてなくなってしまいます。

tcp        0      0 127.0.0.1:12345         127.0.0.1:36807         ESTABLISHED
tcp        0      0 127.0.0.1:36807         127.0.0.1:12345         ESTABLISHED
tcp        0      0 127.0.0.1:36930         127.0.0.1:12345         ESTABLISHED    ← これ
tcp        0      0 127.0.0.1:36953         127.0.0.1:12345         ESTABLISHED    ← これ

クライアントは ESTABLISHED だと思っているのに接続相手のサーバーはいません。

サーバープログラムを停止しても状態は変わりません。 クライアントプログラムを停止しない限り残ったままです。

tcp        0      0 127.0.0.1:36930         127.0.0.1:12345         ESTABLISHED    ← これ
tcp        0      0 127.0.0.1:36953         127.0.0.1:12345         ESTABLISHED    ← これ

このように 200 くらい接続すると SYN_RECV が確認できるのですけど、どうやらこれは listen のバックログの値に関連してるようです。

Socket.tcp_server_loop は内部で listen(Socket::SOMAXCONN) しています。手元の Linux では SOMAXCONN は 128 になっていました。

もっと単純に次のようなサーバープログラムで試してみました。

require 'socket'

TCPServer.new(12345).listen(5)
sleep

このようにバックログを 5 にすると、クライアント接続が 10 くらいでも再現できます。

なお、listen(Socket::SOMAXCONN) しているのは Ruby 2.0 以降です。Ruby 1.9.3 では listen(5) としているので、すぐに発生します。

で、結局こんなことになってしまう理由はわかりませんでした。

クライアントが ESTABLISHED だと思ってるのに SYN_RECV になってるのもわからないし、相手がいないのに ESTABLISHED になってるソケットが存在してるのもわかりません。 ネットワークの向こうの別のサーバであればパケット落ちとかでこのような状況になるのもわかるのですけど。

バックログを超えたら SYN_RECV にならなくてもいいと思うんですけど…。 TCP/IP はそういうもんなんですかね。

[追記]

Facebook の方で色々教えてもらいました。

OS X では発生しないようです。うすうす感じていたのですがやはり Linux の実装の問題のようです。

教えてもらったブログ http://veithen.blogspot.jp/2014/01/how-tcp-backlog-works-in-linux.html に答えがありました。

BSD では接続用のキューが1つで、キューがいっぱいの場合はクライアントからの SYN を破棄するだけなので、クライアントが ESTABLISHED になることはありません。私の知ってる TCP の動きです。

Linux では SYN キューと accept キューの2つがあるようで、SYN キューに入った時に SYN ACK を返し SYN_RECV 状態になり、accept キューに入った時に ESTABLISHED になるようです。

サーバープログラムが accept しなければ、accept キューがいっぱいになり SYN キューも掃けません。SYN キューにたまったものは一定時間たつと削除されるようです。

SYN キューの大きさは、sysctl net.ipv4.tcp_max_syn_backlog で、accept キューの大きさはプログラムから指定する listen のバックログです。

sysctl -w net.ipv4.tcp_max_syn_backlog=0 にして試してみたら BSD の動きに近くなりました。ただし 0 に設定しても1個は SYN_RECV になってしまうようです。

また sysctl -w net.ipv4.tcp_abort_on_overflow=1 にすると accept キューがあふれた場合、クライアントからの SYN に RST を返すようです。この場合はクライアントプログラムは connect(2) に対して ECONNRESET エラーが返ります。

RST じゃなくて SYN を無視するようになれば BSD と同じになるんじゃないかと思ったのですが、そういうパラメータは無さそうでした。

まあ別に BSD と同じ動きにしたかったわけではなく、ただ疑問に思っただけだったので、それは解決したのでもういいです。

スクロールバーの矢印を表示する

最近は Xubuntu を使ってますが、テーマによるのかもしれませんが、スクロールバーの端の矢印が表示されなくなってて不便なので表示する方法を調べました。

Gtk3

$HOME/.config/gtk-3.0/gtk.css を次の内容で作成

.scrollbar {
    -GtkScrollbar-has-backward-stepper: 1;
    -GtkScrollbar-has-forward-stepper: 1;
}

Gtk2

個人ごとに設定する方法はわかりませんでした。

/usr/share/themes/テーマ名/gtk-2.0/gtkrc の内容を変更

        GtkScrollbar            ::has-backward-stepper                  = 1
        GtkScrollbar            ::has-forward-stepper                   = 1
...
        GtkScrollbar::stepper-size      = 13

Xubuntu 14.04

Ubuntu が Unity になって以来、デスクトップ環境を転々としている自分ですが、最近は Xubuntu を使っています。

しばらく前までは Linux Mint を使ってたのですが、会社の PC の OS が Windows 必須になってしまって、しかも PC のスペックがかなりヘボい(Core i5, メモリ4G, Windows 7 32bit)ので、その上の VM で動かすために軽いデスクトップとして Lubuntu を選択しました。

しばらく Lubuntu を使ってたのですが、Ubuntu 14.04 からデスクトップ環境でも 64bit が推奨になったということなので、64bit 化するついでに Xubuntu 14.04 にしてみました。 Lubuntu も良かったのですが、GUI での設定がちょっと弱いとか、Windows の VirtualBox 上で Emacs 上の文字が消える現象が起きたりしてたので。

結局 Xubuntu にしても Emacs で文字が消える現象は直らなかったのですが…。VirtualBox ではなく VMware Player を使っていた時はこの現象は起きてなかったので、VirtualBox 用の X サーバーの問題かもしれません。同じ頃に Emacs 23 から Emacs 24 にしたので、もしかしたら Emacs 24 の問題なのかもしれません。必ず文字単位で消えるわけではないので、部分的な再描画処理がおかしいような気がします。

以下、Xubuntu 14.04 での問題と対処。

Migu フォントでフォントサイズによって"「" の上の横棒が消える

Ubuntu 14.04 の文字が欠けるバグを何とかしてみた

freetype 2.5.x broke rendering of the default Korean font

libfreetype6 の問題のようです。 http://packages.ubuntu.com/utopic/libfreetype6 から新しいものをダウンロードしてインストールしたら直りました。 待っていればそのうち 14.04 にもバグ修正パッケージがリリースされるかもしれません。

[追記] libfreetype6 2.5.2-1ubuntu2.2 で直ったようです。

蓋を閉じてサスペンド後レジュームするとデスクトップが復帰しない

蓋を閉じるのではなく、メニューからサスペンドした場合はちゃんと復帰します。

Ubuntu 14.04 blank screen after wakeup from sleep

Comment 56 for bug 1303736 この対処で直りました。

gnome-terminal の縦方向最大化トグルがおかしい

Xubuntu の端末エミュレータは xfce4-terminal ですが、Ctrl+[+-] で拡大縮小ができないので、gnome-terminal を使ってますが、縦方向最大化トグルで、最大化したあとに何故か元に戻ってくれません。gnome-terminal 以外のウィンドウではちゃんと働くので謎です。

他にも xfwm4 だと、修飾キー+マウスイベントをカスタマイズできなかったので、openbox を使用することにしました。 「設定マネージャー」の「セッションと起動」の「自動開始アプリケーション」に openbox --replace を追加しただけです。ウィンドウマネージャー変更のちゃんとしたやり方があるのかもしれませんが…。

VM 上の Linux で ThinkPad USB トラックポイントキーボードを使う

この前半額キャンペーンがあったので ThinkPad USB トラックポイントキーボード(日本語配列)を買いました。

最近会社のPCがWindowsなんぞになってしまったので、VMware を入れてその上で Linux Mint を動かしています(もっさり)。

Windows でもつなげるだけで普通に使えました。トラックポイントもそれなりに使えます。 が、トラックポイントの速度をカスタマイズするにはドライバが必要です。 tpusbkybdwtrackpoint_110.exe をインストールしました。

キーマップは例によって Linux英語配列に変更します(何故最初から英語配列キーボードを買わなかったのかはこの辺を参照)。が、CapsLock だけは何故か変更できなかったので、この辺を参考に Windows のレジストリをいじって CapsLock を Ctrl に変更しました。

コントロールパネルのマウスの設定の「ThinkPad優先スクローリング」がオンのままだと、中ボタンとスクロールのイベントが VM に渡されないようなので、チェックを外します。

無事ボタンイベントが Linux にも渡されるようになったのですが、スクロールしようと思って中ボタンを押すと即座にボタンダウンイベントが発行されてしまって、よろしくないです。 なお、トラックポイントを動かしながら中ボタンを押した場合は中ボタンイベントが発生することなくスクロールできます。

Xorg の evdev 設定で EmulateWheel を有効にして EmulateWheelButton を 2 に設定したらマシになりました。でも中ボタン押下直後にスクロールするとやはり中ボタンイベントが発生してしまいます。EmulateWheelTimeout (デフォルトは 0.2 秒)を調整した方がいいかもしれません。あまり小さくすると、普通に中ボタンをクリックしても効かなくなるのでビミョーな感じです。いっそのこと Emulate3Buttons も有効にして中ボタンの代わりに左右ボタン同時クリックを使うようにした方がいいのかもしれません。

とりあえずしばらく使ってみます。

日本語キーボードを英語配列風にする

自分は日本語配列キーボードの ThinkPad を使用しているのですが、英語配列風にして使ってます。

昔は Sun の Type3 や Type4 キーボードを使っていて、最近は英語配列HHK キーボードを使ってるので、英語配列の方が使いやすいのです。

英語配列キーボードの ThinkPad にするのも良かったんですが、最近の英語配列は Escape キーとチルダキーが変な位置にあるのが気に入りません。Escape キーは 1 キーの左横で、チルダキーは右端の辺りにあるべきです。ソフト的にキー配列をいじろうにも、Backspace キーが幅をきかせていて、物理的にキーが一つ足りません。

ということで、日本語キーボードを使って、ソフト的にキー配列を変えています。

CapsLock は Control キーにするのは当然ですが、ついでに、無変換も Super キーにします。 チルダキーは Enter キーの左下の「む」キーに割り当てます。

続きを読む

Linux Mint 15 Cinnamon Edition

今まで Compiz のバイナリは https://launchpad.net/~guido-iodice にある PPA を使っていたんですが、それがなくなってしまいました。 どうも Compiz は終了みたいなので、5年ほど愛用していた Compiz から離れることにしました。

ということで Linux Mint 15 Cinnamon Edition をインストールしてみました。

Linux Mint 15 Mate Edition を使っていたので Cinnamon パッケージを追加で入れればなんとかなるかと思ったのですが、どうも動きが怪しいので、最初からインストールすることにしました。

続きを読む

Linux Mint 15

Linux Mint を 14 から 15 にアップグレードしました。

アップグレード手順は http://blog.lindwurm.biz/2013/05/how-to-upgrade-mint.html を参考にしました。 Ubuntu と異なり、GUI でアップグレードする方法はないようで、公式の推奨方法は新規インストールとのこと。

まあ apt でなんとかなるだろうと、非推奨の apt でやりました。特に問題なく終わりました。

スクリーンセーバーのスライドショーが Pictures 配下の画像を使用するようになっていました。 前からそうだったような気もするのですが、どうやってディレクトリを変更したのか忘れてしまったので、 http://d.hatena.ne.jp/vividcode/20100515/1273910299 を参考にして設定しました。

Compiz は Linux Mint 14 でインストールしたものがそのまま特に問題なく使えているのですが、CompizConfig Setting Manager(ccsm)のウィンドウにアイコンとテキストが表示されなくなってしまいました。 https://bugs.launchpad.net/ubuntu/+source/compiz/+bug/1130941/comments/4 の対処で直りました。

困っているのは、Xmodmap の設定がリセットされることがあることです。 おそらく Bluetooth マウスの再接続の際にリセットされているんじゃないかと思っています。 Ubuntu 12.10 でも同様の現象が起きてたので、Xmodmap なんて使わずにちゃんと XKB でキーマップ作れってことなのかもしれません…。

rsync で大量ファイルを消すと遅い

なんかいちいさん(id:ichii386)が面白そうなこと(「rsync で削除すると速い (?) 件」)をやってたので、手元の PC でも試してみました。

% mkdir /tmp/a
% cd /tmp/a
% seq 1000000 | time xargs -n100 touch
xargs -n100 touch  0.33s user 1.02s system 3% cpu 38.471 total
% cd /tmp
% time rm -rf a
rm -rf a  0.94s user 12.84s system 14% cpu 1:35.91 total
% cd /tmp
% mkdir empty
% time rsync -a --delete empty/ a
rsync -a --delete empty/ a  1.65s user 20.53s system 8% cpu 4:31.73 total

rm の約96秒に対して、rsync は約272秒。めっちゃ遅いんですけど…。

dtrace なんて使えないので、strace でシステムコールの回数を取ってみました。

[rm]

1000001 unlinkat
    976 getdents64
    840 brk
     11 mmap2
     10 fstatfs64

[rsync]

1000004 lstat64
1000000 unlink
    978 getdents64
    327 mmap2
    291 munmap
     63 select
     45 close
     42 read

んー、rsync の方が余計なこといっぱいしてそうです。そのせいでしょうか。

rm と rsync のバージョンはこんな感じです。

% rm --version
rm (GNU coreutils) 8.13

% rsync --version
rsync  version 3.0.9  protocol version 30

とくに結論はありません。興味がつきたのでここまで。

NFS で排他制御

NFS 上のファイルに対する flock() は働かない。man 2 flock にも次のように書かれている。

flock() does not lock files over NFS. Use fcntl(2) instead: that does work over NFS, given a sufficiently recent version of Linux and a server which supports locking.

これは常識…と思っていたのだが、どうやら Linux 2.6.12 以降はちゃんと働くらしい。

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.

http://nfs.sourceforge.net/#faq_d10

試してみたらちゃんと排他制御できた。

こんな風にも書かれてる。

It's worth noting that until early 2.6 kernels, O_EXCL creates were not atomic on Linux NFS clients. Don't use O_EXCL creates and expect atomic behavior among multiple NFS client unless you are running a kernel newer than 2.6.5.

2.6.5 以降であれば creat(2) の O_EXCL もちゃんと働くらしい。こっちは未検証。