File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 995 1045 95.2
branch 551 696 79.1
condition 157 238 65.9
subroutine 89 90 98.8
pod 22 22 100.0
total 1814 2091 86.7


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