File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 38 516 7.3
branch 0 294 0.0
condition 1 50 2.0
subroutine 14 66 21.2
pod 14 14 100.0
total 67 940 7.1


line stmt bran cond sub pod time code
1             package IPC::Shareable;
2              
3 35     35   210141 use warnings;
  35         270  
  35         1020  
4 35     35   161 use strict;
  35         56  
  35         1013  
5              
6             require 5.00503;
7              
8 35     35   160 use Carp qw(croak confess carp);
  35         70  
  35         2270  
9 35     35   12986 use Data::Dumper;
  35         147150  
  35         2053  
10 35     35   14317 use IPC::Semaphore;
  35         196365  
  35         1053  
11 35     35   14313 use IPC::Shareable::SharedMem;
  35         88  
  35         1084  
12 35         2181 use IPC::SysV qw(
13             IPC_PRIVATE
14             IPC_CREAT
15             IPC_EXCL
16             IPC_NOWAIT
17             SEM_UNDO
18 35     35   180 );
  35         60  
19 35     35   19340 use JSON qw(-convert_blessed_universally);
  35         419778  
  35         172  
20 35     35   10860 use Scalar::Util;
  35         68  
  35         1914  
21 35     35   12919 use String::CRC32;
  35         13591  
  35         2045  
22 35     35   17373 use Storable 0.6 qw(freeze thaw);
  35         89967  
  35         3458  
23              
24             our $VERSION = '1.13';
25              
26             use constant {
27             LOCK_SH => 1,
28             LOCK_EX => 2,
29             LOCK_NB => 4,
30             LOCK_UN => 8,
31              
32 35   50     199381 DEBUGGING => ($ENV{SHAREABLE_DEBUG} or 0),
33              
34             SHM_BUFSIZ => 65536,
35             SEM_MARKER => 0,
36             SHM_EXISTS => 1,
37              
38             SHMMAX_BYTES => 1073741824, # 1 GB
39              
40             # Perl sends in a double as opposed to an integer to shmat(), and on some
41             # systems, this causes the IPC system to round down to the maximum integer
42             # size of 0x80000000 we correct that when generating keys with CRC32
43              
44             MAX_KEY_INT_SIZE => 0x80000000,
45              
46             EXCLUSIVE_CHECK_LIMIT => 10, # Number of times we'll check for existing segs
47 35     35   260 };
  35         61  
