File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 38 513 7.4
branch 0 292 0.0
condition 1 47 2.1
subroutine 14 66 21.2
pod 14 14 100.0
total 67 932 7.1


line stmt bran cond sub pod time code
1             package IPC::Shareable;
2              
3 34     34   197674 use warnings;
  34         250  
  34         944  
4 34     34   151 use strict;
  34         61  
  34         965  
5              
6             require 5.00503;
7              
8 34     34   148 use Carp qw(croak confess carp);
  34         63  
  34         2161  
9 34     34   12343 use Data::Dumper;
  34         140210  
  34         1985  
10 34     34   13711 use IPC::Semaphore;
  34         185807  
  34         1029  
11 34     34   13359 use IPC::Shareable::SharedMem;
  34         79  
  34         980  
12 34         1896 use IPC::SysV qw(
13             IPC_PRIVATE
14             IPC_CREAT
15             IPC_EXCL
16             IPC_NOWAIT
17             SEM_UNDO
18 34     34   184 );
  34         57  
19 34     34   18287 use JSON qw(-convert_blessed_universally);
  34         402988  
  34         167  
20 34     34   10345 use Scalar::Util;
  34         68  
  34         1910  
21 34     34   12656 use String::CRC32;
  34         12790  
  34         1847  
22 34     34   17032 use Storable 0.6 qw(freeze thaw);
  34         87465  
  34         3467  
