電子書籍の厳選無料作品が豊富!

オープン中のファイルをflockによる排他をしたままrenameしたいのですがどうもうまくいきません。

sysopen(LOCK, "$file", O_WRONLY);   もしくは open(LOCK, "> $file");
flock(LOCK, 2);
rename($tempfile,$file);
close;

close前にrenameするとロックが外れてしまいます。
close後にrenameをするとやはりロックが外れた状態でのrenameになります。

renameするファイル以外にflock専用ファイルを固定で一つ用意すれば簡単なのですが
openしたいファイル数は相当な数があり、各ファイルごとにロックをかけたいのです。

全ファイル分のflock専用ファイルを用意するか、排他方式をmkdirにする以外
何か良い手立てはございますでしょうか。

A 回答 (5件)

しまった。

よく考えたら、$fh->seekや$fh->truncateのところで、プロセスが死ぬと、役立たずの印('+')を書き込まないから、次にロックを取得したプロセスが、データの一貫性を壊しちゃいますね。

なのでやっぱり、別途ロックファイルを用意するのがベスト、という事に。
    • good
    • 0
この回答へのお礼

参考ソースにクラスまで作成して頂き恐縮致します。
>まあ言いたかったのは「renameでロックは外れない」という事で。

言い方が大雑把で申し訳ございませんでした。
今回の質問の主旨である「実ファイルごとにflockを掛ける」には
実ファイル自体flockを掛ける為にまずopen→flockしなければならず、
その後renameする事により「今開いている(実ファイルの為のファイルハンドル)が役立たず」になる結果から
主旨である「実ファイルの為のロック効果」が無効=ロックが外れると言う言い方になってしまいました。
テンポラリファイルの作業を実ファイルopen中に記述することを省略してしまったのも良くありませんでした。誠に申し訳ございません。

現状では別途ロックファイルを用意して対応して参りたいと思います。
大変ご丁寧な対応に感謝致します。

お礼日時:2008/08/12 04:21

>具体的なソースを拝見できたら



ってことで以下に。どっか間違ってるかも。一応、Solaris10とMacOSX上のperlで試してます。
WindowsのSUAだと、truncateをコメントアウトしなきゃダメでした。
やはり、別途ロックファイルを用意するのが一番簡単かと。

まあ言いたかったのは「renameでロックは外れない」という事で。
※UNIX系なら。Windowsだと違うかも。

use warnings;
package CounterFile;
use Carp; use Fcntl qw( :flock ); use IO::File; use POSIX;
my $MARK_INVALID = '+';
sub new { my $class = shift; my $fname = shift;
my $self = bless {}, $class;
$self->{name} = $fname; $self->{cnt} = 0;
$self;
}
sub open { my $self = shift; my $fname = $self->{name};
my $fh = new IO::File( $fname, O_RDWR|O_CREAT );
confess '?![',$$,']IO::File>', $!, ' - ', $fname unless defined $fh;
flock( $fh, LOCK_EX ) or confess '?![',$$,']flock>', $!, ' - ', $fname;
# ロックを取得した時点で、今開いているファイルハンドルは、役立たずになっている可能性がある。その場合は再度openを試みなければいけない。
$fh;
}
sub inc { my $self = shift; my ($fh, $i);
do {
$fh = $self->open();
$i = <$fh> || 0;
} while ( $i eq $MARK_INVALID );
$self->{cnt} = ++$i;
my $fn_tmp = '.'.$self->{name}.$i.'-'.$$.'-'.time().'.tmp';
my $fh_tmp = new IO::File( $fn_tmp, O_WRONLY|O_CREAT );
confess '?![',$$,']IO::File>', $!, ' - ', $fn_tmp unless defined $fh_tmp;
$fh_tmp->print( $i ); undef $fh_tmp;
rename( $fn_tmp, $self->{name} ) or confess '?![',$$,']rename>', $!, ': ', $fn_tmp, '>', $self->{name};
# renameした時点で、今開いているファイル($fh)は役立たずになっており、ロック待ちしているファイルハンドルを持つ別プロセスに対し、役立たずである事を伝える必要がある。
$fh->seek( 0, 0 ) or confess '?![',$$,']seek>', $!, ' - ', $self->{name};
$fh->truncate( 0 ) or confess '?![',$$,']truncate>', $!, ' - ', $self->{name};
$fh->print( $MARK_INVALID ); undef $fh;
$self;
}
1;
package main;
$a = new CounterFile( 'a.dat' );
for ( my $k = 0; $k < 4; $k++ ){ print $$, ':', $a->inc()->{cnt}, "\n"; }
    • good
    • 0

