File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 1009 1061 95.1
branch 565 718 78.6
condition 167 250 66.8
subroutine 89 90 98.8
pod 22 22 100.0
total 1852 2141 86.5


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