23              
24             our $VERSION = '1.12';
25              
26             use constant {
27             LOCK_SH => 1,
28             LOCK_EX => 2,
29             LOCK_NB => 4,
30             LOCK_UN => 8,
31              
32 34   50     192607 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 34     34   262 };
  34         71  
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 0     0 1 0 my ($class, $glue, $warn) = @_;
557              
558 0 0       0 if (! defined $glue) {
559 0         0 croak "singleton() requires a GLUE parameter";
560             }
561              
562 0 0       0 $warn = 0 if ! defined $warn;
563              
564 0         0 tie my $lock, 'IPC::Shareable', {
565             key => $glue,
566             create => 1,
567             exclusive => 1,
568             graceful => 1,
569             destroy => 1,
570             warn => $warn
571             };
572              
573 0         0 return $$;
574             }
575              
576             END {
577 34     34   19322 _end();
578             }
579              
580             # --- Private methods below
581              
582             sub _encode {
583 0     0   0 my ($knot, $seg, $data) = @_;
584              
585 0         0 my $serializer = $knot->attributes('serializer');
586              
587 0 0       0 if ($serializer eq 'storable') {
    0          
588 0         0 return _freeze($seg, $data);
589             }
590             elsif ($serializer eq 'json'){
591 0         0 return _encode_json($seg, $data);
592             }
593              
594 0         0 return undef;
595             }
596             sub _end {
597 34     34   36 for my $s (values %process_register) {
598 0           unlock($s);
599 0 0         next if $s->attributes('protected');
600 0 0         next if ! $s->attributes('destroy');
601 0 0         next if $s->attributes('owner') != $$;
602 0           remove($s);
603             }
604             }
605             sub _decode {
606 0     0     my ($knot, $seg) = @_;
607              
608 0           my $serializer = $knot->attributes('serializer');
609              
610 0 0         if ($serializer eq 'storable') {
    0          
611 0           return _thaw($seg);
612             }
613             elsif ($serializer eq 'json'){
614 0           return _decode_json($seg);
615             }
616              
617 0           return undef;
618             }
619             sub _encode_json {
620 0     0     my $seg = shift;
621 0           my $data = shift;
622              
623 0           my $json = encode_json $data;
624              
625 0 0         if (length($json) > $seg->size) {
626 0           croak "Length of shared data exceeds shared segment size";
627             }
628 0           $seg->shmwrite($json);
629             }
630             sub _decode_json {
631 0     0     my $seg = shift;
632              
633 0           my $json = $seg->shmread;
634              
635 0 0         return if ! $json;
636              
637             # Remove \x{0} after end of string (broke JSON)
638              
639 0           $json =~ s/\x00+//;
640              
641             # my $tag = substr $json, 0, 14, '';
642              
643             # if ($tag eq 'IPC::Shareable') {
644 0           my $data = decode_json $json;
645 0 0         if (! defined($data)){
646 0           croak "Munged shared memory segment (size exceeded?)";
647             }
648 0           return $data;
649             # } else {
650             # return;
651             # }
652             }
653             sub _freeze {
654 0     0     my $seg = shift;
655 0           my $water = shift;
656              
657 0           my $ice = freeze $water;
658             # Could be a large string. No need to copy it. substr more efficient
659 0           substr $ice, 0, 0, 'IPC::Shareable';
660              
661 0 0         if (length($ice) > $seg->size) {
662 0           croak "Length of shared data exceeds shared segment size";
663             }
664 0           $seg->shmwrite($ice);
665             }
666             sub _thaw {
667 0     0     my $seg = shift;
668              
669 0           my $ice = $seg->shmread;
670              
671 0 0         return if ! $ice;
672              
673 0           my $tag = substr $ice, 0, 14, '';
674              
675 0 0         if ($tag eq 'IPC::Shareable') {
676 0           my $water = thaw $ice;
677 0 0         if (! defined($water)){
678 0           croak "Munged shared memory segment (size exceeded?)";
679             }
680 0           return $water;
681             } else {
682 0           return;
683             }
684             }
685             sub _tie {
686 0     0     my ($type, $class, $key_str, $opts);
687              
688 0 0         if (scalar @_ == 4) {
689 0           ($type, $class, $key_str, $opts) = @_;
690 0           $opts->{key} = $key_str;
691             }
692             else {
693 0           ($type, $class, $opts) = @_;
694             }
695              
696 0           $opts = _parse_args($opts);
697              
698 0           my $knot = bless { attributes => $opts }, $class;
699              
700 0           my $key = $knot->_shm_key;
701 0           my $flags = $knot->_shm_flags;
702 0           my $shm_size = $knot->attributes('size');
703              
704 0 0 0       if ($knot->attributes('limit') && $shm_size > SHMMAX_BYTES) {
705 0           croak
706             "Shared memory segment size '$shm_size' is larger than max size of " .
707             SHMMAX_BYTES;
708             }
709              
710 0           my $seg;
711              
712 0 0         if ($knot->attributes('graceful')) {
713 0           my $exclusive = eval {
714 0           $seg = IPC::Shareable::SharedMem->new($key, $shm_size, $flags);
715 0           1;
716             };
717              
718 0 0         if (! defined $exclusive) {
719 0 0         if ($knot->attributes('warn')) {
720 0           warn "Process ID $$ exited due to exclusive shared memory collision\n";
721             }
722 0           exit(0);
723             }
724             }
725             else {
726 0           $seg = IPC::Shareable::SharedMem->new($key, $shm_size, $flags);
727             }
728              
729 0 0         if (! defined $seg) {
730 0 0         if ($! =~ /Cannot allocate memory/) {
731 0           croak "\nERROR: Could not create shared memory segment: $!\n\n" .
732             "Are you using too large a size?";
733             }
734              
735 0 0         if ($! =~ /No space left on device/) {
736 0           croak "\nERROR: Could not create shared memory segment: $!\n\n" .
737             "Are you spawning too many segments in a loop?";
738             }
739              
740 0 0 0       if (! $knot->attributes('create')) {
    0          
741 0           confess "ERROR: Could not acquire shared memory segment... 'create' ".
742             "option is not set, and the segment hasn't been created " .
743             "yet:\n\n $!";
744             }
745             elsif ($knot->attributes('create') && $knot->attributes('exclusive')){
746 0           croak "ERROR: Could not create shared memory segment. 'create' " .
747             "and 'exclusive' are set. Does the segment already exist? " .
748             "\n\n$!";
749             }
750             else {
751 0           croak "ERROR: Could not create shared memory segment.\n\n$!";
752             }
753             }
754              
755 0           my $sem = IPC::Semaphore->new($key, 3, $flags);
756 0 0         if (! defined $sem){
757 0           croak "Could not create semaphore set: $!\n";
758             }
759              
760 0 0         if (! $sem->op(@{ $semop_args{(LOCK_SH)} }) ) {
  0            
761 0           croak "Could not obtain semaphore set lock: $!\n";
762             }
763              
764 0           %$knot = (
765             %$knot,
766             _iterating => 0,
767             _key => $key,
768             _lock => 0,
769             _shm => $seg,
770             _sem => $sem,
771             _type => $type,
772             _was_changed => 0,
773             );
774              
775 0           $knot->{_data} = _thaw($seg);
776              
777 0 0         if ($sem->getval(SEM_MARKER) != SHM_EXISTS) {
778              
779 0 0         if (! exists $global_register{$knot->seg->id}) {
780 0           $global_register{$knot->seg->id} = $knot;
781             }
782              
783 0   0       $process_register{$knot->seg->id} ||= $knot;
784 0 0         if (! $sem->setval(SEM_MARKER, SHM_EXISTS)){
785 0           croak "Couldn't set semaphore during object creation: $!";
786             }
787             }
788              
789 0           $sem->op(@{ $semop_args{(LOCK_SH|LOCK_UN)} });
  0            
790              
791 0           return $knot;
792             }
793             sub _parse_args {
794 0     0     my ($opts) = @_;
795              
796 0 0         $opts = defined $opts ? $opts : { %default_options };
797              
798 0           for my $k (keys %default_options) {
799 0 0         if (not defined $opts->{$k}) {
    0          
800 0           $opts->{$k} = $default_options{$k};
801             }
802             elsif ($opts->{$k} eq 'no') {
803 0 0         if ($^W) {
804 0           require Carp;
805 0           Carp::carp("Use of `no' in IPC::Shareable args is obsolete");
806             }
807              
808 0           $opts->{$k} = 0;
809             }
810             }
811 0   0       $opts->{owner} = ($opts->{owner} or $$);
812 0   0       $opts->{magic} = ($opts->{magic} or 0);
813 0           return $opts;
814             }
815             sub _shm_key {
816             # Generates a 32-bit CRC on the key string. The $key_str parameter is used
817             # for testing only, for purposes of testing various key strings
818              
819 0     0     my ($knot, $key_str) = @_;
820              
821 0   0       $key_str //= ($knot->attributes('key') || '');
      0        
822              
823 0           my $key;
824              
825 0 0         if ($key_str eq '') {
    0          
826 0           $key = IPC_PRIVATE;
827             }
828             elsif ($key_str =~ /^\d+$/) {
829 0           $key = $key_str;
830             }
831             else {
832 0           $key = crc32($key_str);
833             }
834              
835 0           $used_ids{$key}++;
836              
837 0 0         if ($key > MAX_KEY_INT_SIZE) {
838 0           $key = $key - MAX_KEY_INT_SIZE;
839              
840 0 0         if ($key == 0) {
841 0           croak "We've calculated a key which equals 0. This is a fatal error";
842             }
843             }
844              
845 0           return $key;
846             }
847             sub _shm_key_rand {
848 0     0     my $key;
849              
850             # Unfortunatly, the only way I know how to check if a segment exists is
851             # to actually create it. We must do that here, then remove it just to
852             # ensure the slot is available
853              
854 0           my $verified_exclusive = 0;
855              
856 0           my $check_count = 0;
857              
858 0   0       while (! $verified_exclusive && $check_count < EXCLUSIVE_CHECK_LIMIT) {
859 0           $check_count++;
860              
861 0           $key = _shm_key_rand_int();
862              
863 0 0         next if $used_ids{$key};
864              
865 0           my $flags;
866 0           $flags |= IPC_CREAT;
867 0           $flags |= IPC_EXCL;
868              
869 0           my $seg;
870              
871 0           my $shm_slot_available = eval {
872 0           $seg = IPC::Shareable::SharedMem->new($key, 1, $flags);
873 0           1;
874             };
875              
876 0 0         if ($shm_slot_available) {
877 0           $verified_exclusive = 1;
878 0 0         $seg->remove if $seg;
879             }
880             }
881              
882 0 0         if (! $verified_exclusive) {
883 0           croak
884             "_shm_key_rand() can't get an available key after $check_count tries";
885             }
886              
887 0           $used_ids{$key}++;
888              
889 0           return $key;
890             }
891             sub _shm_key_rand_int {
892 0     0     srand();
893 0           return int(rand(1_000_000));
894             }
895             sub _shm_flags {
896             # --- Parses the anonymous hash passed to constructors; returns a list
897             # --- of args suitable for passing to shmget
898 0     0     my ($knot) = @_;
899              
900 0           my $flags = 0;
901              
902 0 0         $flags |= IPC_CREAT if $knot->attributes('create');
903 0 0         $flags |= IPC_EXCL if $knot->attributes('exclusive');;
904 0   0       $flags |= ($knot->attributes('mode') or 0666);
905              
906 0           return $flags;
907             }
908             sub _mg_tie {
909 0     0     my ($parent, $val, $identifier) = @_;
910              
911 0           my $key;
912              
913 0 0         if ($parent->{_key} == IPC_PRIVATE) {
914 0           $key = IPC_PRIVATE;
915             }
916             else {
917 0           $key = _shm_key_rand();
918             }
919              
920             my %opts = (
921 0           %{ $parent->attributes },
  0            
922             key => $key,
923             exclusive => 1,
924             create => 1,
925             magic => 1,
926             );
927              
928             # XXX I wish I didn't have to take a copy of data here and copy it back in
929             # XXX Also, have to peek inside potential objects to see their implementation
930 0           my $child;
931 0   0       my $type = Scalar::Util::reftype($val) || '';
932              
933 0 0         if ($type eq "HASH") {
    0          
    0          
934 0           my %copy = %$val;
935 0           $child = tie %$val, 'IPC::Shareable', $key, { %opts };
936 0 0         croak "Could not create inner tie" if ! $child;
937              
938 0 0         _reset_segment($parent, $identifier) if $opts{tidy};
939              
940 0           %$val = %copy;
941             }
942             elsif ($type eq "ARRAY") {
943 0           my @copy = @$val;
944 0           $child = tie @$val, 'IPC::Shareable', $key, { %opts };
945 0 0         croak "Could not create inner tie" if ! $child;
946              
947 0 0         _reset_segment($parent, $identifier) if $opts{tidy};
948              
949 0           @$val = @copy;
950             }
951             elsif ($type eq "SCALAR") {
952 0           my $copy = $$val;
953 0           $child = tie $$val, 'IPC::Shareable', $key, { %opts };
954 0 0         croak "Could not create inner tie" if ! $child;
955              
956 0           $$val = $copy;
957             }
958             else {
959 0           croak "Variables of type $type not implemented";
960             }
961              
962 0           return $child;
963             }
964             sub _is_kid {
965 0 0   0     my $data = shift or return;
966              
967 0           my $type = Scalar::Util::reftype( $data );
968 0 0         return unless $type;
969              
970 0           my $obj;
971              
972 0 0         if ($type eq "HASH") {
    0          
    0          
973 0           $obj = tied %$data;
974             }
975             elsif ($type eq "ARRAY") {
976 0           $obj = tied @$data;
977             }
978             elsif ($type eq "SCALAR") {
979 0           $obj = tied $$data;
980             }
981              
982 0 0         if (ref $obj eq 'IPC::Shareable') {
983 0           return $obj;
984             }
985              
986 0           return;
987             }
988             sub _need_tie {
989 0     0     my ($knot, $val, $identifier) = @_;
990              
991 0           my $type = Scalar::Util::reftype($val);
992 0 0         return 0 if ! $type;
993              
994 0           my $need_tie;
995              
996 0 0         if ($type eq "HASH") {
    0          
    0          
997 0           $need_tie = !(tied %$val);
998             }
999             elsif ($type eq "ARRAY") {
1000 0           $need_tie = !(tied @$val);
1001             }
1002             elsif ($type eq "SCALAR") {
1003 0           $need_tie = !(tied $$val);
1004             }
1005              
1006 0 0         return $need_tie ? 1 : 0;
1007             }
1008             sub _reset_segment {
1009 0     0     my ($parent, $id) = @_;
1010              
1011 0   0       my $parent_type = Scalar::Util::reftype($parent->{_data}) || '';
1012              
1013 0 0         if ($parent_type eq 'HASH') {
    0          
1014 0           my $data = $parent->{_data};
1015 0 0 0       if (exists $data->{$id} && keys %{ $data->{$id} } && tied %{ $data->{$id} }) {
  0   0        
  0            
1016 0           (tied %{ $parent->{_data}{$id} })->remove;
  0            
1017             }
1018             }
1019             elsif ($parent_type eq 'ARRAY') {
1020 0           my $data = $parent->{_data};
1021 0 0 0       if (exists $data->[$id] && tied @{ $data->[$id] }) {
  0            
1022 0           (tied @{ $parent->{_data}[$id] })->remove;
  0            
1023             }
1024             }
1025             }
1026              
1027             sub _trace {
1028 0     0     require Carp;
1029 0           require Data::Dumper;
1030 0           my $caller = ' ' . (caller(1))[3] . " called with:\n";
1031 0           my $i = -1;
1032             my @msg = map {
1033 0           ++$i;
  0            
1034 0           my $obj;
1035 0 0         if (ref eq 'IPC::Shareable') {
1036 0           ' ' . "\$_[$i] = $_: shmid: $_->{_shm}->{_id}; " .
1037             Data::Dumper->Dump([ $_->attributes ], [ 'opts' ]);
1038             } else {
1039 0           ' ' . Data::Dumper->Dump( [ $_ ] => [ "\_[$i]" ]);
1040             }
1041             } @_;
1042 0           Carp::carp "IPC::Shareable ($$) debug:\n", $caller, @msg;
1043             }
1044             sub _debug {
1045 0     0     require Carp;
1046 0           require Data::Dumper;
1047 0           local $Data::Dumper::Terse = 1;
1048 0           my $caller = ' ' . (caller(1))[3] . " tells us that:\n";
1049             my @msg = map {
1050 0           my $obj;
  0            
1051 0 0         if (ref eq 'IPC::Shareable') {
1052 0           ' ' . "$_: shmid: $_->{_shm}->{_id}; " .
1053             Data::Dumper->Dump([ $_->attributes ], [ 'opts' ]);
1054             }
1055             else {
1056 0           ' ' . Data::Dumper::Dumper($_);
1057             }
1058             } @_;
1059 0           Carp::carp "IPC::Shareable ($$) debug:\n", $caller, @msg;
1060             }
1061       0     sub _placeholder {}
1062              
1063             1;
1064              
1065             __END__