From: rkinyon Date: Tue, 11 Apr 2006 15:19:35 +0000 (+0000) Subject: Adding transactions further - settled on MVCC X-Git-Tag: 0-99_01~25 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=15ba72cca3f518ab20883c61fbacfee07ff382ed;p=dbsrgits%2FDBM-Deep.git Adding transactions further - settled on MVCC --- diff --git a/lib/DBM/Deep.pm b/lib/DBM/Deep.pm index 9921062..815cfd9 100644 --- a/lib/DBM/Deep.pm +++ b/lib/DBM/Deep.pm @@ -143,67 +143,14 @@ sub TIEARRAY { return DBM::Deep::Array->TIEARRAY( @_ ); } -#XXX Unneeded now ... -#sub DESTROY { -#} - sub lock { - ## - # If db locking is set, flock() the db file. If called multiple - # times before unlock(), then the same number of unlocks() must - # be called before the lock is released. - ## my $self = shift->_get_self; - my ($type) = @_; - $type = LOCK_EX unless defined $type; - - if (!defined($self->_fh)) { return; } - - if ($self->_fileobj->{locking}) { - if (!$self->_fileobj->{locked}) { - flock($self->_fh, $type); - - # refresh end counter in case file has changed size - my @stats = stat($self->_fh); - $self->_fileobj->{end} = $stats[7]; - - # double-check file inode, in case another process - # has optimize()d our file while we were waiting. - if ($stats[1] != $self->_fileobj->{inode}) { - $self->_fileobj->close; - $self->_fileobj->open; - $self->{engine}->setup_fh( $self ); - flock($self->_fh, $type); # re-lock - - # This may not be necessary after re-opening - $self->_fileobj->{end} = (stat($self->_fh))[7]; # re-end - } - } - $self->_fileobj->{locked}++; - - return 1; - } - - return; + return $self->_fileobj->lock( $self, @_ ); } sub unlock { - ## - # If db locking is set, unlock the db file. See note in lock() - # regarding calling lock() multiple times. - ## my $self = shift->_get_self; - - if (!defined($self->_fh)) { return; } - - if ($self->_fileobj->{locking} && $self->_fileobj->{locked} > 0) { - $self->_fileobj->{locked}--; - if (!$self->_fileobj->{locked}) { flock($self->_fh, LOCK_UN); } - - return 1; - } - - return; + return $self->_fileobj->unlock( $self, @_ ); } sub _copy_value { diff --git a/lib/DBM/Deep/Engine.pm b/lib/DBM/Deep/Engine.pm index 7249fe7..ddf16e4 100644 --- a/lib/DBM/Deep/Engine.pm +++ b/lib/DBM/Deep/Engine.pm @@ -109,7 +109,7 @@ sub write_file_header { SIG_HEADER, pack('N', 1), # header version pack('N', 12), # header size - pack('N', 0), # file version + pack('N', 0), # currently running transaction IDs pack('S', $self->{long_size}), pack('A', $self->{long_pack}), pack('S', $self->{data_size}), @@ -138,20 +138,23 @@ sub read_file_header { ); unless ( $file_signature eq SIG_FILE ) { - $self->{fileobj}->close; + $self->_fileobj->close; $self->_throw_error( "Signature not found -- file is not a Deep DB" ); } unless ( $sig_header eq SIG_HEADER ) { - $self->{fileobj}->close; + $self->_fileobj->close; $self->_throw_error( "Old file version found." ); } my $buffer2; $bytes_read += read( $fh, $buffer2, $size ); - my ($file_version, @values) = unpack( 'N S A S A S', $buffer2 ); + my ($running_transactions, @values) = unpack( 'N S A S A S', $buffer2 ); + + $self->_fileobj->set_transaction_offset( 13 ); + if ( @values < 5 || grep { !defined } @values ) { - $self->{fileobj}->close; + $self->_fileobj->close; $self->_throw_error("Corrupted file - bad header"); } diff --git a/lib/DBM/Deep/File.pm b/lib/DBM/Deep/File.pm index 1282a10..2862ca5 100644 --- a/lib/DBM/Deep/File.pm +++ b/lib/DBM/Deep/File.pm @@ -28,6 +28,7 @@ sub new { filter_fetch_value => undef, transaction_id => 0, + transaction_offset => 0, }, $class; # Grab the parameters we want to use @@ -90,15 +91,88 @@ sub DESTROY { return; } +## +# If db locking is set, flock() the db file. If called multiple +# times before unlock(), then the same number of unlocks() must +# be called before the lock is released. +## +sub lock { + my $self = shift; + my ($obj, $type) = @_; + $type = LOCK_EX unless defined $type; + + if (!defined($self->{fh})) { return; } + + if ($self->{locking}) { + if (!$self->{locked}) { + flock($self->{fh}, $type); + + # refresh end counter in case file has changed size + my @stats = stat($self->{fh}); + $self->{end} = $stats[7]; + + # double-check file inode, in case another process + # has optimize()d our file while we were waiting. + if ($stats[1] != $self->{inode}) { + $self->close; + $self->open; + + #XXX This needs work + $obj->{engine}->setup_fh( $obj ); + + flock($self->{fh}, $type); # re-lock + + # This may not be necessary after re-opening + $self->{end} = (stat($self->{fh}))[7]; # re-end + } + } + $self->{locked}++; + + return 1; + } + + return; +} + +## +# If db locking is set, unlock the db file. See note in lock() +# regarding calling lock() multiple times. +## +sub unlock { + my $self = shift; + + if (!defined($self->{fh})) { return; } + + if ($self->{locking} && $self->{locked} > 0) { + $self->{locked}--; + if (!$self->{locked}) { flock($self->{fh}, LOCK_UN); } + + return 1; + } + + return; +} + +sub set_transaction_offset { + my $self = shift; + $self->{transaction_offset} = shift; +} + sub begin_transaction { my $self = shift; + my $fh = $self->{fh}; + + seek( $fh, $self->{transaction_offset}, SEEK_SET ); + $self->{transaction_id}++; } sub end_transaction { my $self = shift; +# seek( $fh, $self->{transaction_offset}, SEEK_SET ); + $self->{transaction_id} = 0; } diff --git a/t/28_transactions.t b/t/28_transactions.t index 736beff..3915a00 100644 --- a/t/28_transactions.t +++ b/t/28_transactions.t @@ -48,6 +48,12 @@ is( $db2->{other_x}, 'foo', "After DB1 transaction is over, DB2 can still see ot # Should the transaction be in the Root and not the Engine? How would that # work? +# What about the following: +# $db->{foo} = {}; +# $db2 = $db->{foo}; +# $db2->begin_work; +# $db->{foo} = 3; + __END__ Plan for transactions: