DBIx-Locker
view release on metacpan
or search on metacpan
Changes
view on Meta::CPAN
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 0.100117 2014-05-19 17:38:21-04:00 America/New_York
- Fix tests on DBD::SQLite 1.42 (frew)
0.100116 2013-12-17 13:08:36-0500 America/New_York
- tweak test expectations to allow for multiline unique key error
(seems to be found in older SQLite?)
0.100115 2013-11-27 22:40:10 America/New_York
Add lockstring method to ::Lock (frew)
Add is_locked flag to ::Lock to make unlock idempotent (frew)
0.100114 2013-07-30 18:03:23 America/New_York
Include errstr from underlying db (frew)
update bugtracker
0.100113 2013-07-05 21:13:26 America/New_York
fix typo, update bugtracker
0.100112 2012-11-06 12:01:01 America/New_York
|
lib/DBIx/Locker.pm
view on Meta::CPAN
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
lib/DBIx/Locker.pm
view on Meta::CPAN
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
lib/DBIx/Locker.pm
view on Meta::CPAN
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | Carp::confess( "no lockstring provided" )
unless defined $lockstring and length $lockstring ;
my $expires = $arg ->{expires} ||= 3600;
Carp::confess( "expires must be a positive integer" )
unless $expires > 0 and $expires == int $expires ;
$expires = time + $expires ;
my $locked_by = {
host => Sys::Hostname::hostname(),
guid => Data::GUID->new->as_string,
pid => $$,
};
my $table = $self ->table;
my $dbh = $self ->dbh;
local $dbh ->{RaiseError} = 0;
local $dbh ->{PrintError} = 0;
my $rows = $dbh -> do (
"INSERT INTO $table (lockstring, created, expires, locked_by)
VALUES (?, ?, ?, ?)",
undef ,
$lockstring ,
$self ->_time_to_string,
$self ->_time_to_string([ localtime ( $expires ) ]),
$JSON ->encode( $locked_by ),
);
die (
"could not lock resource <$lockstring>" . (
$dbh ->err && $dbh ->errstr
? ( ': ' . $dbh ->errstr)
: ''
)
) unless $rows and $rows == 1;
my $lock = DBIx::Locker::Lock->new({
locker => $self ,
lock_id => $self ->last_insert_id,
expires => $expires ,
locked_by => $locked_by ,
lockstring => $lockstring ,
});
return $lock ;
}
sub _time_to_string {
my ( $self , $time ) = @_ ;
$time = [ localtime ] unless $time ;
|
lib/DBIx/Locker.pm
view on Meta::CPAN
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | ...and a B<warning>.
DBIx::Locker was written to replace some lousy database resource locking code.
The code would establish a MySQL lock with C<GET_LOCK> to lock arbitrary
resources. Unfortunately, the code would also silently reconnect in case of
database connection failure, silently losing the connection-based lock .
DBIx::Locker locks by creating a persistent row in a "locks" table.
Because DBIx::Locker locks are stored in a table, they won't go away. They
have to be purged regularly. (A program for doing this, F<dbix_locker_purge>,
is included.) The locked resource is just a string. All records in the lock
(or semaphore) table are unique on the lock string.
This is the I<entire> mechanism. This is quick and dirty and quite effective,
but it's not highly efficient. If you need high speed locks with multiple
levels of resolution, or anything other than a quick and brutal solution,
I<keep looking>.
|
lib/DBIx/Locker.pm
view on Meta::CPAN
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
lib/DBIx/Locker/Lock.pm
view on Meta::CPAN
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | sub new {
my ( $class , $arg ) = @_ ;
my $guts = {
is_locked => 1,
locker => $arg ->{locker},
lock_id => $arg ->{lock_id},
expires => $arg ->{expires},
locked_by => $arg ->{locked_by},
lockstring => $arg ->{lockstring},
};
return bless $guts => $class ;
}
BEGIN {
for my $attr ( qw(locker lock_id locked_by lockstring) ) {
Sub::Install::install_sub({
code => sub {
Carp::confess( "$attr is read-only" ) if @_ > 1;
$_ [0]->{ $attr }
},
as => $attr ,
});
}
}
|
lib/DBIx/Locker/Lock.pm
view on Meta::CPAN
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | return $new_expiry ;
}
sub guid { $_ [0]->locked_by->{guid} }
sub is_locked {
$_ [0]->{is_locked} = $_ [1] if exists $_ [1];
$_ [0]->{is_locked}
}
sub unlock {
my ( $self ) = @_ ;
return unless $self ->is_locked;
my $dbh = $self ->locker->dbh;
my $table = $self ->locker->table;
my $rows = $dbh -> do ( "DELETE FROM $table WHERE id=?" , undef , $self ->lock_id);
Carp::confess( 'error releasing lock' ) unless $rows == 1;
$self ->is_locked(0);
}
sub DESTROY {
my ( $self ) = @_ ;
local $@;
return unless $self ->locked_by->{pid} == $$;
$self ->unlock;
}
1;
|
lib/DBIx/Locker/Lock.pm
view on Meta::CPAN
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | L<DBIx::Locker> to create locks. Since you are not a locker, you should not
call this method. Seriously.
my $locker = DBIx::Locker::Lock->new(\ %arg );
This returns a new lock .
locker - the locker creating the lock
lock_id - the id of the lock in the lock table
expires - the time (in epoch seconds) at which the lock will expire
locked_by - a hashref of identifying information
lockstring - the string that was locked
|
lib/DBIx/Locker/Lock.pm
view on Meta::CPAN
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
$lock ->expires( time + 3600);
When updating the expiration time , if the given expiration time is not a valid
unix time , or if the expiration cannot be updated, an exception will be raised.
|
sql/mssql.sql
view on Meta::CPAN
1 2 3 4 5 6 7 8 9 | CREATE TABLE [locks] (
[id] bigint NOT NULL IDENTITY (1, 1),
[lockstring] varchar(128) NOT NULL,
[created] datetime NOT NULL,
[expires] datetime NOT NULL,
[locked_by] text NOT NULL,
CONSTRAINT [PK_locks] PRIMARY KEY CLUSTERED ([id]),
CONSTRAINT [UC_locks] UNIQUE ([lockstring])
);
|
sql/mysql.sql
view on Meta::CPAN
1 2 3 4 5 6 7 | CREATE TABLE semaphores (
id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
lockstring varchar(128) UNIQUE,
created datetime NOT NULL,
expires datetime NOT NULL,
locked_by text NOT NULL
);
|
sql/sqlite.sql
view on Meta::CPAN
1 2 3 4 5 6 7 | CREATE TABLE locks (
id INTEGER PRIMARY KEY,
lockstring varchar(128) UNIQUE,
created varchar(14) NOT NULL,
expires varchar(14) NOT NULL,
locked_by varchar(1024)
);
|
t/basic.t
view on Meta::CPAN
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | my @conn = ( 'dbi:SQLite:dbname=test.db' , undef , undef , {});
{
my $dbh = DBI-> connect ( @conn );
$dbh -> do ('CREATE TABLE locks (
id INTEGER PRIMARY KEY,
lockstring varchar(128) UNIQUE,
created varchar(14) NOT NULL,
expires varchar(14) NOT NULL,
locked_by varchar(1024)
)');
}
my $locker = DBIx::Locker->new({
dbi_args => \ @conn ,
table => 'locks' ,
});
isa_ok( $locker , 'DBIx::Locker' );
|
t/basic.t
view on Meta::CPAN
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | my $expiry = $lock ->expires;
like( $expiry , qr/\A\d+\z/ , "expiry is an integer" );
cmp_ok( $expiry , '>' , time , "expiry is in the future" );
$guid = $lock ->guid;
eval { $locker -> lock ( 'Zombie Soup' ); };
ok(
$@,
"can't lock already-locked resources"
);
ok( $lock ->is_locked, 'lock is active' );
$lock ->unlock;
ok(! $lock ->is_locked, 'lock is not active' );
ok( eval { $lock ->unlock; 1}, 'unlock twice works' );
}
{
my $lock = $locker -> lock ( 'Zombie Soup' );
isa_ok( $lock , 'DBIx::Locker::Lock' , 'newly obtained lock' );
isnt( $lock ->guid, $guid , "new lock guid is not the old lock guid" );
my $lock_2 = $locker -> lock ( 'Zombie Cola' );
|