【しばらく編集不可モードで運営します】 編集(管理者用) | 差分 | 新規作成 | 一覧 | RSS | FrontPage | 検索 | 更新履歴

Yuki::Lock::Mkdir - YukiWikiLock用mkdir版データベースロックモジュール

目次

YukiWikiLock用mkdir版データベースロックモジュール

極悪さんのrename版ロック機能付きYukiWikiDB、YukiWikiLockに対抗(笑)して作りました。

とりあえず、ロック機能部分だけをモジュールにしてみました。後はこのモジュールを持つ、YukiWikiDBを継承(/包含/実装/委任)したロック機能付きデータベースを作るだけですね。<-だったらさっさと作れ(笑)。

Mkdir.pm

ソースコード

 package Yuki::Lock::Mkdir;
 use strict;
 require Exporter;
 use vars qw($VERSION @ISA @EXPORT);
 $VERSION = 0.001;
 @ISA = qw(Exporter);
 @EXPORT = qw(Nop Shared Exclusive NonBlock ForceUnlock NewLock);
 
 sub Nop         {0}
 sub Shared      {1}
 sub Exclusive   {2}
 sub NonBlock    {4}
 sub ForceUnlock {8}
 sub NewLock {return new Yuki::Lock::Mkdir @_}
 
 DESTROY {
     my ($self) = @_;
     $self->unlock;
 }
 
 sub new {
     my ($class, $dir, $mode, %params) = @_;
     $mode ||= Nop;
     my $self = {
         dir      => undef,
         mode     => Nop,
         nonblock => $mode & NonBlock,
         exp_sh   => $params{exp_sh}  || ".lock_sh",
         exp_ex   => $params{exp_ex}  || ".lock_ex",
         trynum   => $params{trynum}  || 9,
         trytime  => $params{trytime} || 4,
         timeout  => $params{timeout} || 20,
         uniqid   => undef,
         errmsg   => [],
     };
     bless $self, $class;
     $self->setdir($dir) or return $self;
     $mode &= ~NonBlock;
     if ($mode == Nop) {
         ;;;
     } elsif ($mode == Shared) {
         $self->shared;
     } elsif ($mode == Exclusive) {
         $self->exclusive;
     } elsif ($mode == ForceUnlock) {
         $self->force_unlock;
     } elsif ($mode == ForceUnlock | Shared) {
         $self->force_shared;
     } elsif ($mode == ForceUnlock | Exclusive) {
         $self->force_exclusive;
     } else {
         push @{$self->{errmsg}}, "new: mode $mode: unknown.";
     }
     return $self;
 }
 
 sub setdir {
     my ($self, $dir) = @_;
     unless ($self->{mode} == Nop) {
         push @{$self->{errmsg}}, "setdir: now locking.";
         return 0;
     }
     $dir =~ s|/$||;
     unless ($dir) {
         push @{$self->{errmsg}}, "setdir: parameter error.";
         return 0;
     }
     unless (mkdir $dir or -d $dir) {
         push @{$self->{errmsg}}, "setdir: mkdir $dir: $!.";
         return 0;
     }
     unless (-w "$dir/..") {
         push @{$self->{errmsg}}, "setdir: not writable parent directory.";
         return 0;
     }
     $self->{dir} = $dir;
     return 1;
 }
 
 sub unlock {
     my ($self) = @_;
     my $mode = $self->{mode};
     unless ($self->{dir} and $self->{exp_sh} and $self->{exp_ex}) {
         push @{$self->{errmsg}}, "unlock: not set directory.";
         return 0;
     }
     my $shlock = $self->{dir}.$self->{exp_sh};
     my $exlock = $self->{dir}.$self->{exp_ex};
     my $timeout = $self->{timeout}/(24*60*60);
     if ($mode == Shared) {
         rmdir $shlock."/".$self->{uniqid};
         rmdir $shlock;
     } elsif ($mode == Exclusive) {
         unlink $shlock;
         rmdir $exlock;
     }
     $self->{mode} = Nop;
     if (opendir DIR, $shlock) {
         foreach (map "$shlock/$_", grep !/^\.\.?$/, readdir DIR) {
             unlink and next;
             -M > $timeout and rmdir;
         }
         closedir DIR;
         rmdir $shlock;
     }
     if (-e $exlock and -M _ > $timeout) {
         unlink $shlock;
         rmdir $exlock;
     }
     return 1;
 }
 
 sub shared {
     my ($self) = @_;
     my $phase = "shared";
     my $mode = $self->{mode};
     unless ($self->{dir} and $self->{exp_sh} and $self->{exp_ex}) {
         push @{$self->{errmsg}}, "$phase: not set directory.";
         return 0;
     }
     my $shlock = $self->{dir}.$self->{exp_sh};
     my $exlock = $self->{dir}.$self->{exp_ex};
     $mode == Shared and $self->{uniqid} and -d $shlock."/".$self->{uniqid}
         and return 1;                           # すでにロックされている
     my $err;
     for (my $count=0; $count < $self->{trytime}; $count++) {
         # 最初に、排他ロックのチェックとそのブロックを行なう。
         unless (mkdir $exlock) {
             $err = "mkdir $exlock: $!";
             # 排他ロックの可能性あり。チェックする。
             if (-d $exlock) {
                 # $^T = time;
                 my $locktime = (-M $exlock)*(24*60*60);
                 if (-f $shlock) {
                     # 排他ロックされている。
                     if ($mode == Exclusive) {
                         # ロック移行:排他ロックしているのは自分だった。
                         $phase = "shared:sh->ex";
                     } elsif ($locktime > $self->{timeout}) {
                         # 強制解除:排他ロックはタイムアウトしている。
                         $phase = "shared:force";
                         push @{$self->{errmsg}},
                           "$phase: found timeout exclusive lock."; 
                     } else {
                         # 排他ロックは有効である。
                         undef $err;
                         if ($self->{nonblock}) {
                             push @{$self->{errmsg}},
                               "$phase: found exclusive lock, none blocking.";
                             return 0;
                         } else {
                             # 待ってみる。
                             sleep 1;
                             next;
                         }
                     }
                 } else {
                     # 排他ロックではない。
                     $mode = Nop;
                 }
                 # ここに来た場合、排他ロックは有効ではない。
                 if (-e _ and not -d _) {
                     unless (unlink $shlock) {
                         # あるのに消えない。仕方ないのでエラーとする。
                         $mode = Nop;
                         push @{$self->{errmsg}},
                           "$phase: unlink $shlock: $!.";
                         rmdir $exlock;
                         return 0;
                     }
                 }
                 # 準備はできた。
             } elsif (-e _) {
                 # 異常である。復旧を試みる。
                 unless (unlink $exlock) {
                     # あるのに消えない。仕方ないのでエラーとする。
                     $mode = Nop;
                     $phase = "shared:force";
                     push @{$self->{errmsg}},
                       "$phase: unlink $exlock: $!.";
                     return 0;
                 }
                 # 復旧に成功。
             } else {
                 # おや?もう一度試してみる。
                 next;
             }
             undef $err;
         }
         unless (mkdir $shlock or -d $shlock) {
             # ここまで来て作れないなら終了させる。
             push @{$self->{errmsg}},
               "$phase: mkdir $shlock: $!.";
             if ($mode == Exclusive) {
                 # 元に戻す。
                 if (open LOCK, ">$shlock") {
                     close LOCK;
                     push @{$self->{errmsg}},
                       "$phase: stay exclusive mode.";
                     return 0;
                 } else {
                     # 元に戻せない!!
                     $mode = Nop;
                     push @{$self->{errmsg}},
                       "$phase: create $shlock: $!, unlock!!"; 
                 }
             }
             rmdir $exlock;
             return 0;
         }
         my $uniqid = time.("00000$$" =~ /(.{5})$/)[0];
         foreach my $i (0 .. $self->{trynum}) {
             mkdir $shlock;                      # 念のため。
             mkdir "$shlock/$uniqid$i" or next;
             $self->{uniqid} = $uniqid.$i;
             $self->{mode} = Shared;
             rmdir $exlock;
             return 1;
         }
         push @{$self->{errmsg}}, "$phase: mkdir $shlock/${uniqid}00: $!.";
         rmdir $shlock;
         if ($mode == Exclusive) {
             # 元に戻す。
             if (open LOCK, ">$shlock") {
                 close LOCK;
                 push @{$self->{errmsg}},
                   "$phase: stay exclusive mode.";
                 return 0;
             } else {
                 # 元に戻せない!!
                 $mode = Nop;
                 push @{$self->{errmsg}},
                   "$phase: create $shlock: $!, unlock!!"; 
             }
         }
         rmdir $exlock;
         return 0;
     }
     if ($err) {
         push @{$self->{errmsg}}, "$phase: $err.";
     } else {
         push @{$self->{errmsg}}, "$phase: timeout.";
     }
     return 0;
 print $phase."\n";
 if (-e $phase) {
   print -M _."\n";
 } else {
   print "none\n";
 }
 print $phase."\n";
 }
 
 sub exclusive {
     my ($self) = @_;
     my $mode = $self->{mode};
     unless ($self->{dir} and $self->{exp_sh} and $self->{exp_ex}) {
         push @{$self->{errmsg}}, "exclusive: not set directory.";
         return 0;
     }
     my $shlock = $self->{dir}.$self->{exp_sh};
     my $exlock = $self->{dir}.$self->{exp_ex};
     $mode == Exclusive and -d $exlock and -f $shlock and return 1;
     my $err;
     for (my $count=0; $count < $self->{trytime}; $count++) {
         # 最初に、排他ロックのチェックとそのブロックを行なう。
         unless (mkdir $exlock) {
             $err = "mkdir $exlock: $!";
             # 排他ロックの可能性あり。チェックする。
             if (-d $exlock) {
                 # $^T = time;
                 my $locktime = (-M $exlock)*(24*60*60);
                 if (-f $shlock) {
                     # 排他ロックされている。
                     if ($locktime > $self->{timeout}) {
                         # タイムアウトしている。
                         push @{$self->{errmsg}},
                           "exclusive: found timeout exclusive lock.";
                         rmdir $exlock;
                         next;
                     } else {
                         # 排他ロックは有効である。
                         undef $err;
                         if ($self->{nonblock}) {
                             push @{$self->{errmsg}},
                               "exclusive: found exclusive lock, none blocking.";
                             $self->{mode} = Nop;
                             return 0;
                         } else {
                             # 待ってみる。
                             sleep 1;
                             next;
                         }
                     }
                 } elsif (-d _) {
                     # 共有ロック作業中かもしれない。
                     if ($locktime > $self->{timeout}) {
                         # タイムアウトしている。
                         push @{$self->{errmsg}},
                           "exclusive: found timeout temporary shared lock.";
                         rmdir $exlock;
                         next;
                     } else {
                         # 有効である可能性が高い。
                         undef $err;
                         if ($self->{nonblock}) {
                             push @{$self->{errmsg}},
                               "exclusive: found shared lock, none blocking.";
                             return 0;
                         } else {
                             sleep 1;
                             next;
                         }
                     }
                 }
             } elsif (-e _) {
                 # 異常である。復旧を試みる。
                 unless (unlink $exlock) {
                     # あるのに消えない。仕方ないのでエラーとする。
                     push @{$self->{errmsg}},
                       "exclusive: unlink $exlock: $!.";
                     return 0;
                 }
             }
             next;
         }
         # 第一段階成功。共有ロックのブロックをする。
         if ($mode == Shared) {
             # 解除する
             rmdir $shlock."/".$self->{uniqid};
         }
         # タイムアウトした共有ロックがあれば消す。
         if (opendir DIR, $shlock) {
             foreach (map "$shlock/$_", grep !/^\.\.?$/, readdir DIR) {
                 -M > $self->{timeout}/(24*60*60) and rmdir;
             }
             closedir DIR;
         }
         rmdir $shlock;
         # ブロックする。
         if (open LOCK, ">$shlock") {
             close LOCK;
             $self->{mode} = Exclusive;
             return 1;
         } else {
             # 共有ロック中だった。
             if ($self->{nonblock}) {
                 push @{$self->{errmsg}},
                   "exclusive: found shared lock, none blocking.";
                 if ($mode == Shared) {
                     mkdir $shlock;
                     if (mkdir $shlock."/".$self->{uniqid}) {
                         push @{$self->{errmsg}}, "exclusive: stay shared mode.";
                     } else {
                         $mode = Nop;
                         push @{$self->{errmsg}},
                           "exclusive: mkdir $shlock/$self->{uniqid}: $!, unlock!!"; 
                     }
                 }
                 rmdir $exlock;
                 return 0;
             } else {
                 sleep 1;
                 rmdir $exlock;
                 next;
             }
         }
     }
     if ($err) {
         push @{$self->{errmsg}}, "exclusive: $err.";
     } else {
         push @{$self->{errmsg}}, "exclusive: timeout.";
     }
     return 0;
 }
 
 sub force_unlock {
     my ($self) = @_;
     unless ($self->{dir} and $self->{exp_sh} and $self->{exp_sh}) {
         push @{$self->{errmsg}}, "force_unlock: not set directory.";
         return 0;
     }
     my $shlock = $self->{dir}.$self->{exp_sh};
     my $exlock = $self->{dir}.$self->{exp_ex};
     # とにかく全てのロックを解除する。
     if (opendir DIR, $shlock) {
         foreach (map "$shlock/$_", grep !/^\.\.?$/, readdir DIR) {
             rmdir or unlink;
         }
         closedir DIR;
     }
     rmdir $shlock or unlink $shlock;
     rmdir $exlock or unlink $exlock;
 }
 
 sub force_shared {
     my ($self) = @_;
     unless ($self->{dir} and $self->{exp_sh} and $self->{exp_sh}) {
         push @{$self->{errmsg}}, "force_shared: not set directory.";
         return 0;
     }
     my $shlock = $self->{dir}.$self->{exp_sh};
     my $exlock = $self->{dir}.$self->{exp_ex};
     $self->{mode} == Shared and $self->{uniqid}
         and -d $shlock."/".$self->{uniqid} and return 1;
     my $uniqid = time.("00000$$" =~ /(.{5})$/)[0];
     foreach my $i ("00" .. $self->{trynum}) {
         unlink $shlock;
         mkdir $shlock;
         mkdir "$shlock/$uniqid$i" or next;
         $self->{uniqid} = $uniqid.$i;
         $self->{mode} = Shared;
         rmdir $exlock;
         return 1;
     }
     
 }
 
 sub force_exclusive {
     my ($self) = @_;
     unless ($self->{dir} and $self->{exp_sh} and $self->{exp_sh}) {
         push @{$self->{errmsg}}, "force_unlock: not set directory.";
         return 0;
     }
     my $shlock = $self->{dir}.$self->{exp_sh};
     my $exlock = $self->{dir}.$self->{exp_ex};
     mkdir $exlock;
     if (opendir DIR, $shlock) {
         foreach (map "$shlock/$_", grep !/^\.\.?$/, readdir DIR) {
             rmdir or unlink;
         }
         closedir DIR;
     }
     rmdir $shlock;
     open LOCK, ">$shlock" and close LOCK;
 }
 
 1;

