Perl/CGIプログラムのファイルロック
今回は、Perl/CGIプログラムのファイルロックについて学習していきましょう。
編集前記
今回は Perl/CGIプログラム でファイルロックを使いこなすための学習をするので、編集前記ではそれに関連してファイルテスト演算子について書くことにします。
以前、 Perl/CGIプログラムからファイルを読み書きする方法 について説明しましたが、今回はそのあたりに関連した内容です。
ファイルテスト演算子とは、Perl/CGIプログラム上から指定されたファイルがどのような状態になっているかをチェックするための命令文のことです。
ファイルテスト演算子を簡単に説明すると、各ファイルのパーミッション(状態)をチェックすることのできる命令というイメージです。
ファイルテスト演算子には複数の種類があるのでこの限りではありませんが、イメージ的にはこれで問題ないと思います。
そもそもなぜファイルテスト演算子が必要なんでしょうか?
複数の種類のPerl/CGIスクリプトをダウンロードできるサイトを見ればわかるように、CGIやPerlなどのプログラム一式というのは、ほとんどの確立で1つのファイルにおさまることなどありません。
もちろんユーザー側の視点に立てば、Perl/CGIプログラムというのは1つのファイルにまとめられていた方がわかりやすいです。
Perl/CGIプログラムをダウンロード後解凍した瞬間、たくさんのファイルやフォルダが出てくると、それだけでサーバーに設置する気がなくなりますからね。
しかし、 サブルーチン や 関数ライブラリ など、Perl/CGIプログラムを分割して個々で処理を作成するという考え方があります。
これはPerl/CGIプログラムだけの話ではなく、プログラミングの世界全般にあります。
Perl/CGIプログラムひとつとっても、分割できる処理は分割してプログラミングした方が、プログラマー側としては楽だし早いのです。
それに、なにもかも1つのファイルにまとめてしまおうという考え方自体が、今のプログラム構造から見て無理な話です。
例を挙げて説明すると…。
Perl/CGIの中でも比較的お手軽なアクセスカウンターCGIでさえ、CGIプログラム本体と、数値カウント記録用のファイルの2つが最低でも必要です。
さらに、数値カウント用の記録ファイルに対して、今回学習するファイルロック処理などを考えると、さらに増えていくわけです。
このように、1つのPerl/CGIプログラムでもファイル数が増えてしまうことはわかりましたが、そうなると設置する作業がややこしくなってきます。
特にCGI初心者は、複数のファイルをASCIIモードとバイナリモードでアップロードしただけで、もう限界ですから、アップロードしたCGIプログラムファイルを確認する余裕なんてありません。
そういった状況も考慮して、Perl/CGIプログラマー側で、サーバーに設置する作業を助けるようなプログラムというのも一緒に作ってあげる必要があります。
とは言っても、そんなに大掛かりなPerl/CGIプログラムを用意する必要はありません。
簡単に、Perl/CGIプログラムファイルやデータ記録用のファイル一式が、すべてそろっているかを確認するぐらいでかまいません。
欲を言うと、データ記録用のファイルには、実際に書き込みテストなどもするとベターです。
で、今回は、そんなファイルをチェックするPerl/CGIプログラミングで使用するファイルテスト演算子の種類をいくつか紹介します。
ファイルテスト演算子があるおかげで、Perl/CGIプログラム側から、外部ファイルの状態を簡単にチェックすることができます。
例えば、ファイルにデータを書き込む際、ファイルの存在チェックはもちろん、本当にデータを書き込めるのかどうかも調べることができます。
ファイルテスト演算子を使って、CGI設置時にすべてのファイルがそろっているかを確認させて、もしそろっていなければエラーメッセージを出すようにプログラミングすることもできます。
ファイルテスト演算子にはいろいろな種類があるので、使用用途に合わせて使い分けていきましょう。
では、Perlプログラミングで使用するファイルテスト演算子を紹介していきます。
- -r
- 読み込み可能かどうかを調べるファイルテスト演算子。
- -w
- 書き込み可能かどうかを調べるファイルテスト演算子。
- -x
- 実行可能かどうかを調べるファイルテスト演算子。
- -e
- ファイルの存在を調べるファイルテスト演算子。
- -z
- ファイルサイズが「0」かどうかを調べるファイルテスト演算子。
- -s
- ファイルサイズを調べるファイルテスト演算子。
- -f
- 指定されたものがファイルかどうかを調べるファイルテスト演算子。
- -d
- 指定されたものがディレクトリ(フォルダ)かどうかを調べるファイルテスト演算子。
- -T
- テキストファイルかどうかを調べるファイルテスト演算子。
- -B
- バイナリファイルかどうかを調べるファイルテスト演算子。
- -M
- 更新日時の古さを調べるファイルテスト演算子。
- -A
- 最終アクセス日時の古さを調べるファイルテスト演算子。
まだまだありますが、有名どころはこのぐらいだと思います。
ファイルテスト演算子を使用するときは、ファイルテスト演算子の後に、調べたいファイルパスを記述します。
ファイルテスト演算子の中には、結果が真か偽で返されるものもあれば、直接数値で返されるものなどさまざまです。
この中では、「-s」「-M」や「-A」などは真か偽ではなく、数値が帰ってきます。
それ以外のファイルテスト演算子であれば、基本的には if文 が使えます。
それでは実際に使ってみましょう。
Perl/CGIプログラミングにおけるファイルテスト演算子の使用例としてはこんな感じです。
#!/usr/bin/perl
use strict;
print "Content-type: text/html\n\n";
my $file = './test.txt';
if (-e $file) {
print 'File Check OK !';
} else {
print 'File Check Error !';
}
exit;
このPerl/CGIプログラムと同じフォルダ内に、「test.txt」というファイルを置いて実行させてみてください。
変数 「$file」で指定している同じフォルダ内に存在する「test.txt」を調べています。
指定したパスで、ファイルまたはフォルダが存在していれば、if文が成立します。
指定したパスに何も存在していなければ、else文が実行されます。
Perl/CGIプログラムでのファイルテスト演算子で指定するパスには、 絶対パスまたは相対パス が使えます。
ただし、URLでの指定はできないので注意しましょう。
上記のPerl/CGIプログラムでは、指定されたパスに対してひとつしかファイルテスト演算子を使用してないので、少しものたりないですよね。
まぁわかりやすいかもしれませんが…。
今度は、もう少しファイルテスト演算子を多用してみましょう。
例えばこんな感じです。
#!/usr/bin/perl
use strict;
print "Content-type: text/html\n\n";
my $file = './test.txt';
print "Path Check : ";
if (-e $file) {
print "OK<br>\n";
print "Target Type : ";
if (-f $file) {
print "File<br>\n";
print "File Read Check : ";
if (-r $file) {
print "OK<br>\n";
} else {
print "Error<br>\n";
}
print "File Write Check : ";
if (-w $file) {
print "OK<br>\n";
} else {
print "Error<br>\n";
}
} elsif (-d $file) {
print "Directory<br>\n";
} else {
print "Unknown<br>\n";
}
} else {
print "Error $file<br>\n";
}
exit;
最初に登場したPerl/CGIプログラムよりも、if関数が複雑になっているだけでやっていることは同じです。
先ほどと同じように、「test.txt」と同じフォルダ内に入れて、実行させてみてください。
Path Check : OK
Target Type : File
File Read Check : OK
File Write Check : OK
こんな感じで「test.txt」の状態を調べて表示してくれるはずです。
対象のファイルをもっと詳しく調べるには、「-t」で本当にテキストファイルかどうかを調べたり、「-s」でファイルサイズを求めたりするという手もあります。
ファイルテスト演算子については以上です。
それでは今回のPerl/CGIプログラミング学習にうつりましょう。
今回は、Perl/CGIプログラムにおいて重要なファイルロックについて学習していきます。
ファイルロックとは
ファイルロックとは、一言で言ってしまうと、お一人様限定の処理環境を構築するための仕組みのことです。
今後クラウドコンピューティングが主流になってくるので、このファイルロックという考え方は非常に大切になってきます。
ファイルロックの概要 については、すでに説明済みですからそちらを参照してください。
今回は、 Perl/CGIプログラム で構築できる3種類のファイルロック方式を紹介します。
<戻る>
symlink式ロック
最初に断っておきますが、 symlink関数 を使うことからこのように呼んでいるだけです。
Perl/CGIプログラミングの世界でこのような用語があるわけではないのでご注意ください。
symlink式ロックの概要
symlink式ロックとは、特定の場所に指定した名前のファイルが存在しているときを、ファイルロック状態だと定義しプログラム処理させるファイルロック方式のことです。
つまり、あらかじめ決められているパス上にすでにファイルが存在していれば、別のタイミングで動作したPerl/CGIプログラムが作成したものだということがわかります。
まさに今、別のタイミングで動作しているPerl/CGIプログラムが記録ファイルを操作している最中だということが推測できます。
別のタイミングで起動したPerl/CGIプログラムが記録ファイルを操作していることがわかれば、自分はしばらく記録ファイルへの書き込みを待てばよいだけですから簡単な話ですよね。
実は、かなり前にファイルロックをかけたままサーバーダウンなどで処理が止まってしまいそのまま…。
という可能性もあるといえばあるんですが、それはそれで回避する方法があるので、その対処法も組み込んでおけば問題ありません。
symlink式ロックの理屈はこのぐらいにして、Perl/CGIプログラミングの話に移りましょう。
symlink関数を使用すると、指定したパス上に、指定した名前のシンボリックリンク(ファイル)を作成することができます。
シンボリックリンクとは、Macだとエイリアス、Windowsだとショートカットにあたるファイルのことです。
このsymlink関数を利用して、ファイルロックを行うわけです。
symlink関数の特徴として、一度作成したシンボリックリンク(ファイル)と同名のファイルは作成できないという仕組みがあります。
普通にファイルを作成するだけであれば、 open関数 を使えばよいだけなのですが、open関数では、すでにファイルが作成されていたときとの見分けがつかなくなります。
open関数の前に、ファイルテスト演算子を使えば解決しますが、そうすると、Perl/CGIプログラム自体のステップ数(処理の量)が増えて、ファイルロックに時間がかかってしまいます。
このような理由から、symlink関数を利用するわけです。
では、外部ファイルに対して、Perl/CGIプログラムから何らかのデータを書き込むことを考えてみましょう。
1、まずは、ファイルロック状態を宣言しているファイルが、存在しているかどうかを調べます。
2、もし存在していたら、他のプログラムが記録ファイルの操作中だということなので、少し待ちます。
3、存在していなければ、記録ファイルへの操作を行っても良いということになります。
4、symlink関数で、ファイルロックを宣言するファイルを作成し、他のプログラムに邪魔されないようにします。
5、記録ファイルへの操作が終了したら、symlink関数で作成したファイルを削除し、ファイルロック状態を解除します。
ちなみに、ファイルを削除するときは、unlink関数を使用します。
このunlink関数が、symlink式ロックを解除するカギになるわけです。
symlink式ロックの例
それでは、Perl/CGIプログラムを使って、symlink式のファイルロックシステムを構築してみましょう。
今回は今までとは違い、Perl/CGIプログラムファイルひとつで解決するものではないので、少し準備が必要です。
外部ファイルの準備
まずは、Perl/CGIプログラムファイルから読み込むためのデータファイルを準備します。
用意するファイルはテキストファイルで、一行目には「この文字列が表示されればOKです。」と書いておきます。
行末に改行を入れても入れなくてもかまいません。
ファイル名は「file.txt」とします。
これで、Perl/CGIプログラムが読み取る外部ファイルの準備ができました。
ファイルロック用のフォルダの準備
次に、symlink関数で作成したファイルを格納するためのフォルダを作成します。
フォルダを作成し「lock」という名前を付けます。
Perl/CGIプログラムの準備
最後に、symlink式のファイルロックシステムをプログラミングした、Perl/CGIプログラムファイルを準備します。
#!/usr/bin/perl
use strict;
my $LockPath = &lock or die 'Lock Error !';
open (FILE, './file.txt') or die "File Open Error !";
my $message = <FILE>;
close (FILE);
&unlock($LockPath);
print "Content-type: text/html\n\n";
print $message;
sub lock {
my $LockPath = './lock/lock.lok';
my $LockRetry = 5;
my $LockWait = 30;
my $flag = 0;
if (-e $LockPath) {
my $LockTime = (stat($LockPath))[9];
&unlock($LockPath) if ($LockTime < time - $LockWait);
}
for (my $i=1;$i<=$LockRetry;$i++) {
$flag = symlink(".", $LockPath);
last if ($flag);
sleep 1;
}
if ($flag) {return $LockPath;}
else {return;}
}
sub unlock {
my $LockPath = shift;
unlink($LockPath);
}
exit;
設置するレンタルサーバーにあわせて、Perlのパスを書き換えてください。
ファイル名は何でもよいのですが、ここでは「symlink.cgi」という名前を付けることにします。
Perl/CGIプログラムの設置
今まで準備してきた「file.txt」「symlink.cgi」ファイルと、「lock」フォルダをわかりやすいように「symlink」フォルダ内にすべて格納しておいてください。
ここではsymlink式のファイルロックの解説ですが、この後、別のファイルロックも説明します。
単純に、ごちゃごちゃになると困るので、区別するためですね。
それでは準備しましたこれらのファイルをインターネットサーバーに設置しましょう。
FTPソフトのASCIIモードを使って、「symlink」フォルダごとインターネットサーバーにアップロードします。
Perl/CGIプログラムファイル「symlink.cgi」には、パーミッション設定で実行権を与えます。
「705」または「755」あたりが一般的ですね。
そして、「lock」フォルダには、読み取り権と書き込み権を与えるようにします。
「606」または「666」ですが、サーバーによってはパーミッションを変更しなくてもよい場合があります。
「644」あたりでも問題ない場合もあるので、もしうまくいかない場合はいろいろと試してみてください。
これでsymlink式のファイルロックシステムを搭載しましたPerl/CGIプログラムサンプルの動作準備ができました。
Perl/CGIプログラムの動作
それでは、「symlink.cgi」にアクセスしてみてください。
このPerl/CGIプログラムサンプルを実行すると、「file.txt」の一行目に書かれている文字列が表示されます。
このPerl/CGIプログラムサンプルの「file.txt」の一行目には…。
「この文字列が表示されればOKです。」
と書きましたよね。
したがって、表示される文字も「この文字列が表示されればOKです。」となるわけです。
Perl/CGIプログラム解説
Perl/CGIプログラムの処理の流れについてはとても簡単ですね。
指定した外部ファイル「file.txt」の先頭の1行を読み込んで、表示させているだけです。
外部ファイルをPerl/CGIプログラムで読み込む方法も、以前解説しましたopen関数やclose関数などを使用して普通に読み込んでいます。
ただ、今回は、ファイルロックについての解説なので、ファイル処理を行う前にはsymlink式でファイルロックをかけて、ファイル処理終了後はファイルロックを解除しています。
そして、ファイルロックは通常、ひとつのPerl/CGIプログラム内で何度も使用するものなので、それぞれを サブルーチン化(関数化) して何度も呼び出せるようにしています。
ファイルロックをかける関数を「lock関数」、ファイルロックを解除する関数を「unlock関数」としています。
lock関数
lock関数について解説します。
Perl/CGIプログラムサンプルでは以下の部分ですね。
sub lock {
my $LockPath = './lock/lock.lok';
my $LockRetry = 5;
my $LockWait = 30;
my $flag = 0;
if (-e $LockPath) {
my $LockTime = (stat($LockPath))[9];
&unlock($LockPath) if ($LockTime < time - $LockWait);
}
for (my $i=1;$i<=$LockRetry;$i++) {
$flag = symlink(".", $LockPath);
last if ($flag);
sleep 1;
}
if ($flag) {return $LockPath;}
else {return;}
}
lock関数は、symlink式のファイルロックをかける関数です。
しかもlock関数が呼び出されたとき、すでにファイルロックがかけられていた場合の処理も考慮したプログラミングをしています。
lock関数の引数は何も必要ありませんが、戻り値はファイルロックが成功したときと失敗したときで異なります。
ファイルロックが成功したときには、ファイルロックの宣言とも言える作成したファイルまでのパスを返し、失敗した場合には、戻り値は何もありません。
なので、Perl/CGIプログラムサンプルを見ていただければわかるように、lock関数を呼び出しつつ、その戻り値を「or」を使って評価しています。
my $LockPath = &lock or die 'Lock Error !';
という部分ですね。
もし、lock関数からの戻り値が何もない、つまり、ファイルロックに失敗した場合には、die関数が実行され、Perl/CGIプログラムが強制終了されるというわけです。
それではいよいよ、lock関数のプログラムの流れについて説明していきます。
lock関数の処理部分は、大きく3つの段階に分けることができます。
1、すでにファイルロックがかけられているかどうかを調査する段階。
2、ファイルロックをかける段階。
3、ファイルロックがかかったかどうかを調査する段階。
それぞれの段階について解説していきます。
すでにファイルロックがかけられているかどうかを調査する段階。
まずは、すでにファイルロックがかけられているかどうかを調査する段階についてです。
Perl/CGIプログラムサンプルでは以下の部分のことです。
if (-e $LockPath) {
my $LockTime = (stat($LockPath))[9];
&unlock($LockPath) if ($LockTime < time - $LockWait);
}
まず、ファイルテスト演算子を if文 で評価し、ファイルロックを宣言しているファイルが存在しているかどうかを調べます。
変数「$LockPath」には、ファイルロックが宣言されたことをあらわすファイルへのパスが格納されています。
もし、ファイルロックを宣言しているファイルが存在しない場合、つまり、まだ誰もファイルロックをかけていない場合には、この段階での処理は行われません。
逆に、ファイルロックを宣言しているファイルが存在する場合、つまり、誰かがファイルロックをかけた状態の場合には、if文以降が実行されます。
もし、ファイルロックを宣言しているファイルが存在している場合には、まず、そのファイルについて調査をします。
ファイルロックを宣言しているファイルに対してどんな調査をするのかというと、「いつファイルロックがかけられたのか?」についてです。
つまりここでは、前述しましたファイルロックをかけた瞬間に、何らかのトラブルが発生し、Perl/CGIプログラムが途中で止まったことを想定しているわけです。
では具体的にどうやって調査するのかというと、stat関数を使用します。
statとは、指定したファイルに対して、さまざまな情報を 配列形式 で返す関数です。
そして、stat関数で返される9番目の要素のみを変数「$LockTime」に代入しています。
Perl/CGIプログラムサンプルでは「my $LockTime = (stat($LockPath))[9];」という部分ですね。
返される配列の要素番号を指定することで、他の不必要な情報をキャッチしないようにする方法については、 localtime関数 から返される配列の受け取り方と同じですね。
そして次は、stat関数から返された情報を元に、ファイルロックを宣言しているファイルを評価します。
stat関数から返される9番目の要素が表す情報の種類つまり、変数「$LockTime」に格納されている情報とは、指定されたファイルが作成された時間データをtime関数形式で表したものです。
ということは、このPerl/CGIプログラムからもtime関数を呼び出して、その誤差を計算してやれば、ファイルロックを宣言しているファイルが、今からどれだけ前に作られたのかがわかるわけです。
つまり、今からどれだけ前に別のPerl/CGIプログラムがファイルロックをかけたのかがわかるわけです。
今からどれだけ前にファイルロックをかけたのかを調べつつ、必要であれば、ファイルロックを解除するunlock関数を呼び出しているのは、以下の部分です。
「&unlock($LockPath) if ($LockTime < time - $LockWait);」
現在の時間「time」から、ファイルロックの最大待ち時間(秒)「$LockWait」を引いた値が、ファイルロックを宣言しているファイルが作成された時間「$LockTime」よりも大きい場合…
ファイルロックを強制解除しています。
つまり、ファイルロックを宣言するファイルが作成されてから、最大待ち時間を超えている場合には、if文が成立し、unlock関数が呼び出されます。
このPerl/CGIプログラムのように、たとえ左辺でunlock関数を呼び出していても、右辺にif文の条件式がある場合には、if文が先に実行され、成立しない限り左辺は実行されません。
ファイルロックをかける段階
それではいよいよ、symlink式のファイルロックをかけているPerl/CGIプログラミング部分について解説します。
Perl/CGIサンプルプログラムでは以下の部分です。
for (my $i=1;$i<=$LockRetry;$i++) {
$flag = symlink(".", $LockPath);
last if ($flag);
sleep 1;
}
はじめに、 for関数 で変数「$LockRetry」で設定されている数の分だけループ処理(くりかえし処理)が行われるように設定します。
しかし、これは最大ループ回数を指定するもので、必ずforループで指定した数の分だけ処理が行われるわけではありません。
このあたりの理屈については、後述のPerl/CGIプログラムの流れを追っていけば理解できます。
それでは、forループの中では、どのような処理が行われているかを解説します。
symlink関数は、指定されたパス上に、シンボリックリンク(ファイル)を作成する関数です。
第1引数は、起点となる場所(シンボリックリンク元)を表しています。
Perl/CGIプログラムサンプルでは、「.」つまり、現在位置を起点(シンボリックリンク元)とするという意味になります。
ちなみに、MacPerlで使う場合は「":"」とします。
第2引数は、シンボリックリンクを作成したいパスを示します。
つまり、ここで作成しているのは、カレントディレクトリのシンボリックリンクです。
そして、symlink関数の戻り値は、symlink関数が正常に動作したときは「1」、正常に動作しなかったときには「0」が返されます。
つまり、ファイルロックの宣言となるファイルが作成できれば「1」が、そうでなければ「0」が返されるというわけです。
Perl/CGIサンプルプログラムでは、「$flag = symlink(".", $LockPath);」という部分です。
そして、変数「$flag」にはsymlink関数の動作結果が格納されるわけです。
今後は、このsymlink関数の動作結果が代入された変数「$flag」を評価していきます。
まずは、このままforループを続けるかどうかをif文で判断します。
Perl/CGIプログラムでは「last if ($flag);」という部分です。
last関数が実行されると、forループを抜けることができますが、後ろにif文が付いているので、後ろのif文が先に評価され、if文が成立していればlast関数が実行されます。
つまり、symlink関数で、ファイルロックの宣言を表すファイルが作成できれば、if文が成立し、forループを抜けることができるというわけです。
したがって、例えば1回目の処理で、if文が成立すれば、ループ処理を抜けることができます。
しかし、if文が成立しなかったときには、last関数は実行されませんから、その後のsleep関数の処理に入ります。
symlink関数でファイルを作成できない原因にはいろいろありますが…。
一番考えられるものとして、前の調査段階ですでに別のファイルが作成されているにもかかわらず、ファイルロックの待ち時間以内だったため、ファイルロックを強制解除できなかったことが考えられます。
つまり、わかりやすく言うと…。
ファイルロックの最大待ち時間を表す「$LockWait」で指定されている30秒以内だったために、if文が成立せず、unlock関数が呼び出せなかった場合です。
そんなときは、if文が成り立たず、次のsleep関数が実行されます。
sleep関数は、指定された数値分だけの秒数、Perl/CGIプログラムを一時停止させます。
仮に「sleep 2;」とすると、2秒間一時停止します。
「sleep 1;」とすることで、1秒このPerl/CGIプログラムを一時停止させつつ、他のプログラムがファイル処理を実行し、ファイルロックを解除してくれるのを待つわけです。
そして、このforループを抜けるときというのは…。
symlink関数でのファイル作成が成功し、if文が成立し、last関数により追い出された場合。
symlink関数でのファイル作成が失敗し続けたため、for関数のループ指定回数をすべて使い切った場合。
この2つです。
ここまででは、本当にsymlink関数が動作し、ファイル作成が行われ、ファイルロックを宣言したかどうかはわかりませんよね。
したがって、次の段階で、ファイルが作成されたかどうかを調べます。
ファイルロックがかかったかどうかを調査する段階
ここでは、本当にファイルが作成されたか、つまり、ファイルロックに成功したかどうかを評価します。
Perl/CGIプログラムサンプルでは以下の部分です。
if ($flag) {return $LockPath;}
else {return;}
ここでは、先ほどforループを抜けた条件と同じ条件でif文を作成し、forループを抜けた原因を調べています。
if文が成立、つまり、変数「$flag」に1が代入されていて、ファイルロックを宣言するファイルが作成されていれば、symlink関数で作成したファイルパスを返してlock関数終了です。
if文が成立しなかった場合には、else以降が実行され、戻り値は何もないままlock関数終了です。
これで、lock関数の解説は以上です。
unlock関数
unlock関数について解説します。
Perl/CGIプログラムサンプルでは以下の部分ですね。
sub unlock {
my $LockPath = shift;
unlink($LockPath);
}
unlock関数は、ファイルロックを解除する関数です。
引数には、lock関数で作成しましたファイルロックの宣言を表すファイルパスを渡します。
戻り値は何もありません。
symlink関数で作成されたファイルを削除するには、unlink関数を使用します。
unlink関数の引数には、symlink関数で作成しましたファイルパスを指定します。
unlock関数の解説は以上です。
注意事項
symlink式のファイルロックを使用する上での注意点について解説します。
symlink式ロックでは、symlink関数を使用するため、Perl/CGIプログラムを動作させる環境によっては、うまく機能しない場合があります。
ここを理解しておかないと、インターネットサーバーによっては、Perl/CGIプログラム自体が使い物にならなくなる可能性があります。
symlink関数が使える環境としては「UNIX(Mac OS Xを含む) MacPerl(OS9までのMac用Perl) シンボリックリンクに対応したWindowsのCygwinのPerl」などがあります。
symlink関数が使えない環境としては「一部のUNIX WindowsのActivePerl」などがあります。
symlink関数が使えないことを考慮し、Perl/CGIプログラムの中には、インストール段階で、symlink関数の動作をテストし、ファイルロック方式を決めているものもあります。
mkdir式ロック
これも前述のsymlink式ロックと同じように、 mkdir関数 を使うことからこのように呼んでいるだけです。
Perl/CGIプログラミングの世界でこのような用語があるわけではないのでご注意ください。
mkdir式ロックの概要
mkdir式ロックとは、特定の場所に指定した名前のフォルダが存在しているときを、ファイルロック状態だと定義し処理させる方式のことです。
mkdir関数を使用すると、指定したパス上に、指定した名前のフォルダを作成することができます。
symlink式ロックがファイルの有無を基準にしているのに対し、mkdir式ロックではフォルダの有無が基準になっているだけの話です。
さらに、symlink関数同様、mkdir関数も同じ名前のフォルダを作成することができないので、ファイルロックシステムにぴったりです。
それぞれの関数自体の使用方法は異なりますが、大きなプログラムの流れは同じです。
ちなみに、フォルダを削除するときは、rmdir関数を使用します。
mkdir式ロックの例
それでは、Perl/CGIプログラムを使って、mkdir式のファイルロックシステムを構築してみましょう。
外部ファイルの準備
まずは、Perl/CGIプログラムファイルから読み込むためのデータファイルを準備します。
symlink式のファイルロックシステムで作成しましたテキストファイル、「file.txt」をそのまま使っていただいてかまいません。
一行目に「この文字列が表示されればOKです。」と書いてあるだけのテキストファイルです。
行末に改行を入れても入れなくてもかまいません。
これで、Perl/CGIプログラムが読み取る外部ファイルの準備ができました。
ファイルロック用のフォルダの準備
次に、mkdir関数で作成したフォルダを格納するためのフォルダを作成します。
フォルダを作成し「lock」という名前を付けます。
これも、symlink式ロックで使用したものがそのまま使えます。
Perl/CGIプログラムの準備
最後に、mkdir式のファイルロックシステムをプログラミングした、Perl/CGIプログラムファイルを準備します。
#!/usr/bin/perl
use strict;
my $LockPath = &lock or die 'Lock Error !';
open (FILE, './file.txt') or die "File Open Error !";
my $message = <FILE>;
close (FILE);
&unlock($LockPath);
print "Content-type: text/html\n\n";
print $message;
sub lock {
my $LockPath = './lock/lock.lok';
my $LockRetry = 5;
my $LockWait = 30;
my $flag = 0;
if (-e $LockPath) {
my $LockTime = (stat($LockPath))[9];
&unlock($LockPath) if ($LockTime < time - $LockWait);
}
for (my $i=1;$i<=$LockRetry;$i++) {
$flag = mkdir($LockPath, 0755);
last if ($flag);
sleep 1;
}
if ($flag) {return $LockPath;}
else {return;}
}
sub unlock {
my $LockPath = shift;
rmdir($LockPath);
}
exit;
設置するレンタルサーバーにあわせて、Perlのパスを書き換えてください。
ファイル名は何でもよいのですが、ここでは「mkdir.cgi」という名前を付けることにします。
Perl/CGIプログラムの設置
今まで準備してきました「file.txt」「mkdir.cgi」ファイルと、「lock」フォルダをわかりやすいように「mkdir」フォルダ内にすべて格納しておいてください。
前述のsymlink式ロックのPerl/CGIプログラムと交じっても困るので。
それでは準備しましたこれらのファイルをインターネットサーバーに設置してみましょう。
symlink式ロックのPerl/CGIプログラムのときと同様に、「mkdir」フォルダごとインターネットサーバーにアップロードします。
Perl/CGIプログラムサンプル「mkdir.cgi」には実行権を、「lock」フォルダには読み取り権と書き込み権を与えます。
これも前述のsymlink式ロックのときと同じですね。
Perl/CGIプログラムの動作
それでは、「mkdir.cgi」にアクセスしてみてください。
このPerl/CGIプログラムサンプルを実行すると、「file.txt」の一行目に書かれている文字列が表示されます。
これも前述のsymlink式ロックのときと同じですね。
Perl/CGIプログラム解説
ここで作成しましたPerl/CGIプログラムサンプルは、前述のsymlink式ロックのサンプルプログラムとほとんど同じものです。
違う部分は、symlink関数がmkdir関数に、unlink関数がrmdir関数に変わっただけで、あとはすべて同じです。
しかも、unlink関数とrmdir関数の使い方は同じですから、ここではmkdir関数の解説のみをします。
mkdir関数は、指定したパス上にフォルダを作成する関数です。
使い方は、「mkdir(作成するフォルダ名までのパス, パーミッション値);」という感じに使います。
mkdir関数もsymlink関数と同様に、処理に成功すれば1が、失敗すれば0が返されます。
そして、パーミッション値は8進数で指定することがルールになっています。
Perlプログラミングで8進数を表すには、頭に0を付けますので「0755」などになるわけです。
Perl/CGIプログラムサンプルでは、「$flag = mkdir($LockPath, 0755);」という感じで使っています。
mkdir式のファイルロックシステムの解説は以上です。
<戻る>
rename式ロック
もうわかっているとは思いますが、 rename関数 を使うことからこのように呼んでいるだけです。
Perl/CGIプログラミングの世界でこのような用語があるわけではないのでご注意ください。
rename式ロックの概要
rename式ロックとは、特定のファイルまたはフォルダの名前を変更することにより、ファイルロック状態を宣言するというファイルロック方式のことです。
rename関数を使用すると、名前の変更を行うことができます。
それでは、rename式ロックについて解説します。
まずは、ファイルロックの状態を切り替えるためのファイルまたはフォルダを用意します。
このとき、プログラム側にも、基準となるファイルまたはフォルダの名前(変更する前の名前)を設定しておきます。
そして、記録ファイルを操作する段階になったら、基準となるファイルまたはフォルダ名の存在を調べます。
もし、他のプログラムがすでに、ファイルロックをかけている…。
つまり、基準となるファイルまたはフォルダ名を変更してしまった場合には、基準となるファイルまたはフォルダが存在していませんから、少し待つようにします。
その後、他のプログラムがファイルロックを解除すると、基準となるファイルまたはフォルダが出現することになるので…。
今度は、こちらがrename関数を使用して、基準となるファイルまたはフォルダ名を変更するようにすれば、ファイルロックをかけることができます。
記録ファイルへの操作が終了したら、再びrename関数を使用して、基準となるファイルまたはフォルダ名を、元に戻してファイルロック状態を解除します。
このようにすれば、前述の2種類のファイルロック方式とは異なりますが、同じように、記録ファイルをひとつのプログラムが独占して操作できる環境を構築できます。
rename式ロックの例
それでは、Perl/CGIプログラムを使って、rename式のファイルロックシステムを構築してみましょう。
外部ファイルの準備
まずは、Perl/CGIプログラムファイルから読み込むためのデータファイルを準備します。
symlink式やmkdir式のファイルロックシステムで作成しましたテキストファイル、「file.txt」をそのまま使っていただいてかまいません。
一行目に「この文字列が表示されればOKです。」と書いてあるだけのテキストファイルです。
行末に改行を入れても入れなくてもかまいません。
これで、Perl/CGIプログラムが読み取る外部ファイルの準備ができました。
ファイルロック用のファイルの準備
ファイルロックの宣言を表すファイル、つまり、rename関数で名前を変更するファイルを準備します。
前述のsymlink式やmkdir式のファイルロックの際に準備しました「lock」フォルダ内に、「lock.lok」というファイルを作成します。
Perl/CGIプログラムの準備
最後に、mkdir式のファイルロックシステムをプログラミングした、Perl/CGIプログラムファイルを準備します。
#!/usr/bin/perl
use strict;
my ($LockPath, $NewLockPath) = &lock or die 'Lock Error !';
open (FILE, './file.txt') or die "File Open Error !";
my $message = <FILE>;
close (FILE);
&unlock($LockPath, $NewLockPath);
print "Content-type: text/html\n\n";
print $message;
sub lock {
my $LockFile = 'lock.lok';
my $LockDirectory = './lock/';
my $LockPath = $LockDirectory . $LockFile;
my $LockRetry = 5;
my $LockWait = 30;
for (my $i=1;$i<=$LockRetry;$i++) {
my $NewLockPath = $LockPath . time;
return ($LockPath, $NewLockPath) if (rename($LockPath, $NewLockPath));
sleep 1;
}
opendir(DIRECTORY, $LockDirectory);
my @filelist = readdir(DIRECTORY);
closedir(DIRECTORY);
foreach (@filelist) {
if (/^$LockFile(\d+)/) {
if (time - $1 > $LockWait) {
my $NewLockPath = $LockPath . time;
return ($LockPath, $NewLockPath) if (rename($LockDirectory . $_, $NewLockPath));
}
}
}
return;
}
sub unlock {
my ($LockPath, $NewLockPath) = @_;
rename($NewLockPath, $LockPath);
}
exit;
設置するレンタルサーバーにあわせて、Perlのパスを書き換えてください。
ファイル名は何でもよいのですが、ここでは「rename.cgi」という名前を付けることにします。
Perl/CGIプログラムの設置
今まで準備してきました「file.txt」「rename.cgi」「lock.lok」ファイルと、「lock」フォルダをわかりやすいように「rename」フォルダ内にすべて格納しておいてください。
前述のsymlink式やmkdir式ロックのPerl/CGIプログラムと交じっても困りますからね。
それでは準備しましたこれらのファイルをインターネットサーバーに設置してみましょう。
symlink式やmkdir式ロックのPerl/CGIプログラムのときと同様に、「rename」フォルダごとインターネットサーバーにアップロードします。
Perl/CGIプログラムサンプル「rename.cgi」には実行権を、「lock」フォルダと「lock.lok」ファイルには読み取り権と書き込み権を与えます。
Perl/CGIプログラムの動作
それでは、「rename.cgi」にアクセスしてみてください。
このPerl/CGIプログラムサンプルを実行すると、「file.txt」の一行目に書かれている文字列が表示されます。
これも前述のsymlink式やmkdir式ロックのときと同じですね。
Perl/CGIプログラム解説
それでは、rename式のファイルロックシステムのPerl/CGIプログラミング方法について解説します。
今回も、rename式のファイルロックをかける関数をlock関数、逆にファイルロックを解除する関数をunlock関数としています。
lock関数
Perl/CGIプログラムサンプルのlock関数について解説します。
lock関数は、rename関数を使って、基準となるファイル名を変更することにより、ファイルロック状態を宣言する関数です。
引数は何もありませんが、戻り値はファイル名を変更する前のパスと後のパスです。
しかし、ファイル名の変更に失敗した場合には、戻り値は何もありません。
さらに、ファイル名を変更した後、つまり、ファイルロックをかけた後に何らかのトラブルで、Perl/CGIプログラムが停止したときのことも考慮しています。
Perl/CGIサンプルプログラムでは以下の部分です。
sub lock {
my $LockFile = 'lock.lok';
my $LockDirectory = './lock/';
my $LockPath = $LockDirectory . $LockFile;
my $LockRetry = 5;
my $LockWait = 30;
for (my $i=1;$i<=$LockRetry;$i++) {
my $NewLockPath = $LockPath . time;
return ($LockPath, $NewLockPath) if (rename($LockPath, $NewLockPath));
sleep 1;
}
opendir(DIRECTORY, $LockDirectory);
my @filelist = readdir(DIRECTORY);
closedir(DIRECTORY);
foreach (@filelist) {
if (/^$LockFile(\d+)/) {
if (time - $1 > $LockWait) {
my $NewLockPath = $LockPath . time;
return ($LockPath, $NewLockPath) if (rename($LockDirectory . $_, $NewLockPath));
}
}
}
return;
}
lock関数には、大きく分けて2つの役割があります。
1つ目は、基準となるパスを元に、ファイル名を変更する。
2つ目は、基準となるパスがない場合に、基準となるファイルを探し出してファイル名を変更する。
1つ目は、純粋に指定されたパスを元に、rename関数を使ってファイル名を変更します。
通常は、このプロセスで問題なくファイルロックをかけることができます。
しかし、万が一というケースを考慮し、2つ目の処理も用意しておきます。
2つ目の処理では、ファイル名が変更されっぱなしだった場合のリカバー処理をしています。
つまり、前回Perl/CGIプログラムが走ったときに、ファイルロックをかけた後、何らかのトラブルで、ファイルロックを解除できなかった場合を想定した処理ですね。
それでは、1つ目の処理を「通常処理」、2つ目の処理を「リカバー処理」として解説していきます。
通常処理
まずは、指定されているパスを元に、rename関数を使って、ファイル名を変更するプロセスです。
for (my $i=1;$i<=$LockRetry;$i++) {
my $NewLockPath = $LockPath . time;
return ($LockPath, $NewLockPath) if (rename($LockPath, $NewLockPath));
sleep 1;
}
基本的なプログラムの流れは、前述のsymlink式やmkdir式のファイルロック方法と同じです。
for関数 を使って、変数「$LockRetry」の指定回数分だけループ処理を設定します。
しかし、このforループは最大処理回数を指定するもので、rename関数が正常に働き、ファイル名を変更できれば1回で抜けることができます。
Perl/CGIプログラムを見ていただければわかるように、ファイル名の変更後の名前は、指定されているファイル名プラス、time関数の戻り値としています。
なぜこうしたのか?
理由は簡単です。
この名前の付け方であればいつファイル名を変更したか、つまり、いつファイルロックをかけたのかが素早くわかるからです。
rename関数の使い方も、Perl/CGIプログラムサンプルを見ていただければ、だいたい推測できると思いますが、ねんのために解説しておきます。
rename関数は、「rename(変更対象のパス, 変更後のパス);」という感じで使います。
そして、rename関数が正常に動作したかどうかを、if文を使って評価しています。
if文 が成立、つまり、rename関数でファイル名の変更に成功したら、return関数が実行され、lock関数終了です。
逆に、if文が成立しない、つまり、rename関数でのファイル名変更に失敗した場合には、sleep関数で1秒停止させられた後に、再度ファイル名変更に挑戦します。
以上が通常処理になります。
リカバー処理
ここでは、前述の通常処理でファイル名を変更できなかった場合のリカバー処理をします。
まずは、なぜ前述の通常処理でファイル名が変更できなかったのかについて考えてみましょう。
まぁ、初期設定ミスとかファイルの破損など、細かい原因を考え出したらきりがありませんが、最も可能性のあるものとして、ファイル名を変更したいファイルが存在しないというのがあります。
つまり、すでにファイルロックがかかってしまっている状態ですね。
そして、すでに、ファイルロックがかかってしまっている状態には2種類あります。
1つ目は、まさに今、別のPerl/CGIプログラムがファイル処理の真っ最中だという場合。
2つ目は、前回Perl/CGIプログラムが走ったときに、ファイルロックをかけたまま、とまってしまった場合。
この2つのパターンが考えられるわけです。
1つ目であれば問題ありません。
なぜなら、本当にサーバーが混雑している場合、前述のsleep関数で数秒プログラムを一時停止させていればそのうち順番が回ってくるからです。
しかし、2つ目であれば問題です。
なぜなら、何らかの対策を打たない限り、永久にファイルロックがかかったままになってしまうからです。
したがって、ファイルロックがかかりっぱなしになっている原因が、1であろうと2であろうと、まずは基準となるファイルを探し出して、調査する必要があるわけです。
ということで、基準となるファイルを探しにいきましょう。
まずは、基準となるファイルが格納されているフォルダを調べて、ファイル名のリストを取得します。
opendir(DIRECTORY, $LockDirectory);
my @filelist = readdir(DIRECTORY);
closedir(DIRECTORY);
opendir関数を使って、変数「$LockDirectory」で指定されているフォルダを開き、「DIRECTORY」というハンドル名と関連付けます。
次に、readdir関数でフォルダ内のファイルやフォルダの一覧を、配列「@filelist」に代入します。
最後にclosedir関数でフォルダを閉じます。
このあたりの処理は、 ファイル処理 と似ていますね。
これで、基準となるファイルが格納されているフォルダ内のファイルとフォルダのリストを、配列「@filelist」に代入することができました。
次は、このリストの中から、基準となるファイル名を探します。
foreach (@filelist) {
if (/^$LockFile(\d+)/) {
if (time - $1 > $LockWait) {
my $NewLockPath = $LockPath . time;
return ($LockPath, $NewLockPath) if (rename($LockDirectory . $_, $NewLockPath));
}
}
}
foreachループで配列「@filelist」の要素をひとつずつ取り出し、名前の変更された基準となるファイル名をif文とパターンマッチを使って探します。
基準となるファイルの名前の変更ルールは、前述のとおりです。
したがって、ファイル名の後にtime関数で取得した数値が付いたものを探せばよいわけです。
例えばPerl/CGIサンプルプログラムでは、基準となるファイル名が「lock.lok」となっています。
この後ろにtime関数から返された値が付きますから、「lock.lok1234…」などという感じになっているわけです。
この基準となるファイル名プラス、数値の パターンマッチ を構築すればよいわけです。
さらに、後で必要になってくるので後ろに付けられた数値部分を抽出しておきます。
なので式は「/^$LockFile(\d+)/」という感じになります。
^とは、対象となる文字列の先頭からマッチさせるという意味です。
\d+とは、数値を表します。
さらに、(\d+)とすることにより、数値部分、つまり、time関数から返された時間データを特殊変数$1に代入させています。
つまり、特殊変数$1に格納された値とは、前回ファイルロックがかけられた時間データを表すわけです。
次に、現在time関数から返された時間データから、特殊変数$1に代入されている前回ファイルロックをかけられた時間データを引きます。
これにより、何秒ファイルロックがかかりっぱなしになっていたかがわかります。
最後にファイルロックがかかりっぱなしになっていた時間と、変数「$LockWait」に指定されているファイルロックの最大待ち時間(秒)を比較します。
Perl/CGIサンプルプログラムでは「time - $1 > $LockWait」という部分です。
もし、ファイルロックの最大待ち時間を超えているようなら、if文が成立し、強制的に名前の変更(ファイルロック)がかけなおされます。
前回のPerl/CGIプログラム動作時に、ファイルロックがかかったまま、何らかのトラブルがあり、ファイルロックが解除されていないという判断を受けたわけですね。
その後は、return関数で、lock関数を抜けます。
逆に、ファイルロックの最大待ち時間をまだ超えていなかった場合には、何も処理は行われません。
そして、このforeachループ終了後まで処理が残ってしまった場合というのは、まだファイルロックの待ち時間内で、このPerl/CGIプログラムからはファイル処理ができない状態を表します。
そんなときには、戻り値を何も返さずlock関数を終了させます。
戻り値が何もないままlock関数を終了した場合は、「or」以降のdie関数が実行され、Perl/CGIプログラムが、とまるようになっています。
lock関数の解説は以上です。
unlock関数
unlock関数について解説します。
unlock関数は、lock関数でかけられたファイルロックを解除する関数です。
引数は、最初に指定されていたファイル名と、変更後のファイル名です。
戻り値は何もありません。
Perl/CGIプログラムサンプルでは以下の部分です。
sub unlock {
my ($LockPath, $NewLockPath) = @_;
rename($NewLockPath, $LockPath);
}
rename関数で再び名前の変更を行い、ファイル名を戻しています。
unlock関数の解説は以上です。
編集後記
最後に、rename式のファイルロックについて、補足説明しておきます。
rename式のファイルロックで変更するファイル名には、かならず今回のようなものでなくてはいけないというルールはありません。
もっと効率的なやり方があれば、そちらを採用すべきです。
あと、今回は、ファイルロック状態を宣言する基準のファイルを用意しましたが、rename式のファイルロックの場合は、記録ファイル名そのものを変更するという手もあります。
記録ファイルの名前を変更する方式のほうが、ファイル数が少なくなってよいのかもしれません。
どちらにしろ、あなたのこのみにあったファイルロック方式を使ってみてください。
今回の学習は以上です。
ありがとうございました。
<戻る>