File Coverage

blib/lib/MLDBM/Sync.pm
Criterion Covered Total %
statement 131 165 79.3
branch 37 80 46.2
condition 11 20 55.0
subroutine 23 24 95.8
pod 0 8 0.0
total 202 297 68.0


line stmt bran cond sub pod time code
1              
2             package MLDBM::Sync;
3             $VERSION = '0.30';
4              
5 7     7   28950 use MLDBM;
  7         35992  
  7         56  
6 7     7   5332 use MLDBM::Sync::SDBM_File;
  7         699  
  7         386  
7 7     7   14736 use Data::Dumper;
  7         81665  
  7         758  
8 7     7   62 use Fcntl qw(:flock);
  7         12  
  7         1316  
9 7     7   264 use Digest::MD5 qw(md5_hex);
  7         14  
  7         478  
10 7     7   41 use strict;
  7         12  
  7         1814  
11 7     7   41 use Carp qw(confess);
  7         2735  
  7         3176  
12 7     7   34 no strict qw(refs);
  7         1324  
  7         521  
13 7     7   46 use vars qw($AUTOLOAD @EXT $CACHE_ERR $LOCK_SH $LOCK_EX $LOCK_UN);
  7         167  
  7         21221  
14              
15 7     7   3079 eval "use Tie::Cache;";
  0         0  
  0         0  
