File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 908 952 95.3
branch 502 620 80.9
condition 138 212 65.0
subroutine 83 84 98.8
pod 20 20 100.0
total 1651 1888 87.4


line stmt bran cond sub pod time code
1             package IPC::Shareable;
2              
3 79     79   1882657 use warnings;
  79         148  
  79         4735  
4 79     79   758 use strict;
  79         140  
  79         3317  
5              
6             require 5.00503;
7              
8 79     79   445 use Carp qw(croak confess carp);
  79         129  
  79         6101  
9 79     79   508 use Config;
  79         207  
  79         4136  
10 79     79   42184 use Data::Dumper;
  79         561229  
  79         6473  
11 79     79   34263 use Errno qw(ENOMEM ENOSPC);
  79         128859  
  79         10011  
12 79     79   612 use Digest::MD5 qw(md5_hex);
  79         243  
  79         5175  
13 79     79   40848 use IPC::Semaphore;
  79         529305  
  79         4116  
14 79     79   44292 use IPC::Shareable::SharedMem;
  79         328  
  79         3672  
15 79         5974 use IPC::SysV qw(
16             IPC_PRIVATE
17             IPC_CREAT
18             IPC_EXCL
19             IPC_NOWAIT
20             IPC_RMID
21             IPC_STAT
22             SEM_UNDO
23 79     79   582 );
  79         128  
24 79     79   58023 use JSON qw(-convert_blessed_universally);
  79         1073357  
  79         532  
25 79     79   32987 use Scalar::Util;
  79         167  
  79         5012  
26 79     79   32085 use String::CRC32;
  79         42525  
  79         6243  
27 79     79   48544 use Storable 0.6 qw(freeze thaw);
  79         316869  
  79         14064  
28              
29             our $VERSION;
30             our $_have_xs;
31              
32             BEGIN {
33 79     79   312 $VERSION = '1.14_10';
34 79   33     491 $_have_xs = ! $ENV{IPC_SHAREABLE_NO_XS} && eval {
      50        
35             require XSLoader;
36             XSLoader::load('IPC::Shareable', $VERSION);
37             1;
38             } // 0;
39             }
40              
41             use constant {
42             # Locking
43              
44 79         1149258 LOCK_SH => 1,
45             LOCK_EX => 2,
46             LOCK_NB => 4,
47             LOCK_UN => 8,
48              
49             # SHM parameters
50              
51             SHM_BUFSIZ => 65536,
52             SHMMAX_BYTES => 1073741824, # ~1 GB
53             SHM_EXISTS => 1,
54              
55             # Semaphore slots
56              
57             SEM_MARKER => 0,
58             SEM_READERS => 1,
59             SEM_WRITERS => 2,
60             SEM_PROTECTED => 3,
61              
62             # Perl sends in a double as opposed to an integer to shmat(), and on some
63             # systems, this causes the IPC system to round down to the maximum integer
64             # size of 0x80000000. We correct that when generating keys with CRC32.
65              
66             MAX_KEY_INT_SIZE => 0x80000000,
67              
68             # Number of times we'll check for existing segs
69              
70             EXCLUSIVE_CHECK_LIMIT => 10,
71              
72             # Struct types
73              
74             TYPE_HASH => 0,
75             TYPE_ARRAY => 1,
76             TYPE_SCALAR => 2,
77 79     79   583 };
  79         147  
