File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 943 986 95.6
branch 519 650 79.8
condition 145 220 65.9
subroutine 87 88 98.8
pod 22 22 100.0
total 1716 1966 87.2


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