File Coverage

blib/lib/KiokuDB/Linker.pm
Criterion Covered Total %
statement 168 183 91.8
branch 60 72 83.3
condition 8 12 66.6
subroutine 26 26 100.0
pod 0 18 0.0
total 262 311 84.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package KiokuDB::Linker;
4 18     18   6543 use Moose;
  18         31  
  18         125  
5              
6             # perf improvements:
7             # use a queue of required objects, queue up references, and bulk fetch
8             # bulk fetch arrays
9             # could support a Backend::Queueing which allows queuing of IDs for fetching,
10             # to help clump or start a request and only read it when it's actually needed
11              
12              
13 18     18   80547 use Carp qw(croak);
  18         55  
  18         1149  
14 18     18   88 use Scalar::Util qw(reftype weaken);
  18         27  
  18         859  
15 18     18   91 use Symbol qw(gensym);
  18         26  
  18         1008  
16 18     18   90 use Tie::ToObject;
  18         27  
  18         1379  
17              
18 18     18   6813 use KiokuDB::Error::MissingObjects;
  18         45  
  18         742  
19              
20 18     18   137 use namespace::clean -except => 'meta';
  18         29  
  18         156  
21              
22             has live_objects => (
23             isa => "KiokuDB::LiveObjects",
24             is => "ro",
25             required => 1,
26             handles => [qw(id_to_object ids_to_objects object_to_id objects_to_ids id_to_entry ids_to_entries)],
27             );
28              
29             has backend => (
30             does => "KiokuDB::Backend",
31             is => "ro",
32             required => 1,
33             );
34              
35             has typemap_resolver => (
36             isa => "KiokuDB::TypeMap::Resolver",
37             is => "ro",
38             handles => [qw(expand_method refresh_method)],
39             required => 1,
40             );
41              
42             has queue => (
43             isa => "Bool",
44             is => "ro",
45             default => 1,
46             );
47              
48             has _queue => (
49             isa => "ArrayRef",
50             is => "ro",
51             default => sub { [] },
52             );
53              
54             has _deferred => (
55             isa => "ArrayRef",
56             is => "ro",
57             default => sub { [] },
58             );
59              
60             sub register_object {
61 5673     5673 0 8658 my ( $self, $entry, $object, @args ) = @_;
62              
63 5673 100       136160 if ( my $id = $entry->id ) {
64 5571         132895 my $l = $self->live_objects;
65              
66 5571         16891 $l->register_entry( $id => $entry );
67 5571         16585 $l->register_object( $id => $object, @args );
68              
69 18     18   7249 use Scalar::Util qw(refaddr);
  18         30  
  18         28633  
70             # break cycle for passthrough objects
71 5571 100 100     127170 if ( ref($entry->data) and refaddr($object) == refaddr($entry->data) ) {
72 1         5 weaken($entry->{data}); # FIXME there should be a MOP way to do this
73             }
74             }
75             }
76              
77             sub expand_objects {
78 1167     1167 0 2574 my ( $self, @entries ) = @_;
79              
80 1167         29481 my $l = $self->live_objects;
81              
82 1167         1602 my @objects;
83              
84 1167         2278 foreach my $entry ( @entries ) {
85             # if the object was referred to in some other entry in @entries, it may
86             # have already been loaded.
87 2932 100       65179 if ( defined ( my $obj = $l->id_to_object($entry->id) ) ) {
88 213         581 push @objects, $obj;
89             } else {
90 2719         8500 $self->inflate_data( $entry, \($objects[@objects]) );
91             }
92             }
93              
94 1167         3690 $self->load_queue;
95              
96 1167         19337 return @objects;
97             }
98              
99             sub expand_object {
100 2903     2903 0 4749 my ( $self, $entry ) = @_;
101              
102 2903         9303 $self->inflate_data( $entry, \(my $obj) );
103              
104 2899         7103 $self->load_queue;
105              
106 2899         33751 return $obj;
107             }
108              
109             sub queue_ref {
110 6339     6339 0 7385 my ( $self, $ref, $into ) = @_;
111              
112 6339 100       156500 if ( $self->queue ) {
113              
114             #my $b = $self->backend;
115              
116             #if ( $b->can("prefetch") ) {
117             # $b->prefetch($ref->id);
118             #}
119              
120 3731         3359 push @{ $self->_queue }, [ $ref, $into ];
  3731         81727  
121             } else {
122 2608 50       5050 if ( ref $ref ) {
123 2608         60012 $$into = $self->get_or_load_object($ref->id);
124 2608 100       66687 weaken($$into) if $ref->is_weak;
125             } else {
126 0         0 $$into = $self->get_or_load_object($ref);
127             }
128             }
129             }
130              
131             sub queue_finalizer {
132 5368     5368 0 8704 my ( $self, @hooks ) = @_;
133              
134 5368 100       143919 if ( $self->queue ) {
135 2984         3455 push @{ $self->_deferred }, @hooks;
  2984         70127  
136             } else {
137 2384         5358 foreach my $hook ( @hooks ) {
138 2384         6095 $self->$hook();
139             }
140             }
141             }
142              
143             sub load_queue {
144 4370     4370 0 5985 my $self = shift;
145              
146 4370 100       100547 return unless $self->queue;
147              
148 2138         48853 my $queue = $self->_queue;
149 2138         47819 my $deferred = $self->_deferred;
150              
151 2138         5160 my @queue = @$queue;
152 2138         3641 my @deferred = @$deferred;
153              
154 2138         2922 @$queue = ();
155 2138         2985 @$deferred = ();
156              
157 2138 100       5235 if ( @queue ) {
158 906         1241 my @ids;
159              
160 906         1617 foreach my $entry ( @queue ) {
161 3731         3819 my $ref = $entry->[0];
162 3731 50       86807 push @ids, ref($ref) ? $ref->id : $ref;
163             }
164              
165 906         3095 my @objects = $self->get_or_load_objects(@ids);
166              
167 906         2071 foreach my $item ( @queue ) {
168 3731         5252 my ( $data, $into ) = @$item;
169 3731         5033 my $obj = shift @objects;
170              
171 3731         4387 $$into = $obj;
172              
173 3731 100 66     93654 weaken $$into if ref $data and $data->is_weak;
174             }
175             }
176              
177 2138 100       5586 if ( @deferred ) {
178 1662         2925 foreach my $item ( @deferred ) {
179 2984         15085 $self->$item;
180             }
181             }
182             }
183              
184             sub inflate_data {
185 26856     26856 0 30876 my ( $self, $data, $into, $entry ) = @_;
186              
187             # Kinda ugly... inflates $data into the scalar ref in $into
188             # but this allows us to handle weakening properly.
189             # god I hate perl's reftypes, why couldn't they be a little more consistent
190              
191 26856 100       91659 unless ( ref $data ) {
    100          
    100          
    100          
    100          
    100          
    100          
192 273         686 $$into = $data;
193             } elsif ( ref $data eq 'KiokuDB::Reference' ) {
194 6339         11954 $self->queue_ref( $data, $into );
195             } elsif ( ref $data eq 'KiokuDB::Entry' ) {
196 5727 100       139547 if ( my $class = $data->class ) {
197 5464         17038 my $expand_method = $self->expand_method($class);
198 5464         20178 $$into = $self->$expand_method($data);
199             } else {
200 263         491 my $obj;
201              
202 263         6223 $self->inflate_data($data->data, \$obj, $data);
203              
204 263         973 $self->load_queue; # force vivification of $obj
205              
206 263 100       6820 if ( my $tie = $data->tied ) {
207 35 50       126 if ( $tie eq 'H' ) {
    0          
    0          
    0          
208 35         388 tie my %h, "Tie::ToObject" => $obj;
209 35         687 $obj = \%h;
210             } elsif ( $tie eq 'A' ) {
211 0         0 tie my @a, "Tie::ToObject" => $obj;
212 0         0 $obj = \@a;
213             } elsif ( $tie eq 'G' ) {
214 0         0 my $glob = gensym();
215 0         0 tie *$glob, "Tie::ToObject" => $obj,
216             $obj = $glob;
217             } elsif ( $tie eq 'S' ) {
218 0         0 my $scalar;
219 0         0 tie $scalar, "Tie::ToObject" => $obj;
220 0         0 $obj = \$scalar;
221             } else {
222 0         0 die "Don't know how to tie $tie";
223             }
224             }
225              
226 263         542 $$into = $obj;
227             }
228              
229 5723         140433 $data->object($$into);
230             } elsif ( ref($data) eq 'HASH' ) {
231 294         404 my %targ;
232 294 100       1033 $self->register_object( $entry => \%targ ) if $entry;
233 294         1060 foreach my $key ( keys %$data ) {
234 297         1124 $self->inflate_data( $data->{$key}, \$targ{$key} );
235             }
236 294         835 $$into = \%targ;
237             } elsif ( ref($data) eq 'ARRAY' ) {
238 14103         12874 my @targ;
239 14103 100       22384 $self->register_object( $entry => \@targ ) if $entry;
240 14103         20845 for (@$data ) {
241 5319         7902 push @targ, undef;
242 5319         10973 $self->inflate_data( $_, \$targ[-1] );
243             }
244 14103         30382 $$into = \@targ;
245             } elsif ( ref($data) eq 'SCALAR' ) {
246 37         84 my $targ = $$data;
247 37 100       177 $self->register_object( $entry => \$targ ) if $entry;
248 37         106 $$into = \$targ;
249             } elsif ( ref($data) eq 'REF' ) {
250 6         8 my $targ;
251 6 50       11 $self->register_object( $entry => \$targ ) if $entry;
252 6         18 $self->inflate_data( $$data, \$targ );
253 6         9 $$into = \$targ;
254             } else {
255 77 50       306 if ( blessed($data) ) {
256             # this branch is for passthrough intrinsic values
257 77 100       183 $self->register_object( $entry => $data ) if $entry;
258 77         250 $$into = $data;
259             } else {
260 0         0 die "unsupported reftype: " . ref $data;
261             }
262             }
263             }
264              
265             sub get_or_load_objects {
266 2444     2444 0 5362 my ( $self, @ids ) = @_;
267              
268 2444 100       11261 return $self->get_or_load_object($ids[0]) if @ids == 1;
269              
270 620         1200 my %objects;
271 620         16315 @objects{@ids} = $self->live_objects->ids_to_objects(@ids);
272              
273 620         1919 my @missing = grep { not defined $objects{$_} } keys %objects; # @ids may contain duplicates
  2639         5041  
274              
275 620         2033 @objects{@missing} = $self->load_objects(@missing);
276              
277 620         6016 return @objects{@ids};
278             }
279              
280             sub load_objects {
281 620     620 0 1238 my ( $self, @ids ) = @_;
282              
283 620         1655 return $self->expand_objects( $self->get_or_load_entries(@ids) );
284             }
285              
286             sub get_or_load_entries {
287 620     620 0 1234 my ( $self, @ids ) = @_;
288              
289 620         647 my %entries;
290 620         2575 @entries{@ids} = $self->ids_to_entries(@ids);
291              
292 620 100       1426 if ( my @load = grep { !$entries{$_} } @ids ) {
  1522         3564  
293 524         1978 @entries{@load} = $self->load_entries(@load);
294             }
295              
296 620         3020 return @entries{@ids};
297             }
298              
299             sub load_entries {
300 524     524 0 1080 my ( $self, @ids ) = @_;
301              
302 524         13650 my @entries = $self->backend->get(@ids);
303              
304 524 50 33     2339 if ( @entries != @ids or grep { !$_ } @entries ) {
  1522         3800  
305 0         0 my %entries;
306 0         0 @entries{@ids} = @entries;
307 0         0 my @missing = grep { !$entries{$_} } @ids;
  0         0  
308              
309 0         0 KiokuDB::Error::MissingObjects->throw( ids => \@missing );
310             }
311              
312 524         15503 my $l = $self->live_objects;
313 524         1163 foreach my $entry ( @entries ) {
314 1522         33684 $l->register_entry( $entry->id, $entry, in_storage => 1 );
315             }
316              
317 524         2271 return @entries;
318             }
319              
320             sub register_and_expand_entries {
321 547     547 0 1240 my ( $self, @entries ) = @_;
322              
323 547         15273 my $l = $self->live_objects;
324 547         1165 foreach my $entry ( @entries ) {
325 1410         31852 $l->register_entry( $entry->id, $entry, in_storage => 1 );
326             }
327              
328 547         2200 $self->expand_objects(@entries);
329             }
330              
331             sub get_or_load_object {
332 4437     4437 0 6793 my ( $self, $id ) = @_;
333              
334 4437 100       114008 if ( defined( my $obj = $self->live_objects->id_to_object($id) ) ) {
335 1488         2751 return $obj;
336             } else {
337 2949         8999 return $self->load_object($id);
338             }
339             }
340              
341             sub refresh_objects {
342 33     33 0 69 my ( $self, @objects ) = @_;
343              
344 33         202 $self->refresh_object($_) for @objects;
345             }
346              
347             sub refresh_object {
348 35     35 0 1102 my ( $self, $object ) = @_;
349              
350 35         178 my $id = $self->object_to_id($object);
351              
352 35         128 my $entry = $self->load_entry($id);
353              
354 35         909 my $refresh = $self->refresh_method( $entry->class );
355              
356 35         183 $self->$refresh($object, $entry);
357 35         108 $self->load_queue;
358              
359 35         383 return $object;
360             }
361              
362             sub get_or_load_entry {
363 2949     2949 0 3661 my ( $self, $id ) = @_;
364              
365 2949   66     10854 return $self->id_to_entry($id) || $self->load_entry($id);
366             }
367              
368             sub load_entry {
369 2959     2959 0 4359 my ( $self, $id ) = @_;
370              
371 2959 100       72065 my $entry = ( $self->backend->get($id) )[0]
372             or KiokuDB::Error::MissingObjects->throw( ids => [ $id ] );
373              
374 2890         83510 $self->live_objects->register_entry( $id => $entry, in_storage => 1 );
375              
376 2890         10569 return $entry;
377             }
378              
379             sub load_object {
380 2949     2949 0 5705 my ( $self, $id ) = @_;
381              
382 2949         6836 my $entry = $self->get_or_load_entry($id);
383              
384 2880         8604 return $self->expand_object($entry);
385             }
386              
387             __PACKAGE__->meta->make_immutable;
388              
389             __PACKAGE__
390              
391             __END__
392              
393             =pod
394              
395             =head1 NAME
396              
397             KiokuDB::Linker - Relinks live objects from storage entries
398              
399             =head1 SYNOPSIS
400              
401             # mostly internal
402              
403             =head1 DESCRIPTION
404              
405             The linker reconnects entry data, recreating the connected object graph in
406             memory.
407              
408             The linkage process starts with a an ID (or several IDs) to be loaded passed to
409             the C<get_or_load_objects> method.
410              
411             This ID will first be searched for in the live object set
412             (L<KiokuDB::LiveObjects>). If the object is already live, then it will be
413             returned as is.
414              
415             If the object is not live, then the corresponding entry is fetched from the
416             backend, and expanded into an actual instance.
417              
418             Expansion consults the L<KiokuDB::TypeMap> using L<KiokuDB::TypeMap::Resolver>,
419             to find the correct typemap entry (see
420             L<KiokuDB::Collapser/"COLLAPSING STRATEGIES"> and L<KiokuDB::TypeMap>), and
421             that is used for the actual expansion.
422              
423             Most of the grunt work is delegated by the entries back to the linker using the
424             C<inflate_data> method, which handles circular structures, retrying of tied
425             structures, etc.
426              
427             Inflated objects are registered with L<KiokuDB::LiveObjects>, and get inserted
428             into the current live object scope (L<KiokuDB::LiveObjects::Scope>). The
429             scope's job is to maintain a reference count of at least 1 for any loaded
430             object, until it is destroyed itself. This ensures that weak references are not
431             destroyed prematurely, but allows their use in order to avoid memory leaks.
432              
433             =cut
434