File Coverage

blib/lib/KiokuDB/LiveObjects.pm
Criterion Covered Total %
statement 240 249 96.3
branch 89 108 82.4
condition 32 40 80.0
subroutine 43 44 97.7
pod 19 29 65.5
total 423 470 90.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package KiokuDB::LiveObjects;
4 22     22   31902 use Moose;
  22         393482  
  22         152  
5              
6 22     22   80432 use Scalar::Util qw(weaken refaddr);
  22         46  
  22         1374  
7 22     22   8154 use KiokuDB::LiveObjects::Guard;
  22         476  
  22         964  
8 22     22   598 use Hash::Util::FieldHash::Compat qw(fieldhash);
  22         1829  
  22         231  
9 22     22   1269 use Carp qw(croak);
  22         122  
  22         1033  
10 22     22   48 BEGIN { local $@; eval 'use Devel::PartialDump qw(croak)' };
  22     22   1225  
  22         10630  
  22         484586  
  22         230  
11 22     22   8329 use Set::Object;
  22         15221  
  22         1028  
12              
13 22     22   7370 use KiokuDB::LiveObjects::Scope;
  22         68  
  22         888  
14 22     22   10574 use KiokuDB::LiveObjects::TXNScope;
  22         64  
  22         876  
15              
16 22     22   165 use Moose::Util::TypeConstraints;
  22         33  
  22         196  
17              
18 22     22   38042 use namespace::clean -except => 'meta';
  22         43  
  22         159  