78              
79             require Exporter;
80             our @ISA = 'Exporter';
81             our @EXPORT_OK = qw(
82             LOCK_EX
83             LOCK_SH
84             LOCK_NB
85             LOCK_UN
86             SEM_MARKER
87             SEM_READERS
88             SEM_WRITERS
89             SEM_PROTECTED
90             );
91             our %EXPORT_TAGS = (
92             all => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
93             lock => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
94             flock => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
95             );
96             Exporter::export_ok_tags('all', 'lock', 'flock');
97              
98             # Locking scheme copied from IPC::ShareLite (with minor modifications)
99              
100             my %semop_args = (
101             (LOCK_EX),
102             [
103             SEM_READERS, 0, 0, # Wait for readers to finish
104             SEM_WRITERS, 0, 0, # Wait for writers to finish
105             SEM_WRITERS, 1, SEM_UNDO, # Assert write lock
106             ],
107             (LOCK_EX|LOCK_NB),
108             [
109             SEM_READERS, 0, IPC_NOWAIT, # Wait for readers to finish
110             SEM_WRITERS, 0, IPC_NOWAIT, # Wait for writers to finish
111             SEM_WRITERS, 1, (SEM_UNDO | IPC_NOWAIT), # Assert write lock
112             ],
113             (LOCK_EX|LOCK_UN),
114             [
115             SEM_WRITERS, -1, (SEM_UNDO | IPC_NOWAIT),
116             ],
117             (LOCK_SH),
118             [
119             SEM_WRITERS, 0, 0, # Wait for writers to finish
120             SEM_READERS, 1, SEM_UNDO, # Assert shared read lock
121             ],
122             (LOCK_SH|LOCK_NB),
123             [
124             SEM_WRITERS, 0, IPC_NOWAIT, # Wait for writers to finish
125             SEM_READERS, 1, (SEM_UNDO | IPC_NOWAIT), # Assert shared read lock
126             ],
127             (LOCK_SH|LOCK_UN),
128             [
129             SEM_READERS, -1, (SEM_UNDO | IPC_NOWAIT), # Remove shared read lock
130             ],
131             );
132              
133             my %default_options = (
134             key => IPC_PRIVATE,
135             create => 0,
136             exclusive => 0,
137             destroy => 0,
138             mode => 0666,
139             size => SHM_BUFSIZ,
140             protected => 0,
141             limit => 1,
142             graceful => 0,
143             warn => 0,
144             tidy => 1,
145             serializer => 'json',
146             enforced_write_locking => 1,
147             enforced_read_locking => 1,
148             violated_write_lock_warn => 1,
149             violated_read_lock_warn => 1,
150             );
151              
152             # Seed the random number generator
153              
154             srand();
155              
156             # Class-level variables
157              
158             my %global_register;
159             my %process_register;
160             my %used_ids;
161              
162             # "Magic" methods
163              
164             sub TIESCALAR {
165 135     135   39896360 return _tie('SCALAR', @_);
166             }
167             sub TIEARRAY {
168 99     99   25111 return _tie('ARRAY', @_);
169             }
170             sub TIEHASH {
171 270     270   422186 return _tie('HASH', @_);
172             }
173             sub STORE {
174 726     726   264359 my $knot = shift;
175              
176 726 100       2482 return if ! _write_permitted($knot);
177              
178 719 100       2718 $knot->{_data} = $knot->_decode($knot->seg) unless ($knot->{_lock});
179              
180 719 100       2401 if ($knot->{_type_int} == TYPE_HASH) {
    100          
    50          
181 457         1495 my ($key, $val) = @_;
182             # If $val is a reference, we need to create a new segment
183 457 100 66     1788 _magic_tie($knot, $val, $key) if ref($val) && $knot->_need_tie($val, $key);
184 456         2162 $knot->{_data}{$key} = $val;
185             }
186             elsif ($knot->{_type_int} == TYPE_ARRAY) {
187 174         409 my ($i, $val) = @_;
188 174 100 66     733 _magic_tie($knot, $val, $i) if ref($val) && $knot->_need_tie($val, $i);
189 174         493 $knot->{_data}[$i] = $val;
190             }
191             elsif ($knot->{_type_int} == TYPE_SCALAR) {
192 88         309 my ($val) = @_;
193 88 100 100     410 _magic_tie($knot, $val) if ref($val) && $knot->_need_tie($val);
194 88         316 $knot->{_data} = \$val;
195             }
196              
197 718 100       2331 if ($knot->{_lock} & LOCK_EX) {
198 33         185 $knot->{_was_changed} = 1;
199             }
200             else {
201 685 100       2108 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
202 1         314 croak "Could not write to shared memory: $!\n";
203             }
204             }
205              
206 715         5178 return 1;
207             }
208             sub FETCH {
209 977     977   16121206 my $knot = shift;
210              
211 977         1626 my $data;
212 977 100       2641 if ($knot->{_lock}) {
213 29         109 $data = $knot->{_data};
214             }
215             else {
216 948         2969 _read_check($knot);
217 948         2412 $data = $knot->_decode($knot->seg);
218 946         5102 $knot->{_data} = $data;
219             }
220              
221 975         1382 my $val;
222              
223 975 100       2896 if ($knot->{_type_int} == TYPE_HASH) {
    100          
    50          
224 634         1194 my $key = shift;
225 634         1385 $val = $data->{$key};
226             }
227             elsif ($knot->{_type_int} == TYPE_ARRAY) {
228 182         337 my $i = shift;
229 182         350 $val = $data->[$i];
230             }
231             elsif ($knot->{_type_int} == TYPE_SCALAR) {
232 159 100       337 if (defined $data) {
233 150         308 $val = $$data;
234             }
235             else {
236 9         63 return;
237             }
238             }
239              
240 966 100 66     3475 if (ref($val) && (my $inner = _is_child($val))) {
241             # Register the inner knot so clean_up_all() can find it even when it
242             # was created in a forked child process
243              
244 503 100       1335 if (! exists $global_register{$inner->seg->id}) {
245 13         30 $global_register{$inner->seg->id} = $inner;
246             }
247              
248 503         1331 my $s = $inner->seg;
249 503         1101 $inner->{_data} = $knot->_decode($s);
250             }
251 966         8303 return $val;
252              
253             }
254             sub CLEAR {
255 169     169   8152 my $knot = shift;
256              
257 169 100       567 return if ! _write_permitted($knot);
258              
259 168 100       784 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
260              
261 168 100       712 if ($knot->{_type_int} == TYPE_HASH) {
    50          
262             # Remove any child segments before discarding the data
263 110         203 for my $val (values %{ $knot->{_data} }) {
  110         909  
264 27 100 66     100 if (ref($val) && (my $child = _is_child($val))) {
265 2         23 $child->remove;
266             }
267             }
268 110         573 $knot->{_data} = { };
269             }
270             elsif ($knot->{_type_int} == TYPE_ARRAY) {
271             # Remove any child segments before discarding the data
272 58         98 for my $val (@{ $knot->{_data} }) {
  58         290  
273 20 50 33     61 if (ref($val) && (my $child = _is_child($val))) {
274 0         0 $child->remove;
275             }
276             }
277 58         183 $knot->{_data} = [ ];
278             }
279              
280 168 100       994 if ($knot->{_lock} & LOCK_EX) {
281 1         4 $knot->{_was_changed} = 1;
282             }
283             else {
284 167 100       547 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
285 1         126 croak "Could not write to shared memory: $!";
286             }
287             }
288             }
289             sub DELETE {
290 7     7   1893 my $knot = shift;
291 7         24 my $key = shift;
292              
293 7 100       39 return if ! _write_permitted($knot);
294              
295 6 100       41 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
296 6         27 my $val = delete $knot->{_data}->{$key};
297              
298             # Remove the child segment if the deleted value was a nested tied ref
299              
300 6 100 66     42 if (ref($val) && (my $child = _is_child($val))) {
301 2         24 $child->remove;
302             }
303              
304 6 100       29 if ($knot->{_lock} & LOCK_EX) {
305 1         3 $knot->{_was_changed} = 1;
306             }
307             else {
308 5 100       17 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
309 1         205 croak "Could not write to shared memory: $!";
310             }
311             }
312              
313 5         35 return $val;
314             }
315             sub EXISTS {
316 32     32   679 my $knot = shift;
317 32         78 my $key = shift;
318              
319 32 100       182 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
320 32         277 return exists $knot->{_data}->{$key};
321             }
322             sub FIRSTKEY {
323 29     29   28973 my $knot = shift;
324              
325 29 100       232 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
326              
327 29         128 $knot->{_hkey_list} = [ keys %{$knot->{_data}} ];
  29         184  
328              
329 29         154 return $knot->NEXTKEY;
330             }
331             sub NEXTKEY {
332 76     76   169 my ($knot, $last_key_accessed) = @_;
333              
334             # We don't use ordered hashes, so we don't need to use
335             # the last key accessed parameter
336              
337             # Caveat emptor if hash was changed by another process
338              
339 76         125 return shift @{$knot->{_hkey_list}};
  76         442  
340             }
341       40     sub EXTEND {
342             #XXX Noop
343             }
344             sub PUSH {
345 15     15   2191 my $knot = shift;
346              
347 15 100       55 return if ! _write_permitted($knot);
348              
349 14 100       55 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
350              
351 14         75 push @{$knot->{_data}}, @_;
  14         46  
352 14 100       53 if ($knot->{_lock} & LOCK_EX) {
353 11         54 $knot->{_was_changed} = 1;
354             }
355             else {
356 3 100       11 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
357 1         208 croak "Could not write to shared memory: $!";
358             };
359             }
360             }
361             sub POP {
362 5     5   3984 my $knot = shift;
363              
364 5 100       24 return if ! _write_permitted($knot);
365              
366 4 100       29 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
367              
368 4         12 my $val = pop @{$knot->{_data}};
  4         66  
369 4 100       22 if ($knot->{_lock} & LOCK_EX) {
370 1         3 $knot->{_was_changed} = 1;
371             }
372             else {
373 3 100       13 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
374 1         222 croak "Could not write to shared memory: $!";
375             }
376             }
377 3         22 return $val;
378             }
379             sub SHIFT {
380 15     15   7970 my $knot = shift;
381              
382 15 100       52 return if ! _write_permitted($knot);
383              
384 14 100       53 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
385 14         27 my $val = shift @{$knot->{_data}};
  14         39  
386 14 100       45 if ($knot->{_lock} & LOCK_EX) {
387 11         20 $knot->{_was_changed} = 1;
388             }
389             else {
390 3 100       13 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
391 1         177 croak "Could not write to shared memory: $!";
392             }
393             }
394 13         45 return $val;
395             }
396             sub UNSHIFT {
397 5     5   8983 my $knot = shift;
398              
399 5 100       26 return if ! _write_permitted($knot);
400              
401 4 100       28 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
402 4         10 my $val = unshift @{$knot->{_data}}, @_;
  4         28  
403 4 100       23 if ($knot->{_lock} & LOCK_EX) {
404 1         2 $knot->{_was_changed} = 1;
405             }
406             else {
407 3 100       12 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
408 1         216 croak "Could not write to shared memory: $!";
409             }
410             }
411 3         16 return $val;
412             }
413             sub SPLICE {
414 5     5   5474 my($knot, $off, $n, @av) = @_;
415              
416 5 100       26 return if ! _write_permitted($knot);
417              
418 4 100       27 $knot->{_data} = $knot->_decode($knot->seg, $knot->{_data}) unless $knot->{_lock};
419 4         13 my @val = splice @{$knot->{_data}}, $off, $n, @av;
  4         55  
420 4 100       29 if ($knot->{_lock} & LOCK_EX) {
421 1         3 $knot->{_was_changed} = 1;
422             }
423             else {
424 3 100       13 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
425 1         198 croak "Could not write to shared memory: $!";
426             }
427             }
428 3         22 return @val;
429             }
430             sub FETCHSIZE {
431 60     60   2034509 my $knot = shift;
432              
433 60 100       292 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
434 60         117 return scalar(@{$knot->{_data}});
  60         340  
435             }
436             sub STORESIZE {
437 5     5   1954 my $knot = shift;
438 5         14 my $n = shift;
439              
440 5 100       27 return if ! _write_permitted($knot);
441              
442 4 100       34 $knot->{_data} = $knot->_decode($knot->seg) unless $knot->{_lock};
443 4         15 $#{$knot->{_data}} = $n - 1;
  4         22  
444 4 100       26 if ($knot->{_lock} & LOCK_EX) {
445 1         4 $knot->{_was_changed} = 1;
446             }
447             else {
448 3 100       15 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
449 1         187 croak "Could not write to shared memory: $!";
450             }
451             }
452 3         17 return $n;
453             }
454              
455             # Public methods
456              
457             *shlock = \&lock;
458             *shunlock = \&unlock;
459              
460             # End user methods
461              
462             sub new {
463 7     7 1 23654 my ($class, %opts) = @_;
464              
465 7   100     210 my $type = $opts{var} || 'HASH';
466              
467 7 100       89 if ($type eq 'HASH') {
468 3         354 my $k = tie my %h, 'IPC::Shareable', \%opts;
469 3         53 return \%h;
470             }
471 4 100       13 if ($type eq 'ARRAY') {
472 2         12 my $k = tie my @a, 'IPC::Shareable', \%opts;
473 2         10 return \@a;
474             }
475 2 50       13 if ($type eq 'SCALAR') {
476 2         14 my $k = tie my $s, 'IPC::Shareable', \%opts;
477 2         11 return \$s;
478             }
479             }
480             sub lock {
481 137     137 1 5025858 my $knot = shift;
482              
483 137         873 my ($flags, $code);
484              
485 137 100       1614 if (scalar @_ == 2) {
486 5         29 ($flags, $code) = @_;
487             }
488              
489 137 100       657 if (defined $_[0]) {
490 89 100       688 if (ref $_[0] eq 'CODE') {
491 1         3 $code = shift;
492             }
493             else {
494 88         841 $flags = shift;
495             }
496             }
497              
498 137 100 100     841 if (defined $code && ref $code ne 'CODE') {
499 1         491 croak "\$code param to lock() must be a code ref"
500             }
501              
502 136 100       903 $flags = LOCK_EX if ! defined $flags;
503              
504 136 50       683 return $knot->unlock if ($flags & LOCK_UN);
505              
506 136 50       1027 return 1 if ($knot->{_lock} & $flags);
507              
508             # If they have a different lock than they want, release it first
509              
510 136 50       491 $knot->unlock if ($knot->{_lock});
511              
512 136         1399 my $sem = $knot->sem;
513 136         449 my $lock_success = $sem->op(@{ $semop_args{$flags} });
  136         4245  
514              
515 136 100       5736074 if ($lock_success) {
516 125         623 $knot->{_lock} = $flags;
517 125         965 $knot->{_data} = $knot->_decode($knot->seg);
518              
519 125         1665 my $locked_ref = _lock_children($knot, $flags);
520              
521 125 100       376 if (! $locked_ref) {
522 2         16 my $rflags = $knot->{_lock} | LOCK_UN;
523 2 50       21 $rflags ^= LOCK_NB if $rflags & LOCK_NB;
524 2         11 $knot->sem->op(@{ $semop_args{$rflags} });
  2         53  
525 2         23 $knot->{_lock} = 0;
526 2         4 $lock_success = 0;
527             }
528             else {
529 123         1667 $knot->{_locked_children} = $locked_ref;
530             }
531             }
532              
533 136 100 66     1195 if ($flags == LOCK_EX && $lock_success) {
534 90 100       861 if ($code) {
535 4         9 my $ok = eval { $code->(); 1 };
  4         20  
  2         9  
536 4         21 my $err = $@;
537 4         20 $knot->unlock;
538 4 100       30 die $err if ! $ok;
539 2         8 return 1;
540             }
541             }
542 132         551 return $lock_success;
543             }
544             sub unlock {
545 264     264 1 6585622 my $knot = shift;
546              
547 264 100       1301 return 1 unless $knot->{_lock};
548              
549 124 100       636 if ($knot->{_was_changed}) {
550 54 100       159 if (! defined $knot->_encode($knot->seg, $knot->{_data})){
551 1         381 croak "Could not write to shared memory: $!\n";
552             }
553 53         246 $knot->{_was_changed} = 0;
554             }
555              
556 123   50     298 for my $child (reverse @{ $knot->{_locked_children} // [] }) {
  123         1502  
557 25 100       185 if ($child->{_was_changed}) {
558 3         30 $child->_encode($child->seg, $child->{_data});
559 3         36 $child->{_was_changed} = 0;
560             }
561 25         120 my $child_flags = $child->{_lock} | LOCK_UN;
562 25 100       103 $child_flags ^= LOCK_NB if $child_flags & LOCK_NB;
563 25         115 $child->sem->op(@{ $semop_args{$child_flags} });
  25         402  
564 25         1516 $child->{_lock} = 0;
565             }
566 123         427 $knot->{_locked_children} = [];
567              
568 123         447 my $sem = $knot->sem;
569 123         368 my $flags = $knot->{_lock} | LOCK_UN;
570              
571 123 100       408 $flags ^= LOCK_NB if ($flags & LOCK_NB);
572              
573 123 100       273 if (! $sem->op(@{ $semop_args{$flags} })) {
  123         1175  
574 1         241 croak "Could not release semaphore lock: $!\n";
575             }
576              
577 122         3038 $knot->{_lock} = 0;
578              
579 122         691 1;
580             }
581             sub singleton {
582              
583             # If called with IPC::Shareable::singleton() as opposed to
584             # IPC::Shareable->singleton(), the class isn't sent in. Check
585             # for this and fix it if necessary
586              
587 8 100 100 8 1 12502 if (! defined $_[0] || $_[0] ne __PACKAGE__) {
588 3         20 unshift @_, __PACKAGE__;
589             }
590              
591 8         65 my ($class, $glue, $warn) = @_;
592              
593 8 100       43 if (! defined $glue) {
594 2         827 croak "singleton() requires a GLUE parameter";
595             }
596              
597 6 100       30 $warn = 0 if ! defined $warn;
598              
599 6         150 tie my $lock, 'IPC::Shareable', {
600             key => $glue,
601             create => 1,
602             exclusive => 1,
603             graceful => 1,
604             destroy => 1,
605             warn => $warn
606             };
607              
608 3         45 return $$;
609             }
610              
611             # Helper, maintenance and developer methods
612              
613             sub attributes {
614 11948     11948 1 71524 my ($knot, $attr) = @_;
615              
616 11948 100       24630 if (defined $attr) {
617 11733         53289 return $knot->{attributes}{$attr};
618             }
619             else {
620 215         4327 return $knot->{attributes};
621             }
622             }
623             sub global_register {
624 106     106 1 1127786 return \%global_register;
625             }
626             sub process_register {
627 11     11 1 60 return \%process_register;
628             }
629             sub uuid {
630 531     531 1 12018 my ($knot) = @_;
631              
632 531 100       4084 if (! defined $knot->{_uuid}) {
633 504         9922 $knot->{_uuid} = md5_hex(rand());
634             }
635              
636 531         1884 return $knot->{_uuid};
637             }
638              
639             sub seg {
640 5884     5884 1 43852 my ($knot) = @_;
641 5884 50       24499 return $knot->{_shm} if defined $knot->{_shm};
642             }
643             sub sem {
644 2490     2490 1 20681 my ($knot) = @_;
645 2490 50       9909 return $knot->{_sem} if defined $knot->{_sem};
646             }
647              
648             sub shm_segments {
649 26 50 66 26 1 18183 shift if ref($_[0]) || (defined $_[0] && !ref($_[0]) && UNIVERSAL::isa($_[0], __PACKAGE__));
      66        
      100        
650              
651 26         71 my ($filter_key) = @_;
652              
653 26 100       76 my $filter_int = _key_str_to_int($filter_key) if defined $filter_key;
654              
655 26         42 my %segments;
656              
657 26 50       106604 open my $ipcs_fh, '-|', 'ipcs', '-m' or die "ipcs -m: $!";
658 26         30545 while (my $line = <$ipcs_fh>) {
659 229         637 my ($id, $raw_key);
660              
661             # BSD/macOS format: m ...
662 229 50       1922 if ($line =~ /^\s*m\s+(\d+)\s+(\S+)/) {
    100          
663 0         0 ($id, $raw_key) = ($1, $2);
664             }
665             # Linux format: ...
666             elsif ($line =~ /^\s*(\S+)\s+(\d+)\s+\S+/) {
667 125         1098 ($raw_key, $id) = ($1, $2);
668             }
669             else {
670 104         591 next;
671             }
672              
673 125 0       582 my $key_int = $raw_key =~ /^0x[0-9a-fA-F]+$/
    50          
674             ? hex($raw_key)
675             : $raw_key =~ /^\d+$/
676             ? int($raw_key)
677             : next;
678              
679 125         460 my $hex_key = sprintf('0x%08x', $key_int);
680              
681 125 50       292 next if $key_int == 0; # IPC_PRIVATE segments can't be found by key
682              
683             # Get segment size via IPC_STAT
684 125         310 my $stat_buf = '';
685 125 50       918 shmctl($id, IPC_STAT, $stat_buf) or next;
686              
687             my ($segsz) = $^O eq 'linux'
688             ? ( $Config{longsize} == 8
689             ? unpack('x[48] Q', $stat_buf) # 64-bit Linux
690             : unpack('x[36] L', $stat_buf) ) # 32-bit Linux
691             : $^O eq 'freebsd' && $Config{longsize} == 8
692             ? unpack('x[32] Q', $stat_buf) # 64-bit FreeBSD (key_t=long=8, ipc_perm=32)
693             : $^O eq 'solaris'
694 125 50 0     4704 ? ( $Config{longsize} == 8
    0          
    0          
    0          
    50          
695             ? unpack('x[32] Q', $stat_buf) # 64-bit Solaris (ipc_perm=28 + pad 4)
696             : unpack('x[44] L', $stat_buf) ) # 32-bit Solaris (ipc_perm=44)
697             : unpack('x[24] Q', $stat_buf); # macOS/32-bit BSD
698              
699 125 50       436 next unless $segsz;
700              
701 125         211 my $data = '';
702 125 50       16937 shmread($id, $data, 0, $segsz) or next;
703              
704             # Strip trailing null bytes
705 125         2793 $data =~ s/\x00+$//;
706              
707             # Skip segments not owned by IPC::Shareable (all our segments
708             # are prefixed with the literal string 'IPC::Shareable')
709 125 100       518 next unless substr($data, 0, 14) eq 'IPC::Shareable';
710              
711 124         379 my $json_part = substr($data, 14);
712 124         430 my @child_keys = ($json_part =~ /"child_key_hex":"([^"]+)"/g);
713              
714             $segments{$hex_key} = {
715             child_keys => \@child_keys,
716             content => $data,
717             id => $id,
718             local_process => (exists $process_register{$id} ? 1 : 0),
719 124 100       3007 known => (exists $global_register{$id} ? 1 : 0),
    100          
720             };
721             }
722 26         847 close $ipcs_fh;
723              
724 26 100       101 if (defined $filter_int) {
725             # Walk the segment tree starting from the root whose key matches
726             # $filter_int, collecting it and all its descendants. Use integer
727             # comparison so that hex formatting differences (zero-padding, case)
728             # between ipcs(1) output and child_key_hex values don't matter.
729 2         37 my %int_to_hex = map { hex($_) => $_ } keys %segments;
  8         63  
730 2         12 my (%related, @queue);
731 2         13 push @queue, $filter_int;
732 2         20 while (my $k_int = shift @queue) {
733 3   100     17 my $k_hex = $int_to_hex{$k_int} // next;
734 2 50       15 next if $related{$k_hex}++;
735 2         9 push @queue, map { hex($_) } @{ $segments{$k_hex}{child_keys} };
  1         8  
  2         11  
736             }
737 2         15 %segments = map { $_ => $segments{$_} } keys %related;
  2         14  
738             }
739              
740 26         1052 return \%segments;
741             }
742             sub unknown_segments {
743 2 100   2 1 6820 shift if ref $_[0]; # Allow for object or class method call
744              
745 2         18 my $segs = shm_segments();
746              
747 2         32 return grep { !$segs->{$_}{known} } keys %$segs;
  10         51  
748             }
749             sub seg_count {
750 134     134 1 23009378 my $count = 0;
751              
752 134 50       671595 open my $ipcs_fh, '-|', 'ipcs', '-m' or die "ipcs -m: $!";
753 134         174191 while (my $line = <$ipcs_fh>) {
754             # BSD/macOS format: m ...
755             # Linux format: ...
756 688 50       6486 $count++ if $line =~ /^\s*m\s+\d+\s+\S+/;
757 688 100       13736 $count++ if $line =~ /^\s*(?:0x[0-9a-fA-F]+|\d+)\s+\d+\s+\S+/;
758             }
759 134         5881 close $ipcs_fh;
760              
761 134         6631 return $count;
762             }
763             sub sem_count {
764 125     125 1 104324 my $count = 0;
765              
766 125 50       571663 open my $ipcs_fh, '-|', 'ipcs', '-s' or die "ipcs -s: $!";
767 125         146116 while (my $line = <$ipcs_fh>) {
768             # BSD/macOS format: s ...
769             # Linux format: ...
770 623 50       5707 $count++ if $line =~ /^\s*s\s+\d+\s+\S+/;
771 623 100       19072 $count++ if $line =~ /^\s*(?:0x[0-9a-fA-F]+|\d+)\s+\d+\s+\S+/;
772             }
773 125         5368 close $ipcs_fh;
774              
775 125         6074 return $count;
776             }
777             sub seg_map {
778 6 100   6 1 564 croak "seg_map() must be called as an object method" unless ref $_[0];
779 5         24 my $knot_filter = shift;
780              
781 5         22 my $segs = shm_segments();
782              
783             # Build hex_key -> OS segment ID from shm_segments() data
784             # (already parsed the ipcs output, no need to shell out again).
785 5         16 my %id_by_hex;
786 5         83 $id_by_hex{ $_ } = $segs->{$_}{id} for keys %$segs;
787              
788             # Build hex_key -> knot from global_register (keyed by seg_id)
789 5         29 my %knot_by_hex;
790 5         42 for my $id (keys %global_register) {
791 8         74 my $knot = $global_register{$id};
792 8         23 my $hex = $knot->{_key_hex};
793 8 50       29 $knot_by_hex{$hex} = $knot if defined $hex;
794             }
795              
796             # Supplement child_keys from global_register for Storable segments.
797             # shm_segments() only extracts child_key_hex from JSON segment content;
798             # for Storable we walk each knot's _data looking for tied child references.
799 5         16 my %extra_child_keys; # hex_key -> [ child_hex, ... ]
800 5         18 for my $hex (keys %knot_by_hex) {
801 8         14 my $knot = $knot_by_hex{$hex};
802 8         17 my $data = $knot->{_data};
803 8   50     32 my $rtype = Scalar::Util::reftype($data) // '';
804              
805 8 50       32 my @vals = $rtype eq 'HASH' ? values %$data
    100          
806             : $rtype eq 'ARRAY' ? @$data
807             : ();
808              
809 8         18 for my $v (@vals) {
810 3 100       28 next unless ref($v);
811 1   50     14 my $vtype = Scalar::Util::reftype($v) // '';
812 1         7 my $child_knot;
813 1 50       8 if ($vtype eq 'HASH') { $child_knot = tied(%$v) }
  1 0       4  
    0          
814 0         0 elsif ($vtype eq 'ARRAY') { $child_knot = tied(@$v) }
815 0         0 elsif ($vtype eq 'SCALAR') { $child_knot = tied($$v) }
816 1 50 33     23 next unless $child_knot && $child_knot->{_key_hex};
817 1         6 push @{ $extra_child_keys{$hex} }, $child_knot->{_key_hex};
  1         9  
818             }
819             }
820              
821             # If called as an object method, restrict output to just that knot's tree
822             # by BFS through both child_keys (JSON) and extra_child_keys (Storable).
823 5 50 33     47 if ($knot_filter && $knot_filter->{_key_hex}) {
824 5         12 my $root_hex = $knot_filter->{_key_hex};
825 5         8 my (%in_tree, @queue);
826 5         9 push @queue, $root_hex;
827 5         21 while (my $h = shift @queue) {
828 6 50       32 next if $in_tree{$h}++;
829 6   50     10 push @queue, @{ $segs->{$h}{child_keys} // [] };
  6         22  
830 6   100     11 push @queue, @{ $extra_child_keys{$h} // [] };
  6         75  
831             }
832 5         31 %$segs = map { $_ => $segs->{$_} } grep { $in_tree{$_} } keys %$segs;
  6         37  
  13         32  
833             }
834              
835             # Identify root segments (not a child of any other segment)
836 5         23 my %is_child;
837 5         10 for my $hex (keys %$segs) {
838 6         7 $is_child{$_}++ for @{ $segs->{$hex}{child_keys} };
  6         20  
839             }
840 5         13 for my $hex (keys %extra_child_keys) {
841 1 50       8 next unless exists $segs->{$hex};
842 1         4 $is_child{$_}++ for @{ $extra_child_keys{$hex} };
  1         5  
843             }
844 5         19 my @roots = sort grep { !$is_child{$_} } keys %$segs;
  6         35  
845              
846 5         11 my @lines;
847 5         22 push @lines, 'IPC::Shareable Segment Map';
848 5         20 push @lines, '=' x 26;
849              
850 5 50       14 if (!@roots) {
851 0         0 push @lines, '';
852 0         0 push @lines, ' (no IPC::Shareable segments found)';
853 0         0 return join("\n", @lines) . "\n";
854             }
855              
856 5         8 my $render;
857             $render = sub {
858 6     6   15 my ($hex, $depth) = @_;
859 6         16 my $indent = ' ' x $depth;
860 6   50     15 my $seg = $segs->{$hex} // {};
861              
862 6         8 my @tags;
863 6 50       26 push @tags, $seg->{known} ? 'known' : 'unknown';
864 6 50       34 push @tags, 'owner' if $seg->{local_process};
865 6         44 my $tag_str = '[' . join(', ', @tags) . ']';
866              
867 6   50     18 my $seg_id = $id_by_hex{$hex} // '?';
868              
869             # Read semaphore slot values and ID; for segments not in
870             # global_register attach with nsems=0 (avoids EINVAL on existing sets)
871 6         11 my ($sem_str, $content_str);
872             my $sem = $knot_by_hex{$hex}
873 6 50       37 ? $knot_by_hex{$hex}->sem
874             : IPC::Semaphore->new(hex($hex), 0, 0);
875              
876 6 50       14 if (defined $sem) {
877 6   50     68 my $sem_id = $sem->id // '?';
878 6   50     88 my $marker = $sem->getval(SEM_MARKER) // '?';
879 6   50     136 my $readers = $sem->getval(SEM_READERS) // '?';
880 6   50     96 my $writers = $sem->getval(SEM_WRITERS) // '?';
881 6   50     67 my $protected = $sem->getval(SEM_PROTECTED) // '?';
882             # Continuation indent: one tab (8 spaces) from the left margin
883 6         73 my $cont = ' ' x 8;
884 6         90 $sem_str = join("\n",
885             "sem_id: $sem_id",
886             "${cont}1: SEM_MARKER=$marker",
887             "${cont}2: READERS=$readers",
888             "${cont}3: WRITERS=$writers",
889             "${cont}4: PROTECTED=$protected",
890             );
891             }
892             else {
893 0         0 $sem_str = '(not accessible)';
894             }
895              
896             $content_str = $knot_by_hex{$hex}
897 6 50       84 ? _shm_data_summary($knot_by_hex{$hex})
898             : '(not accessible - segment not tied in this process)';
899              
900             # Merge child keys from shm_segments() and from global_register walk
901 6         12 my %seen_child;
902 1         7 my @child_keys = grep { !$seen_child{$_}++ } (
903 6   50     19 @{ $seg->{child_keys} // [] },
904 6   100     8 @{ $extra_child_keys{$hex} // [] },
  6         30  
905             );
906 6 100       16 my $children = @child_keys ? join(', ', @child_keys) : '(none)';
907              
908 6         9 push @lines, '';
909 6         16 push @lines, "${indent}${tag_str} key: ${hex} seg_id: ${seg_id}";
910 6         22 push @lines, "${indent} Semaphores: ${sem_str}";
911 6         29 push @lines, "${indent} Children: ${children}";
912 6         15 push @lines, "${indent} Content: ${content_str}";
913              
914 6         32 $render->($_, $depth + 1) for @child_keys;
915 5         85 };
916              
917 5         24 $render->($_, 0) for @roots;
918              
919 5         12 push @lines, '';
920 5         77 return join("\n", @lines) . "\n";
921             }
922             sub sysv_info {
923 6     6 1 10266 shift; # Discard invocant (object ref or class name)
924 6         30 my %opts = @_;
925 6   100     68 my $proc_dir = delete $opts{_proc_dir} // '/proc/sys/kernel';
926 6         25 my $sysctl_out = delete $opts{_sysctl_out};
927              
928 6         14 my %info;
929              
930 6 50       102 if ($^O eq 'darwin') {
    100          
    50          
931 0 0       0 my $out = defined $sysctl_out ? $sysctl_out : do {
932 0 0       0 open my $fh, '-|', 'sysctl', 'kern.sysv' or die "sysctl: $!";
933 0         0 local $/;
934 0         0 my $s = <$fh>;
935 0         0 close $fh;
936 0         0 $s;
937             };
938 0         0 for my $line (split /\n/, $out) {
939 0 0       0 if ($line =~ /^kern\.sysv\.(\w+):\s*(\S+)/) {
940 0         0 $info{$1} = $2;
941             }
942             }
943             }
944             elsif ($^O eq 'freebsd') {
945 1 50       14 my $out = defined $sysctl_out ? $sysctl_out : do {
946 0 0       0 open my $fh, '-|', 'sysctl', 'kern.ipc' or die "sysctl: $!";
947 0         0 local $/;
948 0         0 my $s = <$fh>;
949 0         0 close $fh;
950 0         0 $s;
951             };
952 1         12 for my $line (split /\n/, $out) {
953 6 100       37 if ($line =~ /^kern\.ipc\.(shm\w+):\s*(\S+)/) {
954 5         26 $info{$1} = $2;
955             }
956             }
957             }
958             elsif ($^O eq 'linux') {
959 5         23 for my $key (qw(shmmax shmmin shmmni shmall)) {
960 20         80 my $file = "$proc_dir/$key";
961 20 100       978 if (open my $fh, '<', $file) {
962 16         326 chomp(my $val = <$fh>);
963 16         487 $info{$key} = $val;
964             }
965             }
966             }
967              
968 6 50       52 return %info ? \%info : undef;
969             }
970              
971             sub clean_up {
972 9     9 1 21251 my $class = shift;
973              
974 9         50 for my $id (keys %process_register) {
975 8         20 my $s = $process_register{$id};
976 8 50       27 next unless $s->attributes('owner') == $$;
977 8 50       31 next if $s->attributes('protected');
978 8         42 remove($s);
979             }
980             }
981             sub clean_up_all {
982 83     83 1 15209393 my $class = shift;
983              
984 83         585 my $global_register = __PACKAGE__->global_register;
985              
986 83         588 for my $id (keys %$global_register) {
987 219         553 my $s = $global_register->{$id};
988 219 100       755 next if $s->attributes('protected');
989 216         684 remove($s);
990             }
991             }
992             sub clean_up_protected {
993 9     9 1 13699 my ($knot, $protect_key);
994              
995 9 100       77 if (scalar @_ == 2) {
996 5         87 ($knot, $protect_key) = @_;
997             }
998 9 100       64 if (scalar @_ == 1) {
999 3         22 ($protect_key) = @_;
1000             }
1001              
1002 9 100       48 if (! defined $protect_key) {
1003 1         396 croak "clean_up_protected() requires a \$protect_key param";
1004             }
1005              
1006 8 100       78 if ($protect_key !~ /^\d+$/) {
1007 1         193 croak
1008             "clean_up_protected() \$protect_key must be an integer. You sent $protect_key";
1009             }
1010              
1011 7         45 my $global_register = __PACKAGE__->global_register;
1012              
1013 7         38 for my $id (keys %$global_register) {
1014 8         20 my $s = $global_register->{$id};
1015 8         32 my $stored_key = $s->attributes('protected');
1016              
1017 8 50 33     69 if ($stored_key && $stored_key == $protect_key) {
1018 8         36 remove($s);
1019             }
1020             }
1021             }
1022             sub remove {
1023 338     338 1 30683 my ($knot, $key) = @_;
1024              
1025             # If a key is passed, remove that specific segment by key rather than
1026             # via an existing tied object
1027              
1028 338 100       1020 if (defined $key) {
1029 5         45 $key = $knot->_shm_key($key);
1030 5         48 my $id = shmget($key, 0, 0);
1031              
1032 5 100       28 if (! defined $id) {
1033 1         43 warn "remove(): shmget failed for key $key: $!";
1034 1         15 return;
1035             }
1036              
1037 4 50       45 if (! shmctl($id, IPC_RMID, 0)) {
1038 0         0 warn "Couldn't remove shm segment $id: $!";
1039             }
1040             else {
1041 4         478 delete $process_register{$id};
1042 4         8 delete $global_register{$id};
1043             }
1044              
1045             # Remove the associated semaphore set (same key, attach-only with nsems=0)
1046              
1047 4         82 my $sem = IPC::Semaphore->new($key, 0, 0);
1048 4 50       62 if (defined $sem) {
1049 0 0       0 $sem->remove or warn "Couldn't remove semaphore set for key $key: $!";
1050             }
1051              
1052 4         11 return;
1053             }
1054              
1055             # Standard object based removal
1056              
1057 333         918 my $seg = $knot->seg;
1058 333         1653 my $id = $seg->id;
1059              
1060 333         781 my $seg_removed = 0;
1061              
1062 333 50       1361 if (! $seg->remove) {
1063 0         0 warn "Couldn't remove shm segment $id: $!";
1064             }
1065             else {
1066 333         627 $seg_removed = 1;
1067             }
1068              
1069             # Semaphore cleanup
1070              
1071 333         1132 my $sem = $knot->sem;
1072              
1073 333         670 my $sem_removed = 0;
1074 333         1556 my $sem_remove_status = $sem->remove;
1075              
1076 333 100 66     8427 if ($sem_remove_status != 1 && $sem_remove_status ne '0 but true') {
1077 1         46 warn "Couldn't remove semaphore set $id: $!";
1078             }
1079             else {
1080 332         651 $sem_removed = 1;
1081             }
1082              
1083             # If the segment or semaphore couldn't be cleaned up, we need to
1084             # keep state
1085              
1086 333 100 66     1498 if ($seg_removed && $sem_removed) {
1087 332         1041 delete $process_register{$id};
1088 332         9988 delete $global_register{$id};
1089             }
1090             }
1091              
1092             # Private methods
1093              
1094             # Encoding/Decoding
1095              
1096             sub _encode {
1097 922     922   2336 my ($knot, $seg, $data) = @_;
1098              
1099 922         2244 my $serializer = $knot->attributes('serializer');
1100              
1101 922 100       2689 if ($serializer eq 'storable') {
1102 508         1476 return _freeze($seg, $data);
1103             }
1104              
1105 414         1234 return _encode_json($seg, $data);
1106             }
1107             sub _decode {
1108 2812     2812   5307 my ($knot, $seg) = @_;
1109              
1110 2812         7184 my $serializer = $knot->attributes('serializer');
1111              
1112 2812 100       13047 my $data = $serializer eq 'storable'
1113             ? _thaw($seg)
1114             : _decode_json($seg, $knot);
1115              
1116 2807 100       13788 return $data if defined $data;
1117              
1118             # Empty/never-written segment — return appropriate empty default so that
1119             # aggregate tie methods (FETCHSIZE, PUSH, CLEAR, etc.) can deref safely.
1120 491 100       1703 return [] if $knot->{_type_int} == TYPE_ARRAY;
1121 408 100       1803 return {} if $knot->{_type_int} == TYPE_HASH;
1122 100         285 return undef;
1123             }
1124             sub _encode_json {
1125 414     414   701 my $seg = shift;
1126 414         641 my $data = shift;
1127              
1128 414         1060 my $json = encode_json _encode_json_prepare($data);
1129              
1130 414         1517 substr $json, 0, 0, 'IPC::Shareable';
1131              
1132 414 100       1632 if (length($json) > $seg->size) {
1133 1         141 croak "Length of shared data exceeds shared segment size";
1134             }
1135              
1136 413         1334 $seg->shmwrite($json);
1137             }
1138             sub _encode_json_prepare {
1139 415     415   3397 my ($data) = @_;
1140              
1141 415 50       1476 my $type = Scalar::Util::reftype($data) or return $data;
1142              
1143             # Replace direct IPC::Shareable child segments with __ics__ markers.
1144             # All nested refs are tied children — no recursion needed; each child
1145             # segment encodes its own children independently. We have to do this because
1146             # JSON can't store blessed objects
1147              
1148 415 100       1054 if ($type eq 'HASH') {
1149             {
1150 272         437 my $has_child = 0;
  272         420  
1151 272         1240 for my $val (values %$data) {
1152 1192 100 66     2759 if (ref($val) && _is_child($val)) { $has_child = 1; last; }
  51         112  
  51         174  
1153             }
1154 272 100       1971 return $data if !$has_child;
1155             }
1156              
1157 51         94 my %result;
1158 51         177 for my $key (keys %$data) {
1159 118         230 my $val = $data->{$key};
1160 118   66     448 my $inner = ref($val) && _is_child($val);
1161             $result{$key} = $inner
1162 118 100       934 ? { '__ics__' => { type => $inner->{_type}, child_key => $inner->{_key}, child_key_hex => sprintf('0x%08x', $inner->{_key}) } }
1163             : $val;
1164             }
1165 51         498 return \%result;
1166             }
1167              
1168 143 100       334 if ($type eq 'ARRAY') {
1169             {
1170 128         193 my $has_child = 0;
  128         207  
1171 128         319 for my $val (@$data) {
1172 318 100 66     727 if (ref($val) && _is_child($val)) { $has_child = 1; last; }
  20         38  
  20         42  
1173             }
1174 128 100       906 return $data if !$has_child;
1175             }
1176              
1177             return [
1178             map {
1179 20   66     59 my $inner = ref($_) && _is_child($_);
  40         128  
1180             $inner
1181 40 100       603 ? { '__ics__' => { type => $inner->{_type}, child_key => $inner->{_key}, child_key_hex => sprintf('0x%08x', $inner->{_key}) } }
1182             : $_
1183             } @$data
1184             ];
1185             }
1186              
1187 15 100 100     108 if ($type eq 'SCALAR' || $type eq 'REF') {
1188 14         27 my $val = $$data;
1189 14   66     85 my $inner = ref($val) && _is_child($val);
1190             return $inner
1191 14 100       280 ? { '__ics__' => { type => $inner->{_type}, child_key => $inner->{_key}, child_key_hex => sprintf('0x%08x', $inner->{_key}) } }
1192             : { '__sv__' => $val };
1193             }
1194              
1195 1         2 return $data;
1196             }
1197             sub _decode_json {
1198 1478     1478   3603 my ($seg, $knot) = @_;
1199              
1200 1478         5352 my $json = $seg->data;
1201              
1202 1478 100       3792 return if ! $json;
1203              
1204             # The return of shmread() is the actual size of the defined size of the
1205             # shared memory segment. Even if the return equates to an empty string
1206             # (which it will if it contains no data), there will always be a length().
1207             # Therefore, we must see if we've tagged this data as a valid structure,
1208             # or else decode will fail
1209              
1210 1202         2760 my $tag = substr $json, 0, 14, '';
1211              
1212 1202 100       2633 if ($tag eq 'IPC::Shareable') {
1213 1201         14758 my $data = decode_json $json;
1214              
1215 1198 100       2690 if (! defined($data)){
1216 1         340 croak "Munged shared memory segment (size exceeded?)";
1217             }
1218              
1219 1197 100 66     9111 _decode_json_restore($data, $knot) if defined $knot && index($json, '"__ics__"') >= 0;
1220              
1221             # Unwrap scalar-tie values encoded as { '__sv__' => val } or { '__ics__' => {...} }
1222 1197 100 66     5402 if (defined $knot && $knot->{_type_int} == TYPE_SCALAR && ref($data) eq 'HASH') {
      100        
1223 84 100       204 if (exists $data->{'__ics__'}) {
1224 44         67 my $prev = $knot->{_data};
1225 44 100 66     151 my $prev_val = (defined $prev && ref($prev)) ? $$prev : undef;
1226 44         102 my $resolved = _decode_json_resolve($data->{'__ics__'}, $prev_val, $knot);
1227 44         277 return \$resolved;
1228             }
1229 40 100       89 if (exists $data->{'__sv__'}) {
1230 23         60 my $val = $data->{'__sv__'};
1231 23         64 return \$val;
1232             }
1233             }
1234              
1235 1130         2905 return $data;
1236             } else {
1237 1         3 return;
1238             }
1239             }
1240             sub _decode_json_restore {
1241 375     375   776 my ($data, $knot) = @_;
1242              
1243 375 50       1334 my $type = Scalar::Util::reftype($data) or return;
1244              
1245             # Reuse existing tied child refs from previous decode where possible.
1246             # This avoids a shmget+semget system call pair for each child on every
1247             # decode cycle — only the first attach per segment incurs that cost.
1248              
1249 375         795 my $prev = $knot->{_data};
1250              
1251 375 100       829 if ($type eq 'HASH') {
    50          
1252 284         762 my $prev_is_hash = ref($prev) eq 'HASH';
1253 284         1311 for my $key (keys %$data) {
1254 613 100 100     2730 next unless ref($data->{$key}) eq 'HASH' && exists $data->{$key}{'__ics__'};
1255             $data->{$key} = _decode_json_resolve(
1256             $data->{$key}{'__ics__'},
1257 365 100       1138 $prev_is_hash ? $prev->{$key} : undef,
1258             $knot,
1259             );
1260             }
1261             }
1262             elsif ($type eq 'ARRAY') {
1263 91         201 my $prev_is_array = ref($prev) eq 'ARRAY';
1264 91 100       195 my $prev_max = $prev_is_array ? $#$prev : -1;
1265 91         297 for my $i (0 .. $#$data) {
1266 192 100 66     775 next unless ref($data->[$i]) eq 'HASH' && exists $data->[$i]{'__ics__'};
1267             $data->[$i] = _decode_json_resolve(
1268 171 100 100     732 $data->[$i]{'__ics__'},
1269             $prev_is_array && $i <= $prev_max ? $prev->[$i] : undef,
1270             $knot,
1271             );
1272             }
1273             }
1274             }
1275             sub _decode_json_resolve {
1276 580     580   1150 my ($info, $existing, $knot) = @_;
1277              
1278 580 100       1075 if (defined $existing) {
1279 520   66     2435 my $inner = ref($existing) && _is_child($existing);
1280 520 100 100     4769 return $existing if $inner && $inner->{_key} == $info->{child_key};
1281             }
1282              
1283 70         183 return _decode_json_reattach($info, $knot);
1284             }
1285             sub _decode_json_reattach {
1286 70     70   139 my ($info, $knot) = @_;
1287              
1288             my %opts = (
1289 70         203 %{ $knot->attributes },
1290             key => $info->{child_key},
1291 70         107 exclusive => 0,
1292             create => 0,
1293             magic => 1,
1294             );
1295              
1296 70 100       354 if ($info->{type} eq 'HASH') {
    100          
    50          
1297 31         52 my %h;
1298 31         314 tie %h, 'IPC::Shareable', \%opts;
1299 31         282 return \%h;
1300             }
1301             elsif ($info->{type} eq 'ARRAY') {
1302 38         58 my @a;
1303 38         241 tie @a, 'IPC::Shareable', \%opts;
1304 38         249 return \@a;
1305             }
1306             elsif ($info->{type} eq 'SCALAR') {
1307 1         2 my $s;
1308 1         6 tie $s, 'IPC::Shareable', \%opts;
1309 1         3 return \$s;
1310             }
1311             }
1312             sub _freeze {
1313 508     508   830 my $seg = shift;
1314 508         788 my $water = shift;
1315              
1316 508         3102 my $ice = freeze $water;
1317 508         25694 substr $ice, 0, 0, 'IPC::Shareable';
1318              
1319 508 100       1960 if (length($ice) > $seg->size) {
1320 1         306 croak "Length of shared data exceeds shared segment size";
1321             }
1322              
1323 507         1649 $seg->shmwrite($ice);
1324             }
1325             sub _thaw {
1326 1609     1609   2608 my $seg = shift;
1327              
1328 1609         6532 my $ice = $seg->shmread;
1329              
1330 1609 100       3910 return if ! $ice;
1331              
1332 1602         4857 my $tag = substr $ice, 0, 14, '';
1333              
1334 1602 100       3887 if ($tag eq 'IPC::Shareable') {
1335 1136         5082 my $water = thaw $ice;
1336 1136 100       71980 if (! defined($water)){
1337 1         173 croak "Munged shared memory segment (size exceeded?)";
1338             }
1339 1135         9555 return $water;
1340             } else {
1341 466         4929 return;
1342             }
1343             }
1344              
1345             # Data management
1346              
1347             sub _tie {
1348 504     504   1726 my ($type, $class, $key_str, $opts);
1349              
1350 504 100       3040 if (scalar @_ == 4) {
1351             # Legacy API allowed a string scalar key
1352 177         1799 ($type, $class, $key_str, $opts) = @_;
1353 177         836 $opts->{key} = $key_str;
1354             }
1355             else {
1356 327         1972 ($type, $class, $opts) = @_;
1357             }
1358              
1359 504         3934 $opts = _parse_args($opts);
1360              
1361 504         3566 my $knot = bless { attributes => $opts }, $class;
1362              
1363 504         3407 $knot->uuid;
1364              
1365 504         2858 my $key = $knot->_shm_key;
1366 504         2264 my $flags = $knot->_shm_flags;
1367 504         1311 my $shm_size = $knot->attributes('size');
1368              
1369 504 100 100     2805 if ($knot->attributes('limit') && $shm_size > SHMMAX_BYTES) {
1370 2         515 croak
1371             "Shared memory segment size '$shm_size' is larger than max size of " .
1372             SHMMAX_BYTES;
1373             }
1374              
1375 502         1085 my $seg;
1376              
1377 502 100       1373 if ($knot->attributes('graceful')) {
1378 8         23 my $exclusive = eval {
1379 8         33 $seg = IPC::Shareable::SharedMem->new(
1380             key => $key,
1381             size => $shm_size,
1382             flags => $flags,
1383             mode => $knot->attributes('mode'),
1384             type => $type,
1385             );
1386 4         10 1;
1387             };
1388              
1389 8 100       64 if (! defined $exclusive) {
1390 4 100       18 if ($knot->attributes('warn')) {
1391 1         2 my $key = lc(sprintf("0x%X", $knot->_shm_key));
1392              
1393 1         27 warn "Process ID $$ exited due to exclusive shared memory collision at segment/semaphore key '$key'\n";
1394             }
1395 4         574 exit(0);
1396             }
1397             }
1398             else {
1399 494         1817 $seg = IPC::Shareable::SharedMem->new(
1400             key => $key,
1401             size => $shm_size,
1402             flags => $flags,
1403             mode => $knot->attributes('mode'),
1404             type => $type,
1405             );
1406             }
1407              
1408 496 100       1708 if (! defined $seg) {
1409 7 100       59 if ($!{ENOMEM}) {
1410 2         312 croak "\nERROR: Could not create shared memory segment: $!\n\n" .
1411             "Are you using too large a segment size, or spawning too many segments?";
1412             }
1413              
1414 5 50       96 if ($!{ENOSPC}) {
1415 0         0 croak "\nERROR: Could not create shared memory segment: $!\n\n" .
1416             "Are you spawning too many segments (in a loop perhaps)?";
1417             }
1418              
1419 5 100 66     75 if (! $knot->attributes('create')) {
    100          
1420 3         1649 confess "ERROR: Could not acquire shared memory segment... 'create' ".
1421             "option is not set, and the segment hasn't been created " .
1422             "yet:\n\n $!";
1423             }
1424             elsif ($knot->attributes('create') && $knot->attributes('exclusive')){
1425 1         152 croak "ERROR: Could not create shared memory segment. 'create' " .
1426             "and 'exclusive' are set. Does the segment already exist? " .
1427             "\n\n$!";
1428             }
1429             else {
1430 1         160 croak "ERROR: Could not create shared memory segment.\n\n$!";
1431             }
1432             }
1433              
1434             # Try to attach to an existing semaphore set first using nsems=0, which
1435             # avoids EINVAL on macOS/BSD when the existing set has fewer slots than
1436             # the requested count. If the set does not exist yet, fall through to
1437             # create a new 4-slot set (SEM_MARKER=0, SEM_PROTECTED=1, shared/write
1438             # lock counters=2/3).
1439 489   100     1625 my $sem = IPC::Semaphore->new($key, 0, $seg->flags & 0777)
1440             // IPC::Semaphore->new($key, 4, $seg->flags);
1441              
1442 489 100       11671 if (! defined $sem){
1443 1         211 croak "Could not create semaphore set: $!\n";
1444             }
1445              
1446 488 100       874 if (! $sem->op(@{ $semop_args{(LOCK_SH)} }) ) {
  488         3850  
1447 1         172 croak "Could not obtain semaphore set lock: $!\n";
1448             }
1449              
1450 487 100       16035 %$knot = (
    100          
1451             %$knot,
1452             _hkey_list => undef,
1453             _key => $key,
1454             _key_hex => $seg->key_hex,
1455             _lock => 0,
1456             _shm => $seg,
1457             _sem => $sem,
1458             _type => $type,
1459             _type_int => $type eq 'HASH' ? TYPE_HASH : $type eq 'ARRAY' ? TYPE_ARRAY : TYPE_SCALAR,
1460             _was_changed => 0,
1461             );
1462              
1463 487         1994 my $serializer = $knot->attributes('serializer');
1464              
1465 487 100       1520 if ($serializer eq 'json') {
1466 216         475 my $data;
1467 216         879 my $decoded_ok = eval { $data = $knot->_decode($seg); 1 };
  216         1151  
  213         487  
1468              
1469 216 100       696 if (! $decoded_ok) {
1470             # JSON decode threw — the segment may contain legacy Storable data.
1471             # Try Storable; if it succeeds, silently switch this session over
1472             # and warn the caller so they know to migrate.
1473 3         5 my $storable_data;
1474 3         7 my $thaw_ok = eval { $storable_data = _thaw($seg); 1 };
  3         9  
  3         7  
1475              
1476 3 50 33     21 if ($thaw_ok && defined $storable_data) {
1477 3         927 carp sprintf(
1478             "IPC::Shareable: segment 0x%08x contains Storable-encoded data; "
1479             . "switching serializer to 'storable' for this session. "
1480             . "Re-create the segment to migrate it to JSON.",
1481             $key
1482             );
1483 3         34 $knot->{attributes}{serializer} = 'storable';
1484 3         12 $knot->{_data} = $storable_data;
1485             }
1486             else {
1487 0         0 die $@;
1488             }
1489             }
1490             else {
1491 213         833 $knot->{_data} = $data;
1492             }
1493             }
1494             else {
1495 271         981 $knot->{_data} = _thaw($seg);
1496             }
1497              
1498             # Register unconditionally so any process that attaches to an existing
1499             # segment (create=>0, re-attach, cross-process) is also tracked for
1500             # clean_up_all(). Previously only new segments were registered here,
1501             # requiring the Dumper hack in global_register() to catch the rest.
1502              
1503 487 100       1856 if (! exists $global_register{$knot->seg->id}) {
1504 394         964 $global_register{$knot->seg->id} = $knot;
1505             }
1506              
1507 487 100       3161 if ($sem->getval(SEM_MARKER) != SHM_EXISTS) {
1508              
1509 371   33     12300 $process_register{$knot->seg->id} ||= $knot;
1510              
1511 371         1407 $sem->setval(SEM_PROTECTED, $knot->attributes('protected'));
1512              
1513 371 100       8800 if (! $sem->setval(SEM_MARKER, SHM_EXISTS)){
1514 1         807 croak "Couldn't set semaphore during object creation: $!";
1515             }
1516             }
1517             else {
1518             # Segment already existed — restore the protected attribute from the
1519             # semaphore so that clean_up_all() in this process correctly skips it
1520             # even when the caller did not explicitly pass protected => N.
1521 116         2927 my $stored_protected = $sem->getval(SEM_PROTECTED);
1522 116 100 66     2347 $knot->{attributes}{protected} = $stored_protected
1523             if defined $stored_protected && $stored_protected != 0;
1524             }
1525              
1526 486         15729 $sem->op(@{ $semop_args{(LOCK_SH|LOCK_UN)} });
  486         2433  
1527              
1528 486         24790 return $knot;
1529             }
1530             sub _magic_tie {
1531 144     144   455 my ($parent, $val, $identifier) = @_;
1532              
1533 144         683 my $key;
1534              
1535 144 100 100     1113 if ($parent->{_key} == IPC_PRIVATE && $parent->attributes('serializer') ne 'json') {
1536 49         122 $key = IPC_PRIVATE;
1537             }
1538             else {
1539 95         667 $key = _shm_key_rand();
1540             }
1541              
1542             # The individual options in the hash override any pre-set options that are
1543             # being inherited from the parent
1544              
1545             my %opts = (
1546 143         364 %{ $parent->attributes },
  143         424  
1547             key => $key,
1548             exclusive => 1,
1549             create => 1,
1550             magic => 1,
1551             );
1552              
1553             # XXX I wish I didn't have to take a copy of data here and copy it back in
1554             # XXX Also, have to peek inside potential objects to see their implementation
1555              
1556 143         460 my $child;
1557 143   50     579 my $type = Scalar::Util::reftype($val) || '';
1558              
1559 143 100       426 if ($type eq "HASH") {
    100          
    100          
1560 102         364 my %copy = %$val;
1561 102         2204 $child = tie %$val, 'IPC::Shareable', $key, { %opts };
1562 102 50       649 croak "Could not create inner tie" if ! $child;
1563              
1564 102 100       835 _reset_segment($parent, $identifier) if $opts{tidy};
1565              
1566 102         886 %$val = %copy;
1567             }
1568             elsif ($type eq "ARRAY") {
1569 38         139 my @copy = @$val;
1570 38         632 $child = tie @$val, 'IPC::Shareable', $key, { %opts };
1571 38 50       193 croak "Could not create inner tie" if ! $child;
1572              
1573 38 100       215 _reset_segment($parent, $identifier) if $opts{tidy};
1574              
1575 38         192 @$val = @copy;
1576             }
1577             elsif ($type eq "SCALAR") {
1578 2         5 my $copy = $$val;
1579 2         40 $child = tie $$val, 'IPC::Shareable', $key, { %opts };
1580 2 50       24 croak "Could not create inner tie" if ! $child;
1581              
1582 2         15 $$val = $copy;
1583             }
1584             else {
1585 1         895 croak "Variables of type $type not implemented";
1586             }
1587              
1588 142         1189 return $child;
1589             }
1590             sub _is_child {
1591 1245 100   1245   17659 return $_have_xs
1592             ? _is_child_xs($_[0])
1593             : _is_child_pp($_[0]);
1594             }
1595              
1596             sub _is_child_pp {
1597 14 100   14   47 my $data = shift or return;
1598              
1599 13         28 my $type = Scalar::Util::reftype( $data );
1600 13 100       38 return unless $type;
1601              
1602 11         16 my $obj;
1603              
1604 11 100       46 if ($type eq "HASH") {
    100          
    100          
1605 6         17 $obj = tied %$data;
1606             }
1607             elsif ($type eq "ARRAY") {
1608 2         2093 $obj = tied @$data;
1609             }
1610             elsif ($type eq "SCALAR") {
1611 2         4 $obj = tied $$data;
1612             }
1613              
1614 11 100       33 if (ref $obj eq 'IPC::Shareable') {
1615 7         26 return $obj;
1616             }
1617              
1618 4         31 return;
1619             }
1620             sub _need_tie {
1621 144     144   468 my ($knot, $val, $identifier) = @_;
1622              
1623 144         390 my $type = Scalar::Util::reftype($val);
1624 144 50       390 return 0 if ! $type;
1625              
1626 144         264 my $need_tie;
1627              
1628 144 100       473 if ($type eq "HASH") {
    100          
    50          
1629 103         386 $need_tie = !(tied %$val);
1630             }
1631             elsif ($type eq "ARRAY") {
1632 38         98 $need_tie = !(tied @$val);
1633             }
1634             elsif ($type eq "SCALAR") {
1635 3         9 $need_tie = !(tied $$val);
1636             }
1637              
1638 144 100       1305 return $need_tie ? 1 : 0;
1639             }
1640              
1641             # Segment/semaphore operations
1642              
1643             sub _key_str_to_int {
1644             # Convert any key format (hex string, decimal integer string, or arbitrary
1645             # text) to a 32-bit integer using the same algorithm as _shm_key(), but
1646             # without the %used_ids side effect. Safe to call any number of times.
1647 5     5   2317 my ($key_str) = @_;
1648              
1649 5 100       43 return hex($key_str) if $key_str =~ /^0x[0-9a-fA-F]+$/i;
1650 3 100       25 return $key_str + 0 if $key_str =~ /^\d+$/;
1651              
1652 2         11 my $int = crc32($key_str);
1653 2 100       7 $int -= MAX_KEY_INT_SIZE if $int > MAX_KEY_INT_SIZE;
1654 2         6 return $int;
1655             }
1656             sub _lock_children {
1657 125     125   420 my ($root_knot, $flags) = @_;
1658              
1659 125         353 my @locked;
1660 125         530 my %seen = ($root_knot->seg->id => 1);
1661 125         721 my @stack = ([$root_knot, 0]);
1662              
1663 125         536 while (@stack) {
1664 177         339 my $frame = $stack[-1];
1665 177         534 my ($knot, $idx) = @$frame;
1666              
1667 177         532 my $data = $knot->{_data};
1668 177   100     705 my $rtype = Scalar::Util::reftype($data) // '';
1669              
1670 177 100       1087 my @vals = $rtype eq 'HASH' ? values %$data
    100          
1671             : $rtype eq 'ARRAY' ? @$data
1672             : ();
1673              
1674 177         637 my $found = 0;
1675 177         577 for (my $i = $idx; $i < @vals; $i++) {
1676 272         497 my $val = $vals[$i];
1677 272 100       1018 next unless ref($val);
1678 29         140 my $child = _is_child($val);
1679 29 50 33     497 next unless $child && $child->seg;
1680              
1681 29         89 my $id = $child->seg->id;
1682 29 50       189 next if $seen{$id}++;
1683              
1684 29 100       158 if (! $child->sem->op(@{ $semop_args{$flags} })) {
  29         225  
1685 2         62 for my $locked (reverse @locked) {
1686 2         30 my $rflags = $locked->{_lock} | LOCK_UN;
1687 2 50       36 $rflags ^= LOCK_NB if $rflags & LOCK_NB;
1688 2         49 $locked->sem->op(@{ $semop_args{$rflags} });
  2         46  
1689 2         32 $locked->{_lock} = 0;
1690             }
1691 2         11 return;
1692             }
1693              
1694 27         996 $child->{_data} = $child->_decode($child->seg);
1695 27         108 $child->{_lock} = $flags;
1696 27         281 push @locked, $child;
1697              
1698 27         128 $frame->[1] = $i + 1;
1699 27         123 push @stack, [$child, 0];
1700 27         104 $found = 1;
1701 27         71 last;
1702             }
1703 175 100       874 pop @stack unless $found;
1704             }
1705              
1706 123         3978 return \@locked;
1707             }
1708             sub _reset_segment {
1709 123     123   428 my ($parent, $id) = @_;
1710              
1711 123   100     647 my $parent_type = Scalar::Util::reftype($parent->{_data}) || '';
1712              
1713 123 100       430 if ($parent_type eq 'HASH') {
    100          
1714 81         176 my $data = $parent->{_data};
1715 81 100       327 if (exists $data->{$id}) {
1716 9   100     60 my $child_type = Scalar::Util::reftype($data->{$id}) || '';
1717 9 100 66     73 if ($child_type eq 'HASH' && tied %{ $data->{$id} }) {
  7 50 33     38  
1718 7         15 (tied %{ $parent->{_data}{$id} })->remove;
  7         56  
1719             }
1720 0         0 elsif ($child_type eq 'ARRAY' && tied @{ $data->{$id} }) {
1721 0         0 (tied @{ $parent->{_data}{$id} })->remove;
  0         0  
1722             }
1723             }
1724             }
1725             elsif ($parent_type eq 'ARRAY') {
1726 30         70 my $data = $parent->{_data};
1727 30 100       160 if (exists $data->[$id]) {
1728 3   50     25 my $child_type = Scalar::Util::reftype($data->[$id]) || '';
1729 3 50 33     26 if ($child_type eq 'HASH' && tied %{ $data->[$id] }) {
  0 50 33     0  
1730 0         0 (tied %{ $parent->{_data}[$id] })->remove;
  0         0  
1731             }
1732 3         12 elsif ($child_type eq 'ARRAY' && tied @{ $data->[$id] }) {
1733 3         25 (tied @{ $parent->{_data}[$id] })->remove;
  3         22  
1734             }
1735             }
1736             }
1737             }
1738             sub _shm_data_summary {
1739 6     6   12 my ($knot) = @_;
1740              
1741 6         16 my $data = $knot->{_data};
1742 6   50     14 my $rtype = Scalar::Util::reftype($data) // '';
1743              
1744 6 100       21 if ($rtype eq 'SCALAR') {
1745 3         13 my $v = $$data;
1746 3 50       17 return defined $v ? qq("$v") : '(undef)';
1747             }
1748              
1749 3 50       15 if ($rtype eq 'HASH') {
1750 3         6 my @parts;
1751 3         23 for my $k (sort keys %$data) {
1752 3         13 my $v = $data->{$k};
1753 3 100       14 if (ref $v) {
1754 1   50     10 my $vt = Scalar::Util::reftype($v) // '';
1755 1 0       16 my $child = $vt eq 'HASH' ? tied(%$v)
    0          
    50          
1756             : $vt eq 'ARRAY' ? tied(@$v)
1757             : $vt eq 'SCALAR' ? tied($$v)
1758             : undef;
1759             push @parts, $child && $child->{_key_hex}
1760 1 50 33     17 ? qq($k => {_key_hex}>)
1761             : "$k => ";
1762             }
1763             else {
1764 2 50       16 push @parts, defined $v ? qq($k => "$v") : "$k => (undef)";
1765             }
1766             }
1767 3 50       26 return @parts ? '{ ' . join(', ', @parts) . ' }' : '{}';
1768             }
1769              
1770 0 0       0 if ($rtype eq 'ARRAY') {
1771 0         0 my @parts;
1772 0         0 for my $v (@$data) {
1773 0 0       0 if (ref $v) {
1774 0   0     0 my $vt = Scalar::Util::reftype($v) // '';
1775 0 0       0 my $child = $vt eq 'HASH' ? tied(%$v)
    0          
    0          
1776             : $vt eq 'ARRAY' ? tied(@$v)
1777             : $vt eq 'SCALAR' ? tied($$v)
1778             : undef;
1779             push @parts, $child && $child->{_key_hex}
1780 0 0 0     0 ? "{_key_hex}>"
1781             : '';
1782             }
1783             else {
1784 0 0       0 push @parts, defined $v ? qq("$v") : '(undef)';
1785             }
1786             }
1787 0         0 return '[' . join(', ', @parts) . ']';
1788             }
1789              
1790 0         0 return '(unknown type)';
1791             }
1792             sub _shm_flags {
1793             # Parses the anonymous hash passed to constructors; returns a list
1794             # of args suitable for passing to shmget
1795              
1796 504     504   1271 my ($knot) = @_;
1797              
1798 504         1275 my $flags = 0;
1799              
1800 504 100       1392 $flags |= IPC_CREAT if $knot->attributes('create');
1801 504 100       7964 $flags |= IPC_EXCL if $knot->attributes('exclusive');
1802              
1803 504         1764 return $flags;
1804             }
1805             sub _shm_key {
1806             # Generates a 32-bit CRC on the key string. The $key_str parameter is used
1807             # for testing only, for purposes of testing various key strings
1808              
1809 521     521   3580 my ($knot, $key_str) = @_;
1810              
1811 521   100     5216 $key_str //= ($knot->attributes('key') || '');
      66        
1812              
1813 521         1231 my $key;
1814              
1815 521 100       5588 if ($key_str eq '') {
    100          
    100          
1816 101         545 $key = IPC_PRIVATE;
1817             }
1818             elsif ($key_str =~ /^0x[0-9a-fA-F]+$/i) {
1819             # User specified an explicit hex string key (e.g. '0xDEADBEEF'); use the
1820             # bit pattern as-is so the segment key seen by ipcs(1) matches exactly.
1821 20         77 $key = hex($key_str);
1822 20         110 $used_ids{$key}++;
1823 20         155 return $key;
1824             }
1825             elsif ($key_str =~ /^\d+$/) {
1826             # User specified an explicit decimal integer key; use it as-is.
1827 183         424 $key = $key_str;
1828 183         593 $used_ids{$key}++;
1829 183         550 return $key;
1830             }
1831             else {
1832             # String key: compute a 32-bit CRC and apply overflow correction so the
1833             # result fits in a signed 32-bit key_t.
1834 217         1234 $key = crc32($key_str);
1835             }
1836              
1837 318         2146 $used_ids{$key}++;
1838              
1839 318 100       1289 if ($key > MAX_KEY_INT_SIZE) {
1840 129         469 $key = $key - MAX_KEY_INT_SIZE;
1841              
1842 129 50       821 if ($key == 0) {
1843 0         0 croak "We've calculated a key which equals 0. This is a fatal error";
1844             }
1845             }
1846              
1847 318         1142 return $key;
1848             }
1849             sub _shm_key_rand {
1850 95     95   154 my $key;
1851              
1852             # Unfortunatly, the only way I know how to check if a segment exists is
1853             # to actually create it. We must do that here, then remove it just to
1854             # ensure the slot is available
1855              
1856 95         180 my $verified_exclusive = 0;
1857              
1858 95         181 my $check_count = 0;
1859              
1860 95   100     640 while (! $verified_exclusive && $check_count < EXCLUSIVE_CHECK_LIMIT) {
1861 104         358 $check_count++;
1862              
1863 104         312 $key = _shm_key_rand_int();
1864              
1865 104 100       576 next if $used_ids{$key};
1866              
1867 94         206 my $flags;
1868 94         379 $flags |= IPC_CREAT;
1869 94         679 $flags |= IPC_EXCL;
1870              
1871 94         989 my $seg;
1872              
1873 94         235 my $shm_slot_available = eval {
1874 94         537 $seg = IPC::Shareable::SharedMem->new(
1875             key => $key,
1876             size => 1,
1877             flags => $flags,
1878             );
1879 94         245 1;
1880             };
1881              
1882 94 50       287 if ($shm_slot_available) {
1883 94         185 $verified_exclusive = 1;
1884 94 50       538 $seg->remove if $seg;
1885             }
1886             }
1887              
1888 95 100       413 if (! $verified_exclusive) {
1889 1         444 croak
1890             "_shm_key_rand() can't get an available key after $check_count tries";
1891             }
1892              
1893 94         430 $used_ids{$key}++;
1894              
1895 94         305 return $key;
1896             }
1897             sub _shm_key_rand_int {
1898 93     93   414 return int(rand(1_000_000));
1899             }
1900             sub _read_check {
1901 948     948   1818 my ($knot) = @_;
1902              
1903             # Advisory only: never blocks the read, only warns. Called from FETCH
1904             # when this knot is unlocked (a locked FETCH uses _data cache and never
1905             # touches shmem). Race window exists between this getval() and the
1906             # subsequent _decode() — a writer could acquire in between — but this
1907             # still catches the common case where a reader forgot to lock.
1908              
1909 948 100       2677 return unless $knot->attributes('enforced_read_locking');
1910 936 100       2072 return unless $knot->attributes('violated_read_lock_warn');
1911              
1912             # getval() can return undef if the semaphore set has been removed (e.g.
1913             # after clean_up_all). The check is advisory only, so silently skip when
1914             # the semaphore is no longer reachable.
1915              
1916 933         3263 my $writers = $knot->sem->getval(SEM_WRITERS);
1917 933 50       27757 return unless defined $writers;
1918              
1919 933 100       2242 if ($writers > 0) {
1920 4         20 my $uuid = $knot->uuid;
1921 4         21 my $seg_id = $knot->seg->id;
1922 4         325 warn "Object with UUID $uuid attempted read from segment ID "
1923             . "$seg_id which is exclusively locked (enforced read locking "
1924             . "enabled); returned data may be stale or partially-written. "
1925             . "Acquire LOCK_SH before reading to guarantee a coherent snapshot";
1926             }
1927              
1928 933         2036 return;
1929             }
1930             sub _write_permitted {
1931 952     952   2154 my ($knot) = @_;
1932              
1933 952 100       3195 return 1 unless $knot->attributes('enforced_write_locking');
1934              
1935             # If this knot itself holds LOCK_EX it is the owner of the lock and is
1936             # permitted to write.
1937              
1938 924 100       3582 return 1 if $knot->{_lock} & LOCK_EX;
1939              
1940 866         2169 my $sem = $knot->sem;
1941              
1942             # Semaphore index 2 is the write-lock counter; it is 1 when any other knot
1943             # holds LOCK_EX (set via SEM_UNDO so it auto-releases on process exit).
1944              
1945             # Block if any process holds LOCK_EX
1946              
1947 866 100       3470 if ($sem->getval(SEM_WRITERS) > 0) {
1948 12 100       306 if ($knot->attributes('violated_write_lock_warn')) {
1949 11         31 my $uuid = $knot->uuid;
1950 11         34 my $seg_id = $knot->seg->id;
1951 11         225 warn "Object with UUID $uuid attempted write to segment ID "
1952             . "$seg_id which is exclusively locked (enforced write "
1953             . "locking enabled). Your write was not accepted. Lock with "
1954             . "LOCK_EX to ensure successful writes when a segment is "
1955             . "already locked";
1956             }
1957              
1958 12         3385 return 0;
1959             }
1960              
1961             # Block if any process holds LOCK_SH (active readers present)
1962              
1963 854 100       16587 if ($sem->getval(SEM_READERS) > 0) {
1964 3 50       26 if ($knot->attributes('violated_write_lock_warn')) {
1965 3         5 my $uuid = $knot->uuid;
1966 3         6 my $seg_id = $knot->seg->id;
1967 3         54 warn "Object with UUID $uuid attempted write to segment ID "
1968             . "$seg_id which has active readers (enforced write locking "
1969             . "enabled)";
1970             }
1971              
1972 3         1517 return 0;
1973             }
1974              
1975 851         10869 return 1;
1976             }
1977              
1978             # Misc
1979              
1980             sub _parse_args {
1981 504     504   1408 my ($opts) = @_;
1982              
1983 504 100       4446 $opts = defined $opts ? $opts : { %default_options };
1984              
1985 504         10720 for my $k (keys %default_options) {
1986 8064 100       23591 if (not defined $opts->{$k}) {
    100          
1987 3416         9465 $opts->{$k} = $default_options{$k};
1988             }
1989             elsif ($opts->{$k} eq 'no') {
1990 2 100       11 if ($^W) {
1991 1         19 require Carp;
1992 1         356 Carp::carp("Use of `no' in IPC::Shareable args is obsolete");
1993             }
1994              
1995 2         18 $opts->{$k} = 0;
1996             }
1997             }
1998 504   66     7648 $opts->{owner} = ($opts->{owner} or $$);
1999 504   100     4699 $opts->{magic} = ($opts->{magic} or 0);
2000 504         1834 return $opts;
2001             }
2002             sub _end {
2003 126     126   3176979 for my $s (values %process_register) {
2004 140         451 eval { unlock($s) };
  140         841  
2005 140 50       792 next if $s->attributes('protected');
2006 140 100       510 next if ! $s->attributes('destroy');
2007 124 100       1127 next if $s->attributes('owner') != $$;
2008 81         175 eval { remove($s) };
  81         330  
2009             }
2010             }
2011             END {
2012 79     79   2358182 _end();
2013             }
2014              
2015       0     sub _placeholder {}
2016              
2017             1;
2018              
2019             __END__