> close前にrenameするとロックが外れてしまいます。



ロックが外れるのではなく、ロックをかけてないだけでは。
renameにより、$tempfileが$fnameになったときに、別プロセス/スレッドがロックを取得しようとしている$fnameの実体は、$tempfileなので。

ただ、renameが完了した時点で、その$fnameは次の人がアクセスしても良い状態になっているはず(逆に$tempfileの処理が完了してないのにrenameしてはダメ。)なので、ロックがかかってないのは問題にならないと思われます。

この回答への補足

ご回答ありがとうございます。

> ただ、renameが完了した時点で、その$fnameは次の人がアクセスしても良い状態になっているはず(逆に$tempfileの処理が完了してないのにrenameしてはダメ。)なので、ロックがかかってないのは問題にならないと思われます。

renameする場所はflock内を想定していると思われますが、rename→ロックが外れる→closeとなりますと排他処理が不完全になってしまいます。具体的なソースを拝見できたらと思います。解釈が間違えておりましたらご指摘をお願い致します。

ちなみに簡単なカウンターを例にソースを書いてみました。rename後のcloseに問題があるようで排他は不完全でした。

sysopen(LOCK, $file, O_WRONLY|O_CREAT);
flock(LOCK, 2);
copy($file,$tempfile);
chmod(0666,$tempfile);

sysopen(TMPLOCK, $tempfile, O_RDWR|O_CREAT);
flock(TMPLOCK, 2);
my$cnt=<TMPLOCK>;
seek(TMPLOCK, 0, 0);
$cnt++;
print TMPLOCK $cnt;
truncate(TMPLOCK, tell(TMPLOCK));
close(TMPLOCK);

rename($tempfile,$file);

close(LOCK);

補足日時:2008/08/08 10:51
    • good
    • 0

ちょっと用途が分かりませんがファイルをコピーしてコピーした方を


ロック付きで開きその後にコピー元を削除するという方法はどう
でしょうか。
結構面倒ですね。
何をしたいのかがもう少し詳細に分かればまた別の代替案なども
でてくるかも。

この回答への補足

ご回答ありがとうございます。

ファイル読み書き時のファイルの破損確立を極力減らす事を目標にしております。
具体的にやりたい事は
書き込み予定ファイル($file)のテンポファイル($tempfile)をコピー作成
→ rename($tempfile,$file); です。

そこで確実な排他処理を行いたいと言う事になります。

テンポファイルが必要な理由は以下の通りです。
・$tempfileへの正常に書き込みが完了した確認が取れた後に本ファイル$fileにrename
・ディスクスペース確認

> コピーした方をロック付きで開き
> その後にコピー元を削除

コピー元($file)が存在しなくなりますと、
コピーした方($tempfile)が正常に処理を完了して戻す処理→rename($tempfile,$file); を行うまでにプロセスが死にますと元ファイルの存在がなくなってしまいます。
$fileを保護する為に代理ファイルの$tempfileを用意したい意向ですので根本の$fileを削除してしまうのでは無意味になってしまいます。

案外、結構面倒です。。
本当の答えは実はシンプルなんだ!みたいな意見が出たら嬉しいです。
幻想でしょうか?

補足日時:2008/08/03 00:50
    • good
    • 0

代替案ですが、renameしてからopenではダメですか?

この回答への補足

お答え頂きましてありがとうございます。
renameしてからopenとは$file自体のファイル名をrenameして一時的に$file名の存在を消す。と言う意味でしょうか。

何卒お導きを頂けませんでしょうか。

補足日時:2008/08/02 16:16
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!