48              
49             require Exporter;
50             our @ISA = 'Exporter';
51             our @EXPORT_OK = qw(LOCK_EX LOCK_SH LOCK_NB LOCK_UN);
52             our %EXPORT_TAGS = (
53             all => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
54             lock => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
55             flock => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
56             );
57             Exporter::export_ok_tags('all', 'lock', 'flock');
58              
59             # Locking scheme copied from IPC::ShareLite -- ltl
60             my %semop_args = (
61             (LOCK_EX),
62             [
63             1, 0, 0, # wait for readers to finish
64             2, 0, 0, # wait for writers to finish
65             2, 1, SEM_UNDO, # assert write lock
66             ],
67             (LOCK_EX|LOCK_NB),
68             [
69             1, 0, IPC_NOWAIT, # wait for readers to finish
70             2, 0, IPC_NOWAIT, # wait for writers to finish
71             2, 1, (SEM_UNDO | IPC_NOWAIT), # assert write lock
72             ],
73             (LOCK_EX|LOCK_UN),
74             [
75             2, -1, (SEM_UNDO | IPC_NOWAIT),
76             ],
77              
78             (LOCK_SH),
79             [
80             2, 0, 0, # wait for writers to finish
81             1, 1, SEM_UNDO, # assert shared read lock
82             ],
83             (LOCK_SH|LOCK_NB),
84             [
85             2, 0, IPC_NOWAIT, # wait for writers to finish
86             1, 1, (SEM_UNDO | IPC_NOWAIT), # assert shared read lock
87             ],
88             (LOCK_SH|LOCK_UN),
89             [
90             1, -1, (SEM_UNDO | IPC_NOWAIT), # remove shared read lock
91             ],
92             );
93              
94             my %default_options = (
95             key => IPC_PRIVATE,
96             create => 0,
97             exclusive => 0,
98             destroy => 0,
99             mode => 0666,
100             size => SHM_BUFSIZ,
101             protected => 0,
102             limit => 1,
103             graceful => 0,
104             warn => 0,
105             tidy => 0,
106             serializer => 'storable',
107             );
108              
109             my %global_register;
110             my %process_register;
111             my %used_ids;
112              
113             sub _trace;
114             sub _debug;
115              
116             # --- "Magic" methods
117             sub TIESCALAR {
118 0     0   0 return _tie('SCALAR', @_);
119             }
120             sub TIEARRAY {
121 0     0   0 return _tie('ARRAY', @_);
122             }
123             sub TIEHASH {
124 0     0   0 return _tie('HASH', @_);
125             }
126             sub STORE {
127 0     0   0 my $knot = shift;
128              
129 0 0       0 if (! exists $global_register{$knot->seg->id}) {
130 0         0 $global_register{$knot->seg->id} = $knot;
131             }
132              
133 0 0       0 $knot->{_data} = $knot->_decode($knot->seg) unless ($knot->{_lock});
134              
135 0 0       0 if ($knot->{_type} eq 'HASH') {
    0          
    0          
136 0         0 my ($key, $val) = @_;
137 0 0       0 _mg_tie($knot, $val, $key) if $knot->_need_tie($val, $key);
138 0         0 $knot->{_data}{$key} = $val;
139             }
140             elsif ($knot->{_type} eq 'ARRAY') {
141 0         0 my ($i, $val) = @_;
142 0 0       0 _mg_tie($knot, $val, $i) if $knot->_need_tie($val, $i);
143 0         0 $knot->{_data}[$i] = $val;
144             }
145             elsif ($knot->{_type} eq 'SCALAR') {
146 0         0 my ($val) = @_;
147 0 0       0 _mg_tie($knot, $val) if $knot->_need_tie($val);
148 0         0 $knot->{_data} = \$val;
149             }
150             else {
151 0         0 croak "Variables of type $knot->{_type} not supported";
152             }
153              
154 0 0       0 if ($knot->{_lock} & LOCK_EX) {
155 0         0 $knot->{_was_changed} = 1;
156             } else {
157 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
158 0         0 croak "Could not write to shared memory: $!\n";
159             }
160             }
161              
162 0         0 return 1;
163             }
164             sub FETCH {
165 0     0   0 my $knot = shift;
166              
167 0 0       0 if (! exists $global_register{$knot->seg->id}) {
168 0         0 $global_register{$knot->seg->id} = $knot;
169             }
170              
171 0         0 my $data;
172 0 0 0     0 if ($knot->{_lock} || $knot->{_iterating}) {
173 0         0 $knot->{_iterating} = 0; # In case we break out
174 0         0 $data = $knot->{_data};
175             } else {
176 0         0 $data = $knot->_decode($knot->seg);
177 0         0 $knot->{_data} = $data;
178             }
179              
180 0         0 my $val;
181              
182 0 0       0 if ($knot->{_type} eq 'HASH') {
    0          
    0          
183 0 0       0 if (defined $data) {
184 0         0 my $key = shift;
185 0         0 $val = $data->{$key};
186             } else {
187 0         0 return;
188             }
189             }
190             elsif ($knot->{_type} eq 'ARRAY') {
191 0 0       0 if (defined $data) {
192 0         0 my $i = shift;
193 0         0 $val = $data->[$i];
194             } else {
195 0         0 return;
196             }
197             }
198             elsif ($knot->{_type} eq 'SCALAR') {
199 0 0       0 if (defined $data) {
200 0         0 $val = $$data;
201             } else {
202 0         0 return;
203             }
204             }
205             else {
206 0         0 croak "Variables of type $knot->{_type} not supported";
207             }
208              
209 0 0       0 if (my $inner = _is_kid($val)) {
210 0         0 my $s = $inner->seg;
211 0         0 $inner->{_data} = $knot->_decode($s);
212             }
213 0         0 return $val;
214              
215             }
216             sub CLEAR {
217 0     0   0 my $knot = shift;
218              
219 0 0       0 if ($knot->{_type} eq 'HASH') {
    0          
220 0         0 $knot->{_data} = { };
221             }
222             elsif ($knot->{_type} eq 'ARRAY') {
223 0         0 $knot->{_data} = [ ];
224             }
225              
226             else {
227 0         0 croak "Attempt to clear non-aggegrate";
228             }
229              
230 0 0       0 if ($knot->{_lock} & LOCK_EX) {
231 0         0 $knot->{_was_changed} = 1;
232             } else {
233 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
234 0         0 croak "Could not write to shared memory: $!";
235             }
236             }
237             }
238             sub DELETE {
239 0     0   0 my $knot = shift;
240 0         0 my $key = shift;
241              
242 0 0       0 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
243 0         0 my $val = delete $knot->{_data}->{$key};
244 0 0       0 if ($knot->{_lock} & LOCK_EX) {
245 0         0 $knot->{_was_changed} = 1;
246             } else {
247 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
248 0         0 croak "Could not write to shared memory: $!";
249             }
250             }
251              
252 0         0 return $val;
253             }
254             sub EXISTS {
255 0     0   0 my $knot = shift;
256 0         0 my $key = shift;
257              
258 0 0       0 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
259 0         0 return exists $knot->{_data}->{$key};
260             }
261             sub FIRSTKEY {
262 0     0   0 my $knot = shift;
263              
264 0         0 $knot->{_iterating} = 1;
265 0 0       0 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
266 0         0 my $reset = keys %{$knot->{_data}};
  0         0  
267 0         0 my $first = each %{$knot->{_data}};
  0         0  
268 0         0 return $first;
269             }
270             sub NEXTKEY {
271 0     0   0 my $knot = shift;
272              
273             # caveat emptor if hash was changed by another process
274 0         0 my $next = each %{$knot->{_data}};
  0         0  
275 0 0       0 if (not defined $next) {
276 0         0 $knot->{_iterating} = 0;
277 0         0 return;
278             } else {
279 0         0 $knot->{_iterating} = 1;
280 0         0 return $next;
281             }
282             }
283       0     sub EXTEND {
284             #XXX Noop
285             }
286             sub PUSH {
287 0     0   0 my $knot = shift;
288              
289 0 0       0 if (! exists $global_register{$knot->seg->id}) {
290 0         0 $global_register{$knot->seg->id} = $knot;
291             }
292              
293 0 0       0 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
294              
295 0         0 push @{$knot->{_data}}, @_;
  0         0  
296 0 0       0 if ($knot->{_lock} & LOCK_EX) {
297 0         0 $knot->{_was_changed} = 1;
298             } else {
299 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
300 0         0 croak "Could not write to shared memory: $!";
301             };
302             }
303             }
304             sub POP {
305 0     0   0 my $knot = shift;
306              
307 0 0       0 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
308              
309 0         0 my $val = pop @{$knot->{_data}};
  0         0  
310 0 0       0 if ($knot->{_lock} & LOCK_EX) {
311 0         0 $knot->{_was_changed} = 1;
312             } else {
313 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
314 0         0 croak "Could not write to shared memory: $!";
315             }
316             }
317 0         0 return $val;
318             }
319             sub SHIFT {
320 0     0   0 my $knot = shift;
321              
322 0 0       0 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
323 0         0 my $val = shift @{$knot->{_data}};
  0         0  
324 0 0       0 if ($knot->{_lock} & LOCK_EX) {
325 0         0 $knot->{_was_changed} = 1;
326             } else {
327 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
328 0         0 croak "Could not write to shared memory: $!";
329             }
330             }
331 0         0 return $val;
332             }
333             sub UNSHIFT {
334 0     0   0 my $knot = shift;
335              
336 0 0       0 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
337 0         0 my $val = unshift @{$knot->{_data}}, @_;
  0         0  
338 0 0       0 if ($knot->{_lock} & LOCK_EX) {
339 0         0 $knot->{_was_changed} = 1;
340             } else {
341 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
342 0         0 croak "Could not write to shared memory: $!";
343             }
344             }
345 0         0 return $val;
346             }
347             sub SPLICE {
348 0     0   0 my($knot, $off, $n, @av) = @_;
349              
350 0 0       0 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
351 0         0 my @val = splice @{$knot->{_data}}, $off, $n, @av;
  0         0  
352 0 0       0 if ($knot->{_lock} & LOCK_EX) {
353 0         0 $knot->{_was_changed} = 1;
354             } else {
355 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
356 0         0 croak "Could not write to shared memory: $!";
357             }
358             }
359 0         0 return @val;
360             }
361             sub FETCHSIZE {
362 0     0   0 my $knot = shift;
363              
364 0 0       0 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
365 0         0 return scalar(@{$knot->{_data}});
  0         0  
366             }
367             sub STORESIZE {
368 0     0   0 my $knot = shift;
369 0         0 my $n = shift;
370              
371 0 0       0 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
372 0         0 $#{$knot->{_data}} = $n - 1;
  0         0  
373 0 0       0 if ($knot->{_lock} & LOCK_EX) {
374 0         0 $knot->{_was_changed} = 1;
375             } else {
376 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
377 0         0 croak "Could not write to shared memory: $!";
378             }
379             }
380 0         0 return $n;
381             }
382              
383             # --- Public methods
384              
385             sub new {
386 0     0 1 0 my ($class, %opts) = @_;
387              
388 0   0     0 my $type = $opts{var} || 'HASH';
389              
390 0 0       0 if ($type eq 'HASH') {
391 0         0 my $k = tie my %h, 'IPC::Shareable', \%opts;
392 0         0 return \%h;
393             }
394 0 0       0 if ($type eq 'ARRAY') {
395 0         0 my $k = tie my @a, 'IPC::Shareable', \%opts;
396 0         0 return \@a;
397             }
398 0 0       0 if ($type eq 'SCALAR') {
399 0         0 my $k = tie my $s, 'IPC::Shareable', \%opts;
400 0         0 return \$s;
401             }
402             }
403             sub global_register {
404             # This is a ridiculous way to do this, but if we don't call Dumper, hashes
405             # that are created in a separate process than the parent hash don't
406             # show up properly in the global register. t/81
407              
408             local $SIG{__WARN__} = sub {
409 0     0   0 my ($warning) = @_;
410 0 0       0 if ($warning !~ /hash after insertion/) {
411 0         0 warn $warning;
412             }
413 0     0 1 0 };
414              
415 0         0 Dumper \%global_register;
416              
417 0         0 return \%global_register;
418             }
419             sub process_register {
420 0     0 1 0 return \%process_register;
421             }
422              
423             sub attributes {
424 0     0 1 0 my ($knot, $attr) = @_;
425              
426 0         0 my $attrs = $knot->{attributes};
427              
428 0 0       0 if (defined $attr) {
429 0         0 return $knot->{attributes}{$attr};
430             }
431             else {
432 0         0 return $knot->{attributes};
433             }
434             }
435             sub ipcs {
436 0     0 1 0 my $count = `ipcs -m | wc -l`;
437 0         0 chomp $count;
438 0         0 return int($count);
439             }
440             sub lock {
441 0     0 1 0 my ($knot, $flags) = @_;
442 0 0       0 $flags = LOCK_EX if ! defined $flags;
443              
444 0 0       0 return $knot->unlock if ($flags & LOCK_UN);
445              
446 0 0       0 return 1 if ($knot->{_lock} & $flags);
447              
448             # If they have a different lock than they want, release it first
449 0 0       0 $knot->unlock if ($knot->{_lock});
450              
451 0         0 my $sem = $knot->sem;
452 0         0 my $return_val = $sem->op(@{ $semop_args{$flags} });
  0         0  
453 0 0       0 if ($return_val) {
454 0         0 $knot->{_lock} = $flags;
455 0         0 $knot->{_data} = $knot->_decode($knot->seg),
456             }
457 0         0 return $return_val;
458             }
459             sub unlock {
460 0     0 1 0 my $knot = shift;
461              
462 0 0       0 return 1 unless $knot->{_lock};
463 0 0       0 if ($knot->{_was_changed}) {
464 0 0       0 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
465 0         0 croak "Could not write to shared memory: $!\n";
466             }
467 0         0 $knot->{_was_changed} = 0;
468             }
469 0         0 my $sem = $knot->sem;
470 0         0 my $flags = $knot->{_lock} | LOCK_UN;
471 0 0       0 $flags ^= LOCK_NB if ($flags & LOCK_NB);
472 0         0 $sem->op(@{ $semop_args{$flags} });
  0         0  