使用方法

 # use指定
 use Yuki::Lock::Mkdir;

 # 標準的?場合
 # オブジェクトの作成とロック
 my $lock = NewLock "foo/bar/fogedb", Shared, trytime=>5;
 # エラーチェック
 @{$lock->{errmsg}} and die join "\n","error!",@{$lock->{errmsg}};
 # 排他ロックへ変更
 $lock->exclusive or ...;
 # エラーメッセージクリア
 @{$lock->{errmsg}} = ();
 # 共有ロックへ変更
 $lock->shared or die join "\n","error!",@{$lock->{errmsg}};
 # 暗黙的ロック解除
 undef $lock;

 # 別の場合
 # オブジェクトの作成
 my $lock = new Yuki::Lock::Mkdir "foo/bar/foge";
 # 排他ロック
 $lock->exclusive;
 # 明示的ロック解除
 $lock->unlock;

解説

ロック機構は排他ロック用のディレクトリと共有ロック用のディレクトリ、そのサブディレクトリを使って実現しています。 排他/共有ロック用ディレクトリはデータベースと同じディレクトリに、デフォルトではそれぞれデータベース名に".lock_ex",".lock_sh"を付加したものです(変更できます)。 以下では、データベース名をfogeとして説明します。

排他ロックはディレクトリfoge.lock_exとファイルfoge.lock_shが存在した時とし、共有ロックはディレクトリfoge.lock_shと任意の名前を持つそのサブディレクトリが存在した時とします。 排他ロックを行う時は、まずディレクトリfoge.lock_exの作成(mkdir)を試みて、成功した場合のみファイルfoge.lock_shを作成してロック状態とします。 共有ロックを行う時は、まずディレクトリfoge.lock_exを作成(mkdir)を試みて、成功したらfoge.lock_shとそのサブディレクトリを作成して、foge.lock_exを消します。

バグ

現状では致命的エラーと一時的エラーを区別していません。 区別する方法としては以下の方法がありますが...

newでエラーが発生した場合はundefを返すべきかもしれません。

例外処理が多く、見通しがわるいです。よい方法はありませんか?

御意見募集

(Too many spams ... embedded comments are not allowed now, sorry.)