File Coverage

blib/lib/CHI/Driver/SharedMem.pm
Criterion Covered Total %
statement 125 131 95.4
branch 29 40 72.5
condition 7 15 46.6
subroutine 23 23 100.0
pod 8 8 100.0
total 192 217 88.4


line stmt bran cond sub pod time code
1             package CHI::Driver::SharedMem;
2              
3             # There is an argument for mapping namespaces into keys and then putting
4             # different namespaces into different shared memory areas. I will think about
5             # that.
6              
7 5     5   679723 use warnings;
  5         23  
  5         207  
8 5     5   31 use strict;
  5         22  
  5         107  
9 5     5   3223 use Moose;
  5         2456006  
  5         49  
10 5     5   41871 use IPC::SysV qw(S_IRUSR S_IWUSR IPC_CREAT);
  5         2584  
  5         547  
11 5     5   957 use IPC::SharedMem;
  5         10155  
  5         166  
12 5     5   582 use JSON::MaybeXS;
  5         5982  
  5         367  
13 5     5   35 use Carp;
  5         17  
  5         333  
14 5     5   40 use Config;
  5         22  
  5         211  
15 5     5   46 use Fcntl;
  5         21  
  5         9168  
16              
17             extends 'CHI::Driver';
18              
19             has 'shmkey' => (is => 'ro', isa => 'Int');
20             has 'shm' => (is => 'ro', builder => '_build_shm', lazy => 1);
21             has 'size' => (is => 'ro', isa => 'Int', default => 8 * 1024);
22             has 'lock' => (
23             is => 'ro',
24             builder => '_build_lock',
25             );
26             has 'lock_file' => (is => 'rw', isa => 'Str|Undef');
27             has '_data_size' => (
28             is => 'rw',
29             isa => 'Int',
30             reader => '_get_data_size',
31             writer => '_set_data_size'
32             );
33             has '_data' => (
34             is => 'rw',
35             # isa => 'ArrayRef[ArrayRef]', # For Storable, now using JSON
36             isa => 'Str',
37             reader => '_get_data',
38             writer => '_set_data'
39             );
40              
41             __PACKAGE__->meta->make_immutable();
42              
43             =head1 NAME
44              
45             CHI::Driver::SharedMem - Cache data in shared memory
46              
47             =head1 VERSION
48              
49             Version 0.16
50              
51             =cut
52              
53             our $VERSION = '0.16';
54              
55             # FIXME - get the pod documentation right so that the layout of the memory
56             # area looks correct in the man page
57              
58             =head1 SYNOPSIS
59              
60             L<CHI> driver which stores data in shared memory objects for persistence
61             over processes.
62             Size is an optional parameter containing the size of the shared memory area,
63             in bytes.
64             Shmkey is a mandatory parameter containing the IPC key for the shared memory
65             area.
66             See L<IPC::SharedMem> for more information.
67              
68             use CHI;
69             my $cache = CHI->new(
70             driver => 'SharedMem',
71             size => 8 * 1024,
72             shmkey => 12344321, # Choose something unique, but the same across
73             # all caches so that namespaces will be shared,
74             # but we won't step on any other shm areas
75             );
76             # ...
77              
78             The shared memory area is stored thus:
79              
80             # Number of bytes in the cache [ int ]
81             'cache' => {
82             'namespace1' => {
83             'key1' => 'value1',
84             'key2' => 'value2',
85             # ...
86             },
87             'namespace2' => {
88             'key1' => 'value3',
89             'key3' => 'value2',
90             # ...
91             }
92             # ...
93             }
94              
95             =head1 SUBROUTINES/METHODS
96              
97             =head2 store
98              
99             Stores an object in the cache.
100             The data are serialized into JSON.
101              
102             =cut
103              
104             sub store {
105 543     543 1 297004 my($self, $key, $value) = @_;
106              
107 543         1651 $self->_lock(type => 'write');
108 543         2285 my $h = $self->_data();
109 543         37633 $h->{$self->namespace()}->{$key} = $value;
110 543         1581 $self->_data($h);
111 543         1185 $self->_unlock();
112             }
113              
114             =head2 fetch
115              
116             Retrieves an object from the cache
117              
118             =cut
119              
120             sub fetch {
121 1774     1774 1 728416 my($self, $key) = @_;
122              
123 1774         4802 $self->_lock(type => 'read');
124 1774         7578 my $rc = $self->_data()->{$self->namespace()}->{$key};
125 1774         117833 $self->_unlock();
126 1774         8380 return $rc;
127             }
128              
129             =head2 remove
130              
131             Remove an object from the cache
132              
133             =cut
134              
135             sub remove {
136 129     129 1 152034 my($self, $key) = @_;
137              
138 129         443 $self->_lock(type => 'write');
139 129         589 my $h = $self->_data();
140 129         8754 delete $h->{$self->namespace()}->{$key};
141 129         368 $self->_data($h);
142 129         334 $self->_unlock();
143              
144             # open(my $tulip, '>>', '/tmp/tulip');
145             # print $tulip "remove: $key\n";
146             }
147              
148             =head2 clear
149              
150             Removes all data from the current namespace
151              
152             =cut
153              
154             sub clear {
155 109     109 1 63642 my $self = shift;
156              
157 109         410 $self->_lock(type => 'write');
158 109         605 my $h = $self->_data();
159 109         8800 delete $h->{$self->namespace()};
160 109         396 $self->_data($h);
161 109         355 $self->_unlock();
162              
163             # open(my $tulip, '>>', '/tmp/tulip');
164             # print $tulip "clear ", $self->namespace(), "\n";
165             }
166              
167             =head2 get_keys
168              
169             Gets a list of the keys in the current namespace
170              
171             =cut
172              
173             sub get_keys {
174 123     123 1 60336 my $self = shift;
175              
176 123         368 $self->_lock(type => 'read');
177 123         542 my $h = $self->_data();
178 123         8569 $self->_unlock();
179 123         429 return(keys(%{$h->{$self->namespace()}}));
  123         1890  
180             }
181              
182             =head2 get_namespaces
183              
184             Gets a list of the namespaces in the cache
185              
186             =cut
187              
188             sub get_namespaces {
189 12     12 1 10272 my $self = shift;
190              
191 12         47 $self->_lock(type => 'read');
192 12         62 my $rc = $self->_data();
193 12         1006 $self->_unlock();
194             # Needs to be sorted for RT89892
195 12         33 my @rc = sort keys(%{$rc});
  12         84  
196 12         121 return @rc;
197             }
198              
199             # Internal routines
200              
201             # The area must be locked by the caller
202             sub _build_shm {
203 96     96   191 my $self = shift;
204              
205 96 50 33     2777 if((!defined($self->size())) || ($self->size() == 0)) {
206             # Probably some strange condition in cleanup
207             # croak 'Size == 0';
208 0         0 return;
209             }
210 96         2634 my $shm = IPC::SharedMem->new($self->shmkey(), $self->size(), S_IRUSR|S_IWUSR);
211 96 100       3114 unless($shm) {
212 4         134 $shm = IPC::SharedMem->new($self->shmkey(), $self->size(), S_IRUSR|S_IWUSR|IPC_CREAT);
213 4 50       562 unless($shm) {
214 0         0 croak "Couldn't create a shared memory area with key ",
215             $self->shmkey(), ": $!";
216 0         0 return;
217             }
218 4         74 $shm->write(pack('I', 0), 0, $Config{intsize});
219             }
220 96         843 $shm->attach();
221 96         6995 return $shm;
222             }
223              
224             sub _build_lock {
225 98     98   514467 my $self = shift;
226              
227             # open(my $fd, '<', $0) || croak("$0: $!");
228             # FIXME: make it unique for each object, not a singleton
229 98         3153 $self->lock_file('/tmp/' . __PACKAGE__);
230 98 50       3221 open(my $fd, '>', $self->lock_file()) || croak($self->lock_file(), ": $!");
231 98         728 return $fd;
232             }
233              
234             sub _lock {
235 2786     2786   7398 my ($self, %params) = @_;
236              
237 2786 50       90260 return unless $self->lock_file();
238              
239 2786 50       75861 if(my $lock = $self->lock()) {
240 2786 100       32561 flock($lock, ($params{type} eq 'read') ? Fcntl::LOCK_SH : Fcntl::LOCK_EX);
241             } else {
242             # open(my $tulip, '>>', '/tmp/tulip');
243             # print $tulip "lost lock ", $self->lock_file(), "\n";
244 0         0 croak('Lost lock: ', $self->lock_file());
245             }
246             }
247              
248             sub _unlock {
249 2786     2786   4277 my $self = shift;
250              
251 2786 50       84311 if(my $lock = $self->lock()) {
252 2786         35112 flock($lock, Fcntl::LOCK_UN);
253             }
254             }
255              
256             # The area must be locked by the caller
257             sub _data_size {
258 3567     3567   5931 my($self, $value) = @_;
259              
260 3567 100       6703 if(defined($value)) {
261 781         22197 $self->shm()->write(pack('I', $value), 0, $Config{intsize});
262 781         12984 return $value;
263             }
264 2786 50       88593 unless($self->shm()) {
265 0         0 return 0;
266             }
267 2786         72367 my $size = $self->shm()->read(0, $Config{intsize});
268 2786 50       64092 unless(defined($size)) {
269 0         0 return 0;
270             }
271 2786         8419 return unpack('I', $size);
272             }
273              
274             # The area must be locked by the caller
275             sub _data {
276 3471     3471   7372 my($self, $h) = @_;
277              
278 3471 100       8193 if(defined($h)) {
279 781         2545 my $f = JSON::MaybeXS->new()->ascii()->encode($h);
280 781         35783 my $cur_size = length($f);
281 781         25097 $self->shm()->write($f, $Config{intsize}, $cur_size);
282 781         16504 $self->_data_size($cur_size);
283             # open(my $tulip, '>>', '/tmp/tulip');
284             # print $tulip "set: $cur_size bytes\n";
285 781         1541 return $h;
286             }
287 2690         5717 my $cur_size = $self->_data_size();
288             # open(my $tulip, '>>', '/tmp/tulip');
289             # print $tulip "get: $cur_size bytes\n";
290 2690 100       6025 unless($cur_size) {
291 3         10 return {};
292             }
293 2687         9759 return JSON::MaybeXS->new()->ascii()->decode($self->shm()->read($Config{intsize}, $cur_size));
294             }
295              
296             =head2 BUILD
297              
298             Constructor - validate arguments
299              
300             =cut
301              
302             sub BUILD {
303 98     98 1 499479 my $self = shift;
304              
305 98 100       3124 unless($self->shmkey()) {
306 2         57 croak 'CHI::Driver::SharedMem - no key given';
307             }
308             }
309              
310             =head2 DEMOLISH
311              
312             If there is no data in the shared memory area, and no-one else is using it,
313             it's safe to remove it and reclaim the memory.
314              
315             =cut
316              
317             sub DEMOLISH {
318 98 50 33 98 1 106754 if(defined($^V) && ($^V ge 'v5.14.0')) {
319 98 50       378 return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; # >= 5.14.0 only
320             }
321 98         327 my $self = shift;
322              
323             # open(my $tulip, '>>', '/tmp/tulip');
324 98 100 66     3062 if($self->shmkey() && $self->shm()) {
325 96         168 my $cur_size;
326 96         293 $self->_lock(type => 'write');
327 96         487 $cur_size = $self->_data_size();
328             # print $tulip "DEMOLISH: $cur_size bytes\n";
329 96         185 my $can_remove = 0;
330 96         2712 my $stat = $self->shm()->stat();
331 96 100       13493 if($cur_size == 0) {
332 2 50 33     49 if(defined($stat) && ($stat->nattch() == 1)) {
333 2         75 $self->shm()->detach();
334 2         155 $self->shm()->remove();
335 2         211 $can_remove = 1;
336             }
337             # } elsif(defined($stat) && ($stat->nattch() == 1)) {
338             # # Scan the cache and see if all has expired.
339             # # If it has, then the cache can be removed if nattch = 1
340             # $can_remove = 1;
341             # foreach my $namespace($self->get_namespaces()) {
342             # print $tulip "DEMOLISH: namespace = $namespace\n";
343             # foreach my $key($self->get_keys($namespace)) {
344             # # May give substr error in CHI
345             # print $tulip "DEMOLISH: key = $key\n";
346             # if($self->is_valid($key)) {
347             # print $tulip "DEMOLISH: is_valid\n";
348             # $can_remove = 0;
349             # last;
350             # }
351             # }
352             # }
353             # $self->shm()->detach();
354             # if($can_remove) {
355             # $self->shm()->remove();
356             # }
357             } else {
358 94         2911 $self->shm()->detach();
359             }
360 96         4150 $self->_unlock();
361 96 100 66     2495 if($can_remove && (my $lock_file = $self->lock_file())) {
362 2         64 $self->lock_file(undef);
363 2         60 close $self->lock();
364 2         203 unlink $lock_file;
365             # print $tulip "unlink $lock_file\n";
366             }
367             }
368             }
369              
370             =head1 AUTHOR
371              
372             Nigel Horne, C<< <njh at bandsman.co.uk> >>
373              
374             =head1 BUGS
375              
376             Please report any bugs or feature requests to C<bug-chi-driver-sharedmem at rt.cpan.org>, or through
377             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CHI-Driver-SharedMem>. I will be notified, and then you'll
378             automatically be notified of progress on your bug as I make changes.
379              
380             =head1 SEE ALSO
381              
382             L<CHI>, L<IPC::SharedMem>
383              
384             =cut
385              
386             =head1 SUPPORT
387              
388             You can find documentation for this module with the perldoc command.
389              
390             perldoc CHI::Driver::SharedMemory
391              
392             You can also look for information at:
393              
394             =over 4
395              
396             =item * MetaCPAN
397              
398             L<https://metacpan.org/dist/CHI-Driver-SharedMem>
399              
400             =item * RT: CPAN's request tracker
401              
402             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=CHI-Driver-SharedMemory>
403              
404             =item * CPAN Testers' Matrix
405              
406             L<http://matrix.cpantesters.org/?dist=CHI-Driver-SharedMemory>
407              
408             =item * CPAN Testers Dependencies
409              
410             L<http://deps.cpantesters.org/?module=CHI::Driver::SharedMemory>
411              
412             =back
413              
414             =head1 LICENSE AND COPYRIGHT
415              
416             Copyright 2010-2023 Nigel Horne.
417              
418             This program is released under the following licence: GPL2
419              
420             =cut
421              
422             1;