19              
20             coerce __PACKAGE__, from "HashRef", via { __PACKAGE__->new($_) };
21              
22             has clear_leaks => (
23             isa => "Bool",
24             is => "rw",
25             );
26              
27             has cache => (
28             isa => "Cache::Ref",
29             is => "ro",
30             );
31              
32             has leak_tracker => (
33             isa => "CodeRef|Object",
34             is => "rw",
35             clearer => "clear_leak_tracker",
36             );
37              
38             has keep_entries => (
39             isa => "Bool",
40             is => "ro",
41             default => 1,
42             );
43              
44             has [qw(_objects _entries _object_entries)] => (
45             isa => "HashRef",
46             is => "ro",
47             init_arg => undef,
48             default => sub { fieldhash my %hash },
49             );
50              
51             has _ids => (
52             #metaclass => 'Collection::Hash',
53             isa => "HashRef",
54             is => "ro",
55             init_arg => undef,
56             default => sub { return {} },
57             );
58              
59             sub _id_info {
60 30890     30890   45669 my ( $self, @ids ) = @_;
61              
62 22     22   12224 no warnings 'uninitialized'; # @ids can contain undefs
  22         41  
  22         50212  
63              
64 30890 100       55609 if ( @ids == 1 ) {
65 29692         746412 return $self->_ids->{$ids[0]};
66             } else {
67 1198         1387 return @{ $self->_ids }{@ids};
  1198         30393  
68             }
69             }
70              
71             sub _vivify_id_info {
72 24222     24222   26440 my ( $self, $id ) = @_;
73              
74 24222         21697 my $info;
75              
76 24222         588474 my $i = $self->_ids;
77              
78 24222 100       55286 unless ( $info = $i->{$id} ) {
79 8143         31287 $info = { guard => KiokuDB::LiveObjects::Guard->new( $i, $id ) };
80 8143         23853 weaken( $i->{$id} = $info );
81             }
82              
83 24222         33086 return $info;
84             }
85              
86             sub id_to_object {
87 9559     9559 1 13418 my ( $self, $id ) = @_;
88              
89 9559 100       234402 if ( my $c = $self->cache ) {
90 4304         14113 $c->hit($id);
91             }
92              
93 9559 100       304514 if ( my $data = $self->_id_info($id) ) {
94 4518         15434 return $data->{object};
95             }
96             }
97              
98             sub ids_to_objects {
99 621     621 1 8256 my ( $self, @ids ) = @_;
100              
101 621 100       15506 if ( my $c = $self->cache ) {
102 240         959 $c->hit(@ids);
103             }
104              
105 621 100       19106 map { $_ && $_->{object} } $self->_id_info(@ids);
  3596         12337  
106             }
107              
108             sub known_ids {
109 19     19 0 1565 keys %{ shift->_ids };
  19         989  
110             }
111              
112             sub live_ids {
113 0     0 1 0 my $self = shift;
114              
115 0         0 grep { ref $self->_id_info($_)->{object} } $self->known_ids;
  0         0  
116             }
117              
118             sub live_objects {
119 5944     5944 1 13527 grep { ref } map { $_->{object} } values %{ shift->_ids };
  8903         20286  
  8903         14985  
  5944         150108  
120             }
121              
122             sub id_to_entry {
123 4901     4901 0 42084 my ( $self, $id ) = @_;
124              
125 4901 100       14957 if ( my $data = $self->_id_info($id) ) {
126 1931         6602 return $data->{entry};
127             }
128              
129 2970         15379 return undef;
130             }
131              
132             sub ids_to_entries {
133 965     965 1 8542 my ( $self, @ids ) = @_;
134              
135 965 100       3471 return $self->id_to_entry($ids[0]) if @ids == 1;
136              
137 577 100       1338 map { $_ && $_->{entry} } $self->_id_info(@ids);
  1485         4198  
138             }
139              
140             sub loaded_ids {
141 10     10 0 33 my $self = shift;
142              
143 10         35 grep { $self->_id_info($_)->{entry} } $self->known_ids;
  6         28  
144             }
145              
146             sub live_entries {
147 2632     2632 1 5128 grep { ref } map { $_->{entry} } values %{ shift->_ids };
  674         1724  
  674         1383  
  2632         68209  
148             }
149              
150             has current_scope => (
151             isa => "KiokuDB::LiveObjects::Scope",
152             is => "ro",
153             writer => "_set_current_scope",
154             clearer => "_clear_current_scope",
155             weak_ref => 1,
156             );
157              
158             has _known_scopes => (
159             isa => "Set::Object",
160             is => "ro",
161             default => sub { Set::Object::Weak->new },
162             );
163              
164             sub detach_scope {
165 3483     3483 1 4613 my ( $self, $scope ) = @_;
166              
167 3483         93048 my $current_scope = $self->current_scope;
168 3483 100 66     28241 if ( defined($current_scope) and refaddr($current_scope) == refaddr($scope) ) {
169 3482 100       96940 if ( my $parent = $scope->parent ) {
170 695         22698 $self->_set_current_scope($parent);
171             } else {
172 2787         91417 $self->_clear_current_scope;
173             }
174             }
175             }
176              
177             sub remove_scope {
178 3482     3482 1 4662 my ( $self, $scope ) = @_;
179              
180 3482         10869 $self->detach_scope($scope);
181              
182 3482         122426 $scope->clear;
183              
184 3482         99905 my $known = $self->_known_scopes;
185              
186 3482         29638 $known->remove($scope);
187              
188 3482 100       17489 if ( $known->size == 0 ) {
189 2786         9253 $self->check_leaks;
190             }
191             }
192              
193             sub check_leaks {
194 2786     2786 0 4056 my $self = shift;
195              
196 2786 50       74283 return if $self->_known_scopes->size;
197              
198 2786 100       9531 if ( my @still_live = grep { defined } $self->live_objects ) {
  781         2619  
199             # immortal objects are still live but not considered leaks
200 483         12664 my $o = $self->_objects;
201 781         1736 my @leaked = grep {
202 483         1359 my $i = $o->{$_};
203 781   100     4445 not($i->{immortal} or $i->{cache})
204             } @still_live;
205              
206 483 100       13720 if ( $self->clear_leaks ) {
207 2         11 $self->clear;
208             }
209              
210 483 100 100     13254 if ( my $tracker = $self->leak_tracker and @leaked ) {
211 1 50       6 if ( ref($tracker) eq 'CODE' ) {
212 1         6 $tracker->(@leaked);
213             } else {
214 0         0 $tracker->leaked_objects(@leaked);
215             }
216             }
217             }
218             }
219              
220             has txn_scope => (
221             isa => "KiokuDB::LiveObjects::TXNScope",
222             is => "ro",
223             writer => "_set_txn_scope",
224             clearer => "_clear_txn_scope",
225             weak_ref => 1,
226             );
227              
228             sub new_scope {
229 3482     3482 1 43319 my $self = shift;
230              
231 3482         97411 my $parent = $self->current_scope;
232              
233 3482 100       107870 my $child = KiokuDB::LiveObjects::Scope->new(
234             ( $parent ? ( parent => $parent ) : () ),
235             live_objects => $self,
236             );
237              
238 3482         123973 $self->_set_current_scope($child);
239              
240 3482         96809 $self->_known_scopes->insert($child);
241              
242 3482         13324 return $child;
243             }
244              
245             sub new_txn {
246 2183     2183 1 3471 my $self = shift;
247              
248 2183 100       64716 return unless $self->keep_entries;
249              
250 1143         29619 my $parent = $self->txn_scope;
251              
252 1143 100       37213 my $child = KiokuDB::LiveObjects::TXNScope->new(
253             ( $parent ? ( parent => $parent ) : () ),
254             live_objects => $self,
255             );
256              
257 1143         38985 $self->_set_txn_scope($child);
258              
259 1143         2678 return $child;
260             }
261              
262             sub objects_to_ids {
263 2185     2185 1 13308 my ( $self, @objects ) = @_;
264              
265 2185 100       7065 return $self->object_to_id($objects[0])
266             if @objects == 1;
267              
268 648 100       1337 map { $_ && $_->{guard}->key } @{ $self->_objects }{@objects};
  1633         6555  
  648         17370  
269             }
270              
271             sub object_to_id {
272 38142     38142 1 50788 my ( $self, $obj ) = @_;
273              
274 38142 100       953820 if ( my $info = $self->_objects->{$obj} ){
275 8234         27204 return $info->{guard}->key;
276             }
277              
278 29908         84925 return undef;
279             }
280              
281             sub objects_to_entries {
282 340     340 1 866 my ( $self, @objects ) = @_;
283              
284 340         1009 return $self->ids_to_entries( $self->objects_to_ids(@objects) );
285             }
286              
287             sub object_to_entry {
288 10218     10218 1 13918 my ( $self, $obj ) = @_;
289              
290 10218   100     17925 return $self->id_to_entry( $self->object_to_id($obj) || return );
291             }
292              
293             sub id_in_root_set {
294 867     867 0 1286 my ( $self, $id ) = @_;
295              
296 867 50       1847 if ( my $data = $self->_id_info($id) ) {
297 867         6440 return $data->{root};
298             }
299              
300 0         0 return undef;
301             }
302              
303             sub id_in_storage {
304 13883     13883 0 19537 my ( $self, $id ) = @_;
305              
306 13883 100       27716 if ( my $data = $self->_id_info($id) ) {
307 3549         11650 return $data->{in_storage};
308             }
309              
310 10334         32033 return undef;
311             }
312              
313              
314             sub object_in_storage {
315 1316     1316 0 2040 my ( $self, $object ) = @_;
316              
317 1316   100     3081 $self->id_in_storage( $self->object_to_id($object) || return );
318             }
319              
320             sub update_object_entry {
321 1436     1436 0 3447 my ( $self, $object, $entry, %args ) = @_;
322              
323              
324 1436 50       38561 my $s = $self->current_scope or croak "no open live object scope";
325              
326 1436 50       34139 my $info = $self->_objects->{$object} or croak "Object not yet registered";
327 1436         34382 $self->_entries->{$entry} = $info;
328              
329 1436         2627 @{$info}{keys %args} = values %args;
  1436         2298  
330 1436         3944 weaken($info->{entry} = $entry);
331              
332 1436 100       36431 if ( $self->keep_entries ) {
333 785         20489 $self->_object_entries->{$object} = $entry;
334              
335 785 50 33     2572 if ( $args{in_storage} and my $txs = $self->txn_scope ) {
336 0         0 $txs->push($entry);
337             }
338             }
339              
340             # break cycle for passthrough objects
341 1436 50 66     33185 if ( ref($entry->data) and refaddr($object) == refaddr($entry->data) ) {
342 0         0 weaken($entry->{data}); # FIXME there should be a MOP way to do this
343             }
344             }
345              
346             sub register_object {
347 8091     8091 0 16440 my ( $self, $id, $object, %args ) = @_;
348              
349 8091 50       216326 my $s = $self->current_scope or croak "no open live object scope";
350              
351 8091 50       18869 croak($object, " is not a reference") unless ref($object);
352 8091 50 66     79767 croak($object, " is an entry") if blessed($object) && $object->isa("KiokuDB::Entry");
353              
354 8091 100       19055 if ( my $id = $self->object_to_id($object) ) {
355 1         30 croak($object, " is already registered as $id")
356             }
357              
358 8090         15555 my $info = $self->_vivify_id_info($id);
359              
360 8090 50       18947 if ( ref $info->{object} ) {
361 0         0 croak "An object with the id '$id' is already registered ($info->{object} != $object)"
362             }
363              
364 8090         194751 $self->_objects->{$object} = $info;
365              
366 8090         27603 weaken($info->{object} = $object);
367              
368 8090 100 100     213397 if ( $self->keep_entries and my $entry = $info->{entry} ) {
369 4165         108068 $self->_object_entries->{$object} = $entry;
370             }
371              
372 8090         15172 @{$info}{keys %args} = values %args;
  8090         11898  
373              
374 8090 100 100     24064 if ( $args{cache} and my $c = $self->cache ) {
375 48         192 $c->set( $id => $object );
376             }
377              
378 8090         286502 $s->push($object);
379             }
380              
381             sub register_entry {
382 16132     16132 0 33590 my ( $self, $id, $entry, %args ) = @_;
383              
384 16132         30696 my $info = $self->_vivify_id_info($id);
385              
386 16132         405398 $self->_entries->{$entry} = $info;
387              
388 16132 50       72784 confess "$entry" unless $entry->isa("KiokuDB::Entry");
389 16132         387738 @{$info}{keys %args, 'root'} = ( values %args, $entry->root );
  16132         36872  
390              
391 16132         48430 weaken($info->{entry} = $entry);
392              
393 16132 100 100     309559 if ( $args{in_storage} and $self->keep_entries and my $txs = $self->txn_scope ) {
      100        
394 4313         12969 $txs->push($entry);
395             }
396             }
397              
398             sub insert {
399 254     254 1 16406 my ( $self, @pairs ) = @_;
400              
401 254 50       996 croak "The arguments must be an list of pairs of IDs/Entries to objects"
402             unless @pairs % 2 == 0;
403              
404 254 50       7258 croak "no open live object scope" unless $self->current_scope;
405              
406 254         420 my @register;
407 254         648 while ( @pairs ) {
408 479         1201 my ( $id, $object ) = splice @pairs, 0, 2;
409 479         551 my $entry;
410              
411 479 100       946 if ( ref $id ) {
412 2         7 $entry = $id;
413 2         84 $id = $entry->id;
414             }
415              
416 479 50       827 confess("blah") unless $id;
417              
418 479 100       1264 croak($object, " is not a reference") unless ref($object);
419 478 50 66     4594 croak($object, " is an entry") if blessed($object) && $object->isa("KiokuDB::Entry");
420              
421 478 100       1006 if ( $entry ) {
422 2         11 $self->register_entry( $id => $entry, in_storage => 1 );
423 2         11 $self->register_object( $id => $object );
424              
425             # break cycle for passthrough objects
426 2 50 33     47 if ( ref($entry->data) and refaddr($object) == refaddr($entry->data) ) {
427 0         0 weaken($entry->{data}); # FIXME there should be a MOP way to do this
428             }
429             } else {
430 476         1280 $self->register_object( $id => $object );
431             }
432             }
433             }
434              
435             sub update_entries {
436 101     101 1 289405 my ( $self, @pairs ) = @_;
437 101         180 my @entries;
438              
439 101         307 while ( @pairs ) {
440 101         369 my ( $object, $entry ) = splice @pairs, 0, 2;
441              
442 101         2572 $self->register_entry( $entry->id => $entry, in_storage => 1 );
443              
444 101 100       305 unless ( $self->object_to_id($object) ) {
445 2         38 $self->register_object( $entry->id => $object );
446             } else {
447 99         360 $self->update_object_entry( $object, $entry );
448             }
449             }
450              
451 101         627 return;
452             }
453              
454             sub rollback_entries {
455 140     140 1 359 my ( $self, @entries ) = @_;
456              
457 140         405 foreach my $entry ( reverse @entries ) {
458 476         10383 my $info = $self->_id_info($entry->id);
459              
460 476 100       10547 if ( my $prev = $entry->prev ) {
461 51         336 weaken($info->{entry} = $prev);
462             } else {
463 425         972 delete $info->{entry};
464             }
465             }
466             }
467              
468             sub remove {
469 171     171 1 506 my ( $self, @stuff ) = @_;
470              
471 171         4911 my ( $i, $o, $e, $oe ) = ( $self->_ids, $self->_objects, $self->_entries, $self->_object_entries );
472              
473 171         716 while ( @stuff ) {
474 228         450 my $thing = shift @stuff;
475              
476 228 100       646 if ( ref $thing ) {
477             # FIXME make this a bit less zealous?
478 126         145 my $info;
479 126 100       560 if ( $info = delete $o->{$thing} ) {
    50          
480 72         247 delete $info->{object};
481 72         295 delete $oe->{$thing};
482 72 100       484 push @stuff, $info->{entry} if $info->{entry};
483             } elsif ( $info = delete $e->{$thing} ) {
484 54         138 delete $info->{entry};
485 54 50       490 push @stuff, $info->{object} if ref $info->{object};
486             }
487             } else {
488 102         275 my $info = delete $i->{$thing};
489 102         283 push @stuff, grep { ref } delete @{$info}{qw(entry object)};
  204         1213  
  102         297  
490             }
491             }
492             }
493              
494             sub clear {
495 966     966 1 11369 my $self = shift;
496              
497             # don't waste too much time in DESTROY
498 966         1255 $_->{guard}->dismiss for values %{ $self->_ids };
  966         26339  
499              
500 966         1419 %{ $self->_ids } = ();
  966         23195  
501 966         1340 %{ $self->_objects } = ();
  966         24409  
502 966         1513 %{ $self->_object_entries } = ();
  966         25465  
503 966         1651 %{ $self->_entries } = ();
  966         23993  
504              
505 966         29965 $self->_clear_current_scope;
506 966         23929 $self->_known_scopes->clear;
507             }
508              
509             __PACKAGE__->meta->make_immutable;
510              
511             __PACKAGE__
512              
513             __END__
514              
515             =pod
516              
517             =head1 NAME
518              
519             KiokuDB::LiveObjects - Live object set tracking
520              
521             =head1 SYNOPSIS
522              
523             $live_objects->insert( $entry => $object );
524              
525             $live_objects->insert( $id => $object );
526              
527             my $id = $live_objects->object_to_id( $object );
528              
529             my $obj = $live_objects->id_to_object( $id );
530              
531             my $scope = $live_objects->new_scope;
532              
533             =head1 DESCRIPTION
534              
535             This object keeps track of the set of live objects, their associated IDs, and
536             the storage entries.
537              
538             =head1 ATTRIBUTES
539              
540             =over 4
541              
542             =item clear_leaks
543              
544             Boolean. Defaults to false.
545              
546             If true, when the last known scope is removed but some objects are still live
547             they will be removed from the live object set.
548              
549             Note that this does B<NOT> prevent leaks (memory cannot be reclaimed), it
550             merely prevents stale objects from staying loaded.
551              
552             =item leak_tracker
553              
554             This is a coderef or object.
555              
556             If any objects ar eleaked (see C<clear_leaks>) then the this can be used to
557             report them, or to break the circular structure.
558              
559             When an object is provided the C<leaked_objects> method is called. The coderef
560             is simply invoked with the objects as arguments.
561              
562             Triggered after C<clear_leaks> causes C<clear> to be called.
563              
564             For example, to break cycles you can use L<Data::Structure::Util>'s
565             C<circular_off> function:
566              
567             use Data::Structure::Util qw(circular_off);
568              
569             $dir->live_objects->leak_tracker(sub {
570             my @leaked_objects = @_;
571             circular_off($_) for @leaked_objects;
572             });
573              
574             =item keep_entries
575              
576             B<EXPERIMENTAL>
577              
578             When true (the default), L<KiokuDB::Entries> loaded from the backend or created
579             by the collapser are kept around.
580              
581             This results in a considerable memory overhead, so it's no longer required.
582              
583             =back
584              
585             =head1 METHODS
586              
587             =over 4
588              
589             =item insert
590              
591             Takes pairs, id or entry as the key, and object as the value, registering the
592             objects.
593              
594             =item objects_to_ids
595              
596             =item object_to_id
597              
598             Given objects, returns their IDs, or undef for objects which not registered.
599              
600             =item objects_to_entries
601              
602             =item object_to_entry
603              
604             Given objects, find the corresponding entries.
605              
606             =item ids_to_objects
607              
608             =item id_to_object
609              
610             Given IDs, find the corresponding objects.
611              
612             =item ids_to_entries
613              
614             Given IDs, find the corresponding entries.
615              
616             =item update_entries
617              
618             Given entries, replaces the live entries of the corresponding objects with the
619             newly updated ones.
620              
621             The objects must already be in the live object set.
622              
623             This method is called on a successful transaction commit.
624              
625             =item new_scope
626              
627             Creates a new L<KiokuDB::LiveObjects::Scope>, with the current scope as its
628             parent.
629              
630             =item current_scope
631              
632             The current L<KiokuDB::LiveObjects::Scope> instance.
633              
634             This is the scope into which newly registered objects are pushed.
635              
636             =item new_txn
637              
638             Creates a new L<KiokuDB::LiveObjects::TXNScope>, with the current txn scope as
639             its parent.
640              
641             =item txn_scope
642              
643             The current L<KiokuDB::LiveObjects::TXNScope>.
644              
645             =item clear
646              
647             Forces a clear of the live object set.
648              
649             This removes all objects and entries, and can be useful in the case of leaks
650             (to prevent false positives on lookups).
651              
652             Note that this does not actually break the circular structures, so the leak is
653             unresolved, but the objects are no longer considered live by the L<KiokuDB> instance.
654              
655             =item live_entries
656              
657             =item live_objects
658              
659             =item live_ids
660              
661             Enumerates the live entries, objects or ids.
662              
663             =item rollback_entries
664              
665             Called by L<KiokuDB::LiveObjects::TXNScope/rollback>.
666              
667             =item remove
668              
669             Removes entries from the live object set.
670              
671             =item remove_scope $scope
672              
673             Removes a scope from the set of known scopes.
674              
675             Also calls C<detach_scope>, and calls C<KiokuDB::LiveObjects::Scope/clear> on
676             the scope itself.
677              
678             =item detach_scope $scope
679              
680             Detaches C<$scope> if it's the current scope.
681              
682             This prevents C<push> from being called on this scope object implicitly
683             anymore.
684              
685             =back
686              
687             =cut