File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 875 910 96.1
branch 487 598 81.4
condition 133 207 64.2
subroutine 80 81 98.7
pod 20 20 100.0
total 1595 1816 87.8


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