473              
474 0         0 $knot->{_lock} = 0;
475              
476 0         0 1;
477             }
478             *shlock = \&lock;
479             *shunlock = \&unlock;
480              
481             sub clean_up {
482 0     0 1 0 my $class = shift;
483              
484 0         0 for my $id (keys %process_register) {
485 0         0 my $s = $process_register{$id};
486 0 0       0 next unless $s->attributes('owner') == $$;
487 0 0       0 next if $s->attributes('protected');
488 0         0 remove($s);
489             }
490             }
491             sub clean_up_all {
492 0     0 1 0 my $class = shift;
493              
494 0         0 my $global_register = __PACKAGE__->global_register;
495              
496 0         0 for my $id (keys %$global_register) {
497 0         0 my $s = $global_register->{$id};
498 0 0       0 next if $s->attributes('protected');
499 0         0 remove($s);
500             }
501             }
502             sub clean_up_protected {
503 0     0 1 0 my ($knot, $protect_key);
504              
505 0 0       0 if (scalar @_ == 2) {
506 0         0 ($knot, $protect_key) = @_;
507             }
508 0 0       0 if (scalar @_ == 1) {
509 0         0 ($protect_key) = @_;
510             }
511              
512 0 0       0 if (! defined $protect_key) {
513 0         0 croak "clean_up_protected() requires a \$protect_key param";
514             }
515              
516 0 0       0 if ($protect_key !~ /^\d+$/) {
517 0         0 croak
518             "clean_up_protected() \$protect_key must be an integer. You sent $protect_key";
519             }
520              
521 0         0 my $global_register = __PACKAGE__->global_register;
522              
523 0         0 for my $id (keys %$global_register) {
524 0         0 my $s = $global_register->{$id};
525 0         0 my $stored_key = $s->attributes('protected');
526              
527 0 0 0     0 if ($stored_key && $stored_key == $protect_key) {
528 0         0 remove($s);
529             }
530             }
531             }
532             sub remove {
533 0     0 1 0 my $knot = shift;
534              
535 0         0 my $s = $knot->seg;
536 0         0 my $id = $s->id;
537              
538 0 0       0 $s->remove or warn "Couldn't remove shared memory segment $id: $!";
539              
540 0         0 $s = $knot->sem;
541              
542 0 0       0 $s->remove or warn "Couldn't remove semaphore set $id: $!";
543              
544 0         0 delete $process_register{$id};
545 0         0 delete $global_register{$id};
546             }
547             sub seg {
548 0     0 1 0 my ($knot) = @_;
549 0 0       0 return $knot->{_shm} if defined $knot->{_shm};
550             }
551             sub sem {
552 0     0 1 0 my ($knot) = @_;
553 0 0       0 return $knot->{_sem} if defined $knot->{_sem};
554             }
555             sub singleton {
556              
557             # If called with IPC::Shareable::singleton() as opposed to
558             # IPC::Shareable->singleton(), the class isn't sent in. Check
559             # for this and fix it if necessary
560              
561 0 0 0 0 1 0 if (! defined $_[0] || $_[0] ne __PACKAGE__) {
562 0         0 unshift @_, __PACKAGE__;
563             }
564              
565 0         0 my ($class, $glue, $warn) = @_;
566              
567 0 0       0 if (! defined $glue) {
568 0         0 croak "singleton() requires a GLUE parameter";
569             }
570              
571 0 0       0 $warn = 0 if ! defined $warn;
572              
573 0         0 tie my $lock, 'IPC::Shareable', {
574             key => $glue,
575             create => 1,
576             exclusive => 1,
577             graceful => 1,
578             destroy => 1,
579             warn => $warn
580             };
581              
582 0         0 return $$;
583             }
584              
585             END {
586 35     35   20709 _end();
587             }
588              
589             # --- Private methods below
590              
591             sub _encode {
592 0     0   0 my ($knot, $seg, $data) = @_;
593              
594 0         0 my $serializer = $knot->attributes('serializer');
595              
596 0 0       0 if ($serializer eq 'storable') {
    0          
597 0         0 return _freeze($seg, $data);
598             }
599             elsif ($serializer eq 'json'){
600 0         0 return _encode_json($seg, $data);
601             }
602              
603 0         0 return undef;
604             }
605             sub _end {
606 35     35   40 for my $s (values %process_register) {
607 0           unlock($s);
608 0 0         next if $s->attributes('protected');
609 0 0         next if ! $s->attributes('destroy');
610 0 0         next if $s->attributes('owner') != $$;
611 0           remove($s);
612             }
613             }
614             sub _decode {
615 0     0     my ($knot, $seg) = @_;
616              
617 0           my $serializer = $knot->attributes('serializer');
618              
619 0 0         if ($serializer eq 'storable') {
    0          
620 0           return _thaw($seg);
621             }
622             elsif ($serializer eq 'json'){
623 0           return _decode_json($seg);
624             }
625              
626 0           return undef;
627             }
628             sub _encode_json {
629 0     0     my $seg = shift;
630 0           my $data = shift;
631              
632 0           my $json = encode_json $data;
633              
634 0 0         if (length($json) > $seg->size) {
635 0           croak "Length of shared data exceeds shared segment size";
636             }
637 0           $seg->shmwrite($json);
638             }
639             sub _decode_json {
640 0     0     my $seg = shift;
641              
642 0           my $json = $seg->shmread;
643              
644 0 0         return if ! $json;
645              
646             # Remove \x{0} after end of string (broke JSON)
647              
648 0           $json =~ s/\x00+//;
649              
650             # my $tag = substr $json, 0, 14, '';
651              
652             # if ($tag eq 'IPC::Shareable') {
653 0           my $data = decode_json $json;
654 0 0         if (! defined($data)){
655 0           croak "Munged shared memory segment (size exceeded?)";
656             }
657 0           return $data;
658             # } else {
659             # return;
660             # }
661             }
662             sub _freeze {
663 0     0     my $seg = shift;
664 0           my $water = shift;
665              
666 0           my $ice = freeze $water;
667             # Could be a large string. No need to copy it. substr more efficient
668 0           substr $ice, 0, 0, 'IPC::Shareable';
669              
670 0 0         if (length($ice) > $seg->size) {
671 0           croak "Length of shared data exceeds shared segment size";
672             }
673 0           $seg->shmwrite($ice);
674             }
675             sub _thaw {
676 0     0     my $seg = shift;
677              
678 0           my $ice = $seg->shmread;
679              
680 0 0         return if ! $ice;
681              
682 0           my $tag = substr $ice, 0, 14, '';
683              
684 0 0         if ($tag eq 'IPC::Shareable') {
685 0           my $water = thaw $ice;
686 0 0         if (! defined($water)){
687 0           croak "Munged shared memory segment (size exceeded?)";
688             }
689 0           return $water;
690             } else {
691 0           return;
692             }
693             }
694             sub _tie {
695 0     0     my ($type, $class, $key_str, $opts);
696              
697 0 0         if (scalar @_ == 4) {
698 0           ($type, $class, $key_str, $opts) = @_;
699 0           $opts->{key} = $key_str;
700             }
701             else {
702 0           ($type, $class, $opts) = @_;
703             }
704              
705 0           $opts = _parse_args($opts);
706              
707 0           my $knot = bless { attributes => $opts }, $class;
708              
709 0           my $key = $knot->_shm_key;
710 0           my $flags = $knot->_shm_flags;
711 0           my $shm_size = $knot->attributes('size');
712              
713 0 0 0       if ($knot->attributes('limit') && $shm_size > SHMMAX_BYTES) {
714 0           croak
715             "Shared memory segment size '$shm_size' is larger than max size of " .
716             SHMMAX_BYTES;
717             }
718              
719 0           my $seg;
720              
721 0 0         if ($knot->attributes('graceful')) {
722 0           my $exclusive = eval {
723 0           $seg = IPC::Shareable::SharedMem->new($key, $shm_size, $flags);
724 0           1;
725             };
726              
727 0 0         if (! defined $exclusive) {
728 0 0         if ($knot->attributes('warn')) {
729 0           my $key = lc(sprintf("0x%X", $knot->_shm_key));
730              
731 0           warn "Process ID $$ exited due to exclusive shared memory collision at segment/semaphore key '$key'\n";
732             }
733 0           exit(0);
734             }
735             }
736             else {
737 0           $seg = IPC::Shareable::SharedMem->new($key, $shm_size, $flags);
738             }
739              
740 0 0         if (! defined $seg) {
741 0 0         if ($! =~ /Cannot allocate memory/) {
742 0           croak "\nERROR: Could not create shared memory segment: $!\n\n" .
743             "Are you using too large a size?";
744             }
745              
746 0 0         if ($! =~ /No space left on device/) {
747 0           croak "\nERROR: Could not create shared memory segment: $!\n\n" .
748             "Are you spawning too many segments in a loop?";
749             }
750              
751 0 0 0       if (! $knot->attributes('create')) {
    0          
752 0           confess "ERROR: Could not acquire shared memory segment... 'create' ".
753             "option is not set, and the segment hasn't been created " .
754             "yet:\n\n $!";
755             }
756             elsif ($knot->attributes('create') && $knot->attributes('exclusive')){
757 0           croak "ERROR: Could not create shared memory segment. 'create' " .
758             "and 'exclusive' are set. Does the segment already exist? " .
759             "\n\n$!";
760             }
761             else {
762 0           croak "ERROR: Could not create shared memory segment.\n\n$!";
763             }
764             }
765              
766 0           my $sem = IPC::Semaphore->new($key, 3, $flags);
767 0 0         if (! defined $sem){
768 0           croak "Could not create semaphore set: $!\n";
769             }
770              
771 0 0         if (! $sem->op(@{ $semop_args{(LOCK_SH)} }) ) {
  0            
772 0           croak "Could not obtain semaphore set lock: $!\n";
773             }
774              
775 0           %$knot = (
776             %$knot,
777             _iterating => 0,
778             _key => $key,
779             _lock => 0,
780             _shm => $seg,
781             _sem => $sem,
782             _type => $type,
783             _was_changed => 0,
784             );
785              
786 0           $knot->{_data} = _thaw($seg);
787              
788 0 0         if ($sem->getval(SEM_MARKER) != SHM_EXISTS) {
789              
790 0 0         if (! exists $global_register{$knot->seg->id}) {
791 0           $global_register{$knot->seg->id} = $knot;
792             }
793              
794 0   0       $process_register{$knot->seg->id} ||= $knot;
795 0 0         if (! $sem->setval(SEM_MARKER, SHM_EXISTS)){
796 0           croak "Couldn't set semaphore during object creation: $!";
797             }
798             }
799              
800 0           $sem->op(@{ $semop_args{(LOCK_SH|LOCK_UN)} });
  0            
801              
802 0           return $knot;
803             }
804             sub _parse_args {
805 0     0     my ($opts) = @_;
806              
807 0 0         $opts = defined $opts ? $opts : { %default_options };
808              
809 0           for my $k (keys %default_options) {
810 0 0         if (not defined $opts->{$k}) {
    0          
811 0           $opts->{$k} = $default_options{$k};
812             }
813             elsif ($opts->{$k} eq 'no') {
814 0 0         if ($^W) {
815 0           require Carp;
816 0           Carp::carp("Use of `no' in IPC::Shareable args is obsolete");
817             }
818              
819 0           $opts->{$k} = 0;
820             }
821             }
822 0   0       $opts->{owner} = ($opts->{owner} or $$);
823 0   0       $opts->{magic} = ($opts->{magic} or 0);
824 0           return $opts;
825             }
826             sub _shm_key {
827             # Generates a 32-bit CRC on the key string. The $key_str parameter is used
828             # for testing only, for purposes of testing various key strings
829              
830 0     0     my ($knot, $key_str) = @_;
831              
832 0   0       $key_str //= ($knot->attributes('key') || '');
      0        
833              
834 0           my $key;
835              
836 0 0         if ($key_str eq '') {
    0          
837 0           $key = IPC_PRIVATE;
838             }
839             elsif ($key_str =~ /^\d+$/) {
840 0           $key = $key_str;
841             }
842             else {
843 0           $key = crc32($key_str);
844             }
845              
846 0           $used_ids{$key}++;
847              
848 0 0         if ($key > MAX_KEY_INT_SIZE) {
849 0           $key = $key - MAX_KEY_INT_SIZE;
850              
851 0 0         if ($key == 0) {
852 0           croak "We've calculated a key which equals 0. This is a fatal error";
853             }
854             }
855              
856 0           return $key;
857             }
858             sub _shm_key_rand {
859 0     0     my $key;
860              
861             # Unfortunatly, the only way I know how to check if a segment exists is
862             # to actually create it. We must do that here, then remove it just to
863             # ensure the slot is available
864              
865 0           my $verified_exclusive = 0;
866              
867 0           my $check_count = 0;
868              
869 0   0       while (! $verified_exclusive && $check_count < EXCLUSIVE_CHECK_LIMIT) {
870 0           $check_count++;
871              
872 0           $key = _shm_key_rand_int();
873              
874 0 0         next if $used_ids{$key};
875              
876 0           my $flags;
877 0           $flags |= IPC_CREAT;
878 0           $flags |= IPC_EXCL;
879              
880 0           my $seg;
881              
882 0           my $shm_slot_available = eval {
883 0           $seg = IPC::Shareable::SharedMem->new($key, 1, $flags);
884 0           1;
885             };
886              
887 0 0         if ($shm_slot_available) {
888 0           $verified_exclusive = 1;
889 0 0         $seg->remove if $seg;
890             }
891             }
892              
893 0 0         if (! $verified_exclusive) {
894 0           croak
895             "_shm_key_rand() can't get an available key after $check_count tries";
896             }
897              
898 0           $used_ids{$key}++;
899              
900 0           return $key;
901             }
902             sub _shm_key_rand_int {
903 0     0     srand();
904 0           return int(rand(1_000_000));
905             }
906             sub _shm_flags {
907             # --- Parses the anonymous hash passed to constructors; returns a list
908             # --- of args suitable for passing to shmget
909 0     0     my ($knot) = @_;
910              
911 0           my $flags = 0;
912              
913 0 0         $flags |= IPC_CREAT if $knot->attributes('create');
914 0 0         $flags |= IPC_EXCL if $knot->attributes('exclusive');;
915 0   0       $flags |= ($knot->attributes('mode') or 0666);
916              
917 0           return $flags;
918             }
919             sub _mg_tie {
920 0     0     my ($parent, $val, $identifier) = @_;
921              
922 0           my $key;
923              
924 0 0         if ($parent->{_key} == IPC_PRIVATE) {
925 0           $key = IPC_PRIVATE;
926             }
927             else {
928 0           $key = _shm_key_rand();
929             }
930              
931             my %opts = (
932 0           %{ $parent->attributes },
  0            
933             key => $key,
934             exclusive => 1,
935             create => 1,
936             magic => 1,
937             );
938              
939             # XXX I wish I didn't have to take a copy of data here and copy it back in
940             # XXX Also, have to peek inside potential objects to see their implementation
941 0           my $child;
942 0   0       my $type = Scalar::Util::reftype($val) || '';
943              
944 0 0         if ($type eq "HASH") {
    0          
    0          
945 0           my %copy = %$val;
946 0           $child = tie %$val, 'IPC::Shareable', $key, { %opts };
947 0 0         croak "Could not create inner tie" if ! $child;
948              
949 0 0         _reset_segment($parent, $identifier) if $opts{tidy};
950              
951 0           %$val = %copy;
952             }
953             elsif ($type eq "ARRAY") {
954 0           my @copy = @$val;
955 0           $child = tie @$val, 'IPC::Shareable', $key, { %opts };
956 0 0         croak "Could not create inner tie" if ! $child;
957              
958 0 0         _reset_segment($parent, $identifier) if $opts{tidy};
959              
960 0           @$val = @copy;
961             }
962             elsif ($type eq "SCALAR") {
963 0           my $copy = $$val;
964 0           $child = tie $$val, 'IPC::Shareable', $key, { %opts };
965 0 0         croak "Could not create inner tie" if ! $child;
966              
967 0           $$val = $copy;
968             }
969             else {
970 0           croak "Variables of type $type not implemented";
971             }
972              
973 0           return $child;
974             }
975             sub _is_kid {
976 0 0   0     my $data = shift or return;
977              
978 0           my $type = Scalar::Util::reftype( $data );
979 0 0         return unless $type;
980              
981 0           my $obj;
982              
983 0 0         if ($type eq "HASH") {
    0          
    0          
984 0           $obj = tied %$data;
985             }
986             elsif ($type eq "ARRAY") {
987 0           $obj = tied @$data;
988             }
989             elsif ($type eq "SCALAR") {
990 0           $obj = tied $$data;
991             }
992              
993 0 0         if (ref $obj eq 'IPC::Shareable') {
994 0           return $obj;
995             }
996              
997 0           return;
998             }
999             sub _need_tie {
1000 0     0     my ($knot, $val, $identifier) = @_;
1001              
1002 0           my $type = Scalar::Util::reftype($val);
1003 0 0         return 0 if ! $type;
1004              
1005 0           my $need_tie;
1006              
1007 0 0         if ($type eq "HASH") {
    0          
    0          
1008 0           $need_tie = !(tied %$val);
1009             }
1010             elsif ($type eq "ARRAY") {
1011 0           $need_tie = !(tied @$val);
1012             }
1013             elsif ($type eq "SCALAR") {
1014 0           $need_tie = !(tied $$val);
1015             }
1016              
1017 0 0         return $need_tie ? 1 : 0;
1018             }
1019             sub _reset_segment {
1020 0     0     my ($parent, $id) = @_;
1021              
1022 0   0       my $parent_type = Scalar::Util::reftype($parent->{_data}) || '';
1023              
1024 0 0         if ($parent_type eq 'HASH') {
    0          
1025 0           my $data = $parent->{_data};
1026 0 0 0       if (exists $data->{$id} && keys %{ $data->{$id} } && tied %{ $data->{$id} }) {
  0   0        
  0            
1027 0           (tied %{ $parent->{_data}{$id} })->remove;
  0            
1028             }
1029             }
1030             elsif ($parent_type eq 'ARRAY') {
1031 0           my $data = $parent->{_data};
1032 0 0 0       if (exists $data->[$id] && tied @{ $data->[$id] }) {
  0            
1033 0           (tied @{ $parent->{_data}[$id] })->remove;
  0            
1034             }
1035             }
1036             }
1037              
1038             sub _trace {
1039 0     0     require Carp;
1040 0           require Data::Dumper;
1041 0           my $caller = ' ' . (caller(1))[3] . " called with:\n";
1042 0           my $i = -1;
1043             my @msg = map {
1044 0           ++$i;
  0            
1045 0           my $obj;
1046 0 0         if (ref eq 'IPC::Shareable') {
1047 0           ' ' . "\$_[$i] = $_: shmid: $_->{_shm}->{_id}; " .
1048             Data::Dumper->Dump([ $_->attributes ], [ 'opts' ]);
1049             } else {
1050 0           ' ' . Data::Dumper->Dump( [ $_ ] => [ "\_[$i]" ]);
1051             }
1052             } @_;
1053 0           Carp::carp "IPC::Shareable ($$) debug:\n", $caller, @msg;
1054             }
1055             sub _debug {
1056 0     0     require Carp;
1057 0           require Data::Dumper;
1058 0           local $Data::Dumper::Terse = 1;
1059 0           my $caller = ' ' . (caller(1))[3] . " tells us that:\n";
1060             my @msg = map {
1061 0           my $obj;
  0            
1062 0 0         if (ref eq 'IPC::Shareable') {
1063 0           ' ' . "$_: shmid: $_->{_shm}->{_id}; " .
1064             Data::Dumper->Dump([ $_->attributes ], [ 'opts' ]);
1065             }
1066             else {
1067 0           ' ' . Data::Dumper::Dumper($_);
1068             }
1069             } @_;
1070 0           Carp::carp "IPC::Shareable ($$) debug:\n", $caller, @msg;
1071             }
1072       0     sub _placeholder {}
1073              
1074             1;
1075              
1076             __END__