File Coverage

blib/lib/IPC/Shareable.pm
Criterion Covered Total %
statement 878 913 96.1
branch 487 602 80.9
condition 133 207 64.2
subroutine 81 82 98.7
pod 20 20 100.0
total 1599 1824 87.6


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