16             if (($@)) {
17             $CACHE_ERR = $@;
18             }
19              
20             $LOCK_SH = LOCK_SH;
21             $LOCK_UN = LOCK_UN;
22             $LOCK_EX = LOCK_EX;
23              
24             @EXT = ('.pag', '.dir', '');
25              
26             sub TIEHASH {
27 6     6   202 my($class, $file, @args) = @_;
28              
29 6         37 $file =~ /^(.*)$/s;
30 6         21 $file = $1;
31 6         19 my $fh = $file.".lock";
32              
33 6         156 my $self = bless {
34             'file' => $file,
35             'args' => [ $file, @args ],
36             'lock_fh' => $fh,
37             'lock_file' => $fh,
38             'lock_num' => 0,
39             'md5_keys' => 0,
40             'pid' => $$,
41             'keys' => [],
42             'db_type' => $MLDBM::UseDB,
43             'serializer' => $MLDBM::Serializer,
44             'remove_taint' => $MLDBM::RemoveTaint,
45             };
46              
47 6         23 $self;
48             }
49              
50             sub DESTROY {
51 6     6   475 my $self = shift;
52 6 50       0 if($self->{lock_num}) {
53 0         0 $self->{lock_num} = 1;
54 0         0 $self->UnLock;
55             }
56             }
57              
58             sub AUTOLOAD {
59 845     845   15288 my($self, $key, $value) = @_;
60 845         4035 $AUTOLOAD =~ /::([^:]+)$/;
61 845         1962 my $func = $1;
62 845 50       3111 grep($func eq $_, ('FETCH', 'STORE', 'EXISTS', 'DELETE'))
63             || die("$func not handled by object $self");
64              
65             ## CHECKSUM KEYS
66 845 100 66     4095 if(defined $key && $self->{md5_keys}) {
67 200         365 $key = $self->SyncChecksum($key);
68             }
69              
70             # CACHE, short circuit if found in cache on FETCH/EXISTS
71             # after checksum, since that's what we store
72 845 50       2146 my $cache = (defined $key) ? $self->{cache} : undef;
73 845 0 0     1522 if($cache && ($func eq 'FETCH' or $func eq 'EXISTS')) {
      33        
74 0         0 my $rv = $cache->$func($key);
75 0 0       0 defined($rv) && return($rv);
76             }
77              
78 845         789 my $rv;
79 845 100 66     2916 if ($func eq 'FETCH' or $func eq 'EXISTS') {
80 425         891 $self->read_lock;
81             } else {
82 420         858 $self->lock;
83             }
84              
85             {
86 844         979 local $MLDBM::RemoveTaint = $self->{remove_taint};
  844         1337  
87 844 100       1522 if (defined $value) {
88 415         1362 $rv = $self->{dbm}->$func($key, $value);
89             } else {
90 429         1341 $rv = $self->{dbm}->$func($key);
91             }
92             }
93              
94 844         30257 $self->unlock;
95              
96             # do after lock critical section, no point taking
97             # any extra time there
98 844 50       1884 $cache && $cache->$func($key, $value);
99              
100 844         5677 $rv;
101             }
102              
103             sub CLEAR {
104 11     11   73 my $self = shift;
105            
106 11         38 $self->lock;
107 11         50 $self->{dbm}->CLEAR;
108 11         5983 $self->{dbm} = undef;
109             # delete the files to free disk space
110 11         293 my $unlinked = 0;
111 11         39 for (@EXT) {
112 33         96 my $file = $self->{file}.$_;
113 33 100       420 next if(! -e $file);
114 22 50       223 if(-d $file) {
115 0 0       0 rmdir($file) || warn("can't unlink dir $file: $!");
116             } else {
117 22 50       1654 unlink($file) || die("can't unlink file $file: $!");
118             }
119              
120 22         46 $unlinked++;
121             }
122 11 50       49 if($self->{lock_num} > 1) {
123 0         0 $self->SyncTie; # recreate, not done with it yet
124             }
125              
126 11         52 $self->unlock;
127 11 50       39 if($self->{lock_num} == 0) {
128             # only unlink if we are clear of all the locks
129 11         501 unlink($self->{lock_file});
130             }
131            
132 11 50       47 $self->{cache} && $self->{cache}->CLEAR;
133              
134 11         54 1;
135             };
136              
137             # don't bother with cache for first/next key since it'll kill
138             # the cache anyway likely
139             sub FIRSTKEY {
140 12     12   88 my $self = shift;
141              
142 12 100       63 if($self->{md5_keys}) {
143 1         262 confess("can't get keys() or each() on MLDBM::Sync database ".
144             "with SyncKeysChecksum(1) set");
145             }
146            
147 11         27 $self->read_lock;
148 11         48 my $key = $self->{dbm}->FIRSTKEY();
149 11         154 my @keys;
150 11         18 while(1) {
151 531 100       3239 last if ! defined($key);
152 520         852 push(@keys, $key);
153 520         1441 $key = $self->{dbm}->NEXTKEY($key);
154             }
155 11         31 $self->unlock;
156 11         26 $self->{'keys'} = \@keys;
157              
158 11         45 $self->NEXTKEY;
159             }
160              
161             sub NEXTKEY {
162 531     531   664 my $self = shift;
163              
164 531 50       1080 if($self->{md5_keys}) {
165 0         0 confess("can't get keys() or each() on MLDBM::Sync database ".
166             "with SyncKeysChecksum(1) set");
167             }
168            
169 531         541 my $rv = shift(@{$self->{'keys'}});
  531         2023  
170             }
171              
172             sub SyncChecksum {
173 200     200 0 232 my($self, $key) = @_;
174 200 50       328 if(ref $key) {
175 0         0 join('g', md5_hex($$key), sprintf("%07d",length($$key)));
176             } else {
177 200         2527 join('g', md5_hex($key), sprintf("%07d", length($key)));
178             }
179             }
180              
181             sub SyncCacheSize {
182 0     0 0 0 my($self, $size) = @_;
183 0 0       0 $CACHE_ERR && die("need Tie::Cache installed to use this feature: $@");
184              
185 0 0       0 if ($size =~ /^(\d+)(M|K)$/) {
186 0         0 my($num, $type) = ($1, $2);
187 0 0       0 if (($type eq 'M')) {
    0          
188 0         0 $size = $num * 1024 * 1024;
189             } elsif (($type eq 'K')) {
190 0         0 $size = $num * 1024;
191             } else {
192 0         0 die "$type symbol not understood for $size";
193             }
194             } else {
195 0 0       0 ($size =~ /^\d+$/) or die("$size must be bytes size for cache");
196             }
197            
198 0 0       0 if ($self->{cache}) {
199 0         0 $self->{cache}->CLEAR(); # purge old cache, to free up RAM maybe for mem leaks
200             }
201            
202 0         0 my %cache;
203 0         0 my $cache = tie %cache, 'Tie::Cache', { MaxBytes => $size };
204 0         0 $self->{cache} = $cache; # use non tied interface, faster
205             }
206              
207             sub SyncTie {
208 471     471 0 618 my $self = shift;
209 471         598 my %temp_hash;
210 471         693 my $args = $self->{args};
211 471         856 local $MLDBM::UseDB = $self->{db_type};
212 471         645 local $MLDBM::Serializer = $self->{serializer};
213 471         665 local $MLDBM::RemoveTaint = $self->{remove_taint};
214 471   50     2042 $self->{dbm} = tie(%temp_hash, 'MLDBM', @$args) || die("can't tie to MLDBM with args: ".join(',', @$args)."; error: $!");
215              
216 471         69242 $self->{dbm};
217             }
218              
219             #### DOCUMENTED API ################################################################
220              
221             sub SyncKeysChecksum {
222 1     1 0 6 my($self, $setting) = @_;
223 1 50       5 if(defined $setting) {
224 1         8 $self->{md5_keys} = $setting;
225             } else {
226 0         0 $self->{md5_keys};
227             }
228             }
229              
230             *read_lock = *ReadLock;
231 439     439 0 923 sub ReadLock { shift->Lock(1); }
232              
233             *lock = *SyncLock = *Lock;
234             sub Lock {
235 873     873 0 1281 my($self, $read_lock) = @_;
236              
237 873 100       2677 if($self->{lock_num}++ == 0) {
238 471         709 my $file = $self->{lock_file};
239 471 50       27452 open($self->{lock_fh}, "+>$file") || die("can't open file $file: $!");
240 471 0       4349 flock($self->{lock_fh}, ($read_lock ? $LOCK_SH : $LOCK_EX))
    100          
    50          
241             || die("can't ". ($read_lock ? "read" : "write") ." lock $file: $!");
242 471         845 $self->{read_lock} = $read_lock;
243 471         1138 $self->SyncTie;
244             } else {
245 402 100 100     1355 if ($self->{read_lock} and ! $read_lock) {
246 1         3 $self->{lock_num}--; # roll back lock count
247             # confess here to help developer track this down
248 1         291 confess("Can't upgrade lock type from LOCK_SH to LOCK_EX! ".
249             "This could happen if you tried to write to the MLDBM ".
250             "in a critical section locked by ReadLock(). ".
251             "Also the read expression my \$v = \$db{'key1'}{'key2'} will trigger a write ".
252             "if \$db{'key1'} does not already exist, so this will error in a ReadLock() section"
253             );
254             }
255 401         630 1;
256             }
257             }
258              
259             *unlock = *SyncUnLock = *UnLock;
260             sub UnLock {
261 872     872 0 1486 my $self = shift;
262              
263 872 100 66     4410 if($self->{lock_num} && $self->{lock_num}-- == 1) {
264 471         694 $self->{lock_num} = 0;
265 471         717 undef $self->{dbm};
266 471 50       12757 flock($self->{'lock_fh'}, $LOCK_UN) || die("can't unlock $self->{'lock_file'}: $!");
267 471 50       5254 close($self->{'lock_fh'}) || die("can't close $self->{'lock_file'}");
268 471         907 $self->{read_lock} = undef;
269 471         845 1;
270             } else {
271 401         582 1;
272             }
273             }
274              
275             sub SyncSize {
276 1     1 0 9 my $self = shift;
277 1         2 my $size = 0;
278 1         4 for (@EXT) {
279 3         7 my $file = $self->{file}.$_;
280 3 50       41 next unless -e $file;
281 0         0 $size += (stat($file))[7];
282              
283 0 0       0 if(-d $file) {
284 0         0 $size += (stat($file))[7];
285 0 0       0 opendir(DIR, $file) || next;
286 0         0 my @files = readdir(DIR);
287 0         0 for my $dir_file (@files) {
288 0 0       0 next if $dir_file =~ /^\.\.?$/;
289 0         0 $size += (stat("$file/$dir_file"))[7];
290             }
291 0         0 closedir(DIR);
292             }
293             }
294              
295 1         6 $size;
296             }
297              
298             1;
299              
300             __END__