use Fcntl 'O_CREAT', 'O_RDWR', 'LOCK_EX', 'O_WRONLY', 'O_RDONLY';
sub O_ACCMODE () { O_RDONLY | O_RDWR | O_WRONLY }
-$VERSION = "0.91";
+$VERSION = "0.92";
my $DEFAULT_MEMORY_SIZE = 1<<21; # 2 megabytes
my $DEFAULT_AUTODEFER_THRESHHOLD = 3; # 3 records
my $DEFAULT_AUTODEFER_FILELEN_THRESHHOLD = 65536; # 16 disk blocksful
$opts{mode} = O_CREAT|O_RDWR unless defined $opts{mode};
$opts{rdonly} = (($opts{mode} & O_ACCMODE) == O_RDONLY);
+ $opts{sawlastrec} = undef;
my $fh;
$#{$self->{offsets}} = $len;
# $self->{offsets}[0] = 0; # in case we just chopped this
- $self->{cache}->remove(grep $_ >= $len, $self->{cache}->keys);
+ $self->{cache}->remove(grep $_ >= $len, $self->{cache}->ckeys);
}
sub PUSH {
return unless @data;
$pos = $oldsize; # This is what perl does for normal arrays
}
+
+ # The manual is very unclear here
+ if ($nrecs < 0) {
+ $nrecs = $oldsize - $pos + $nrecs;
+ $nrecs = 0 if $nrecs < 0;
+ }
}
$self->_fixrecs(@data);
# need to be renumbered
# Maybe merge this with the previous block?
{
- my @oldkeys = grep $_ >= $pos + $nrecs, $self->{cache}->keys;
+ my @oldkeys = grep $_ >= $pos + $nrecs, $self->{cache}->ckeys;
my @newkeys = map $_-$nrecs+@data, @oldkeys;
$self->{cache}->rekey(\@oldkeys, \@newkeys);
}
sub _write_record {
my ($self, $rec) = @_;
my $fh = $self->{fh};
+ local $\ = "";
print $fh $rec
or die "Couldn't write record: $!"; # "Should never happen."
# $self->{_written} += length($rec);
$rec = <$fh>;
}
return unless defined $rec;
- if (substr($rec, -$self->{recseplen}) ne $self->{recsep}) {
+ if (! $self->{sawlastrec} &&
+ substr($rec, -$self->{recseplen}) ne $self->{recsep}) {
# improperly terminated final record --- quietly fix it.
# my $ac = substr($rec, -$self->{recseplen});
# $ac =~ s/\n/\\n/g;
+ $self->{sawlastrec} = 1;
unless ($self->{rdonly}) {
+ local $\ = "";
my $fh = $self->{fh};
print $fh $self->{recsep};
}
if (not defined $self->{offsets}[0]) {
_ci_warn("offset 0 is missing!");
$good = 0;
+
} elsif ($self->{offsets}[0] != 0) {
_ci_warn("rec 0: offset <$self->{offsets}[0]> s/b 0!");
$good = 0;
}
my $deferring = $self->_is_deferring;
- for my $n ($self->{cache}->keys) {
+ for my $n ($self->{cache}->ckeys) {
my $r = $self->{cache}->_produce($n);
$cached += length($r);
next if $n+1 <= $.; # checked this already
}
}
+ # That cache has its own set of tests
$good = 0 unless $self->{cache}->_check_integrity;
# Now let's check the deferbuffer
}
}
-sub keys {
+sub ckeys {
my $self = shift;
my @a = keys %{$self->[HASH]};
@a;
$self->[HEAP]->expire_order;
}
-sub _check_integrity {
+BEGIN { *_ci_warn = \&Tie::File::_ci_warn }
+
+sub _check_integrity { # For CACHE
my $self = shift;
- $self->[HEAP]->_check_integrity;
+ my $good = 1;
+
+ # Test HEAP
+ $self->[HEAP]->_check_integrity or $good = 0;
+
+ # Test HASH
+ my $bytes = 0;
+ for my $k (keys %{$self->[HASH]}) {
+ if ($k ne '0' && $k !~ /^[1-9][0-9]*$/) {
+ $good = 0;
+ _ci_warn "Cache hash key <$k> is non-numeric";
+ }
+
+ my $h = $self->[HASH]{$k};
+ if (! defined $h) {
+ $good = 0;
+ _ci_warn "Heap index number for key $k is undefined";
+ } elsif ($h == 0) {
+ $good = 0;
+ _ci_warn "Heap index number for key $k is zero";
+ } else {
+ my $j = $self->[HEAP][$h];
+ if (! defined $j) {
+ $good = 0;
+ _ci_warn "Heap contents key $k (=> $h) are undefined";
+ } else {
+ $bytes += length($j->[2]);
+ if ($k ne $j->[1]) {
+ $good = 0;
+ _ci_warn "Heap contents key $k (=> $h) is $j->[1], should be $k";
+ }
+ }
+ }
+ }
+
+ # Test BYTES
+ if ($bytes != $self->[BYTES]) {
+ $good = 0;
+ _ci_warn "Total data in cache is $bytes, expected $self->[BYTES]";
+ }
+
+ # Test MAX
+ if ($bytes > $self->[MAX]) {
+ $good = 0;
+ _ci_warn "Total data in cache is $bytes, exceeds maximum $self->[MAX]";
+ }
+
+ return $good;
}
sub delink {
# If $i is omitted, default to 1 (the top element.)
sub _insert {
my ($self, $item, $i) = @_;
- $self->_check_loc($i) if defined $i;
+# $self->_check_loc($i) if defined $i;
$i = 1 unless defined $i;
until (! defined $self->[$i]) {
if ($self->[$i][SEQ] > $item->[SEQ]) { # inserted item is older
# bottom.
sub promote {
my ($self, $n) = @_;
- $self->_check_loc($n);
+# $self->_check_loc($n);
$self->[$n][SEQ] = $self->_nseq;
my $i = $n;
while (1) {
# Return item $n from the heap, promoting its LRU status
sub lookup {
my ($self, $n) = @_;
- $self->_check_loc($n);
+# $self->_check_loc($n);
my $val = $self->[$n];
$self->promote($n);
$val->[DAT];
# Assign a new value for node $n, promoting it to the bottom of the heap
sub set_val {
my ($self, $n, $val) = @_;
- $self->_check_loc($n);
+# $self->_check_loc($n);
my $oval = $self->[$n][DAT];
$self->[$n][DAT] = $val;
$self->promote($n);
# alter the heap's record of the hash key
sub rekey {
my ($self, $n, $new_key) = @_;
- $self->_check_loc($n);
+# $self->_check_loc($n);
$self->[$n][KEY] = $new_key;
}
sub _check_loc {
my ($self, $n) = @_;
- unless (defined $self->[$n]) {
+ unless (1 || defined $self->[$n]) {
confess "_check_loc($n) failed";
}
}
+BEGIN { *_ci_warn = \&Tie::File::_ci_warn }
+
sub _check_integrity {
my $self = shift;
my $good = 1;
+ my %seq;
+
unless (eval {$self->[0][1]->isa("Tie::File::Cache")}) {
- print "# Element 0 of heap corrupt\n";
+ _ci_warn "Element 0 of heap corrupt";
$good = 0;
}
$good = 0 unless $self->_satisfies_heap_condition(1);
for my $i (2 .. $#{$self}) {
my $p = int($i/2); # index of parent node
if (defined $self->[$i] && ! defined $self->[$p]) {
- print "# Element $i of heap defined, but parent $p isn't\n";
+ _ci_warn "Element $i of heap defined, but parent $p isn't";
$good = 0;
}
+
+ if (defined $self->[$i]) {
+ if ($seq{$self->[$i][SEQ]}) {
+ my $seq = $self->[$i][SEQ];
+ _ci_warn "Nodes $i and $seq{$seq} both have SEQ=$seq";
+ $good = 0;
+ } else {
+ $seq{$self->[$i][SEQ]} = $i;
+ }
+ }
}
+
return $good;
}
my $c = $n*2 + $_;
next unless defined $self->[$c];
if ($self->[$n][SEQ] >= $self->[$c]) {
- print "# Node $n of heap does not predate node $c\n";
+ _ci_warn "Node $n of heap does not predate node $c";
$good = 0 ;
}
$good = 0 unless $self->_satisfies_heap_condition($c);
=head1 SYNOPSIS
- # This file documents Tie::File version 0.90
+ # This file documents Tie::File version 0.92
tie @array, 'Tie::File', filename or die ...;
=head1 LICENSE
-C<Tie::File> version 0.90 is copyright (C) 2002 Mark Jason Dominus.
+C<Tie::File> version 0.92 is copyright (C) 2002 Mark Jason Dominus.
This library is free software; you may redistribute it and/or modify
it under the same terms as Perl itself.
=head1 WARRANTY
-C<Tie::File> version 0.90 comes with ABSOLUTELY NO WARRANTY.
+C<Tie::File> version 0.92 comes with ABSOLUTELY NO WARRANTY.
For details, see the license.
=head1 THANKS
#
# print "1..0\n"; exit;
-print "1..26\n";
+print "1..42\n";
my ($N, @R, $Q, $ar) = (1);
{
my $good = 1;
for my $meth (qw(new is_empty empty lookup remove
- insert update rekey expire keys bytes
+ insert update rekey expire ckeys bytes
set_limit adj_limit flush reduce_size_to
_produce _produce_lru )) {
unless ($h->can($meth)) {
$N++;
}
-# (4) Straight insert and removal FIFO test
+# (4-5) Straight insert and removal FIFO test
$ar = 'a0';
for (1..10) {
$h->insert($_, $ar++);
print "@R" eq $iota
? "ok $N\n" : "not ok $N \# expected ($iota), got (@R)\n";
$N++;
+check($h);
-# (5) Remove from empty heap
+# (6-7) Remove from empty heap
$n = $h->expire;
print ! defined $n ? "ok $N\n" : "not ok $N \# expected UNDEF, got $n";
$N++;
+check($h);
-# (6) Interleaved insert and removal
+# (8-9) Interleaved insert and removal
$Q = 0;
@R = ();
for my $i (1..4) {
$iota = iota('b', 9);
print "@R" eq $iota ? "ok $N\n" : "not ok $N \# expected ($iota), got (@R)\n";
$N++;
+check($h);
-# (7) It should be empty now
+# (10) It should be empty now
print $h->is_empty ? "ok $N\n" : "not ok $N\n";
$N++;
-# (8) Insert and delete
+# (11-12) Insert and delete
$Q = 1;
for (1..10) {
$h->insert($_, "c$Q");
print "@R" eq "c1 c3 c5 c7 c9" ?
"ok $N\n" : "not ok $N \# expected (c1 c3 c5 c7 c9), got (@R)\n";
$N++;
+check($h);
-# (9) Interleaved insert and delete
+# (13-14) Interleaved insert and delete
$Q = 1; my $QQ = 1;
@R = ();
for my $i (1..4) {
print "@R" eq "d1 d3 d5 d7 d9" ?
"ok $N\n" : "not ok $N \# expected (d1 d3 d5 d7 d9), got (@R)\n";
$N++;
+check($h);
-# (10) Promote
+# (15-16) Promote
$h->empty;
$Q = 1;
for (1..10) {
"ok $N\n" :
"not ok $N \# expected (e1 e3 e5 e7 e9 e2 e4 e6 e8 e10), got (@R)\n";
$N++;
+check($h);
-# (11-15) Lookup
+# (17-22) Lookup
$Q = 1;
for (1..10) {
$h->insert($_, "f$Q");
print $r eq "f$_" ? "ok $N\n" : "not ok $N \# expected f$_, got $r\n";
$N++;
}
+check($h);
-# (16) It shouldn't be empty
+# (23) It shouldn't be empty
print ! $h->is_empty ? "ok $N\n" : "not ok $N\n";
$N++;
-# (17) Lookup should have promoted the looked-up records
+# (24-25) Lookup should have promoted the looked-up records
@R = ();
push @R, $n while defined ($n = $h->expire);
print "@R" eq "f1 f3 f5 f7 f9 f10 f2 f6 f4 f8" ?
"ok $N\n" :
"not ok $N \# expected (f1 f3 f5 f7 f9 f10 f2 f6 f4 f8), got (@R)\n";
$N++;
+check($h);
-# (18-19) Typical 'rekey' operation
+# (26-29) Typical 'rekey' operation
$Q = 1;
for (1..10) {
$h->insert($_, "g$Q");
print $good ? "ok $N\n" : "not ok $N\n";
$N++;
}
+check($h);
{
my $good = 1;
for my $k (6, 7) {
print $good ? "ok $N\n" : "not ok $N\n";
$N++;
}
+check($h);
-# (20) keys
-@R = sort { $a <=> $b } $h->keys;
+# (30-31) ckeys
+@R = sort { $a <=> $b } $h->ckeys;
print "@R" eq "1 2 3 4 5 8 9 10 11 12" ?
"ok $N\n" :
"not ok $N \# expected (1 2 3 4 5 8 9 10 11 12) got (@R)\n";
$N++;
+check($h);
1;
-# (21) update
+# (32-33) update
for (1..5, 8..12) {
$h->update($_, "h$_");
}
@R = ();
-for (sort { $a <=> $b } $h->keys) {
+for (sort { $a <=> $b } $h->ckeys) {
push @R, $h->lookup($_);
}
print "@R" eq "h1 h2 h3 h4 h5 h8 h9 h10 h11 h12" ?
"ok $N\n" :
"not ok $N \# expected (h1 h2 h3 h4 h5 h8 h9 h10 h11 h12) got (@R)\n";
$N++;
+check($h);
-# (22-23) bytes
+# (34-37) bytes
my $B;
$B = $h->bytes;
print $B == 23 ? "ok $N\n" : "not ok $N \# expected 23, got $B\n";
$N++;
+check($h);
$h->update('12', "yobgorgle");
$B = $h->bytes;
print $B == 29 ? "ok $N\n" : "not ok $N \# expected 29, got $B\n";
$N++;
+check($h);
-# (24-25) empty
+# (38-41) empty
$h->empty;
print $h->is_empty ? "ok $N\n" : "not ok $N\n";
$N++;
+check($h);
$n = $h->expire;
print ! defined $n ? "ok $N\n" : "not ok $N \# expected UNDEF, got $n";
$N++;
+check($h);
-# (26) very weak testing of DESTROY
+# (42) very weak testing of DESTROY
undef $h;
# are we still alive?
print "ok $N\n";
$N++;
+sub check {
+ my $h = shift;
+ print $h->_check_integrity ? "ok $N\n" : "not ok $N\n";
+ $N++;
+}
sub iota {
my ($p, $n) = @_;