File Coverage

blib/lib/Gerrit/Client/ForEach.pm
Criterion Covered Total %
statement 243 323 75.2
branch 82 138 59.4
condition 18 37 48.6
subroutine 41 50 82.0
pod n/a
total 384 548 70.0


line stmt bran cond sub pod time code
1             #############################################################################
2             ##
3             ## Copyright (C) 2012-2014 Rohan McGovern
4             ##
5             ## This library is free software; you can redistribute it and/or
6             ## modify it under the terms of the GNU Lesser General Public
7             ## License as published by the Free Software Foundation; either
8             ## version 2.1 of the License, or (at your option) any later version.
9             ##
10             ## This library is distributed in the hope that it will be useful,
11             ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12             ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             ## Lesser General Public License for more details.
14             ##
15             ## You should have received a copy of the GNU Lesser General Public
16             ## License along with this library; if not, write to the Free Software
17             ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18             ##
19             ##
20             #############################################################################
21              
22             package Gerrit::Client::ForEach;
23 1     1   7500 use strict;
  1         2  
  1         27  
24 1     1   5 use warnings;
  1         1  
  1         21  
25              
26 1     1   5 use AnyEvent;
  1         3  
  1         18  
27 1     1   4 use AnyEvent::Util;
  1         1  
  1         79  
28 1     1   5 use Capture::Tiny qw(capture_merged);
  1         3  
  1         44  
29 1     1   6 use Data::Alias;
  1         2  
  1         43  
30 1     1   4 use Data::Dumper;
  1         2  
  1         47  
31 1     1   6 use English qw(-no_match_vars);
  1         1  
  1         9  
32 1     1   415 use File::chdir;
  1         3  
  1         77  
33 1     1   4 use Gerrit::Client;
  1         2  
  1         36  
34 1     1   5 use Scalar::Util qw(weaken);
  1         2  
  1         3687  
35              
36             # counter of how many connections we have per server
37             my %CONNECTION_COUNTER;
38              
39             # counter of how many worker processes we have
40             my $WORKER_COUNTER;
41              
42             # 1 when fetching into a gitdir
43             my %GITDIR_FETCHING;
44              
45             sub _giturl_counter {
46 109     109   2605 my ($giturl) = @_;
47 109         3105 my $gerriturl = Gerrit::Client::_gerrit_parse_url($giturl)->{gerrit};
48 109         34452 return \$CONNECTION_COUNTER{$gerriturl};
49             }
50              
51             sub _debug_print {
52 138     138   1171 return Gerrit::Client::_debug_print(@_);
53             }
54              
55             sub _handle_for_each_event {
56 21     21   89 my ( $self, $event ) = @_;
57              
58 21 50       97 return unless $event->{type} eq 'patchset-created';
59              
60 21 50       168 if (my $wanted = $self->{args}{wanted}) {
61 21 100       154 if (!$wanted->( $event->{change}, $event->{patchSet})) {
62 3         33 return;
63             }
64             }
65              
66 18         354 $self->_enqueue_event($event);
67              
68 18         2643 return $self->_dequeue_soon();
69             }
70              
71             # Git command generators; these are methods so that they can be
72             # overridden for testing
73             sub _git_bare_clone_cmd
74             {
75 0     0   0 my (undef, $giturl, $gitdir) = @_;
76 0         0 return (@Gerrit::Client::GIT, 'clone', '--bare', $giturl, $gitdir);
77             }
78              
79             sub _git_clone_cmd
80             {
81 0     0   0 my (undef, $giturl, $gitdir) = @_;
82 0         0 return (@Gerrit::Client::GIT, 'clone', $giturl, $gitdir);
83             }
84              
85             sub _git_fetch_cmd
86             {
87 0     0   0 my (undef, $giturl, $gitdir, @refs) = @_;
88 0         0 return (@Gerrit::Client::GIT, '--git-dir', $gitdir, 'fetch', '-v', $giturl,
89 0         0 (map { "+$_:$_" } @refs));
90             }
91              
92             sub _git_reset_cmd
93             {
94 0     0   0 my (undef, $ref, $mode) = @_;
95 0   0     0 $mode ||= '--hard';
96 0         0 return (@Gerrit::Client::GIT, 'reset', $mode, $ref);
97             }
98              
99             # Returns 1 iff $gitdir contains the given $ref
100             sub _have_ref {
101 80     80   198 my ($gitdir, $ref) = @_;
102 80         387 my $status;
103             capture_merged {
104 80     80   1148419 $status = system(
105             @Gerrit::Client::GIT,
106             '--git-dir', $gitdir,
107             'rev-parse', $ref
108             );
109 80         6888 };
110 80         117634 return ($status == 0);
111             }
112              
113             # Returns 1 iff it appears the revision for $event has already been handled
114             sub _is_commit_reviewed {
115 0     0   0 my ( $self, $event ) = @_;
116 0         0 my $gitdir = $self->_gitdir($event);
117 0 0       0 return unless ( -d $gitdir );
118              
119 0         0 my $revision = $event->{patchSet}{revision};
120 0         0 my $status;
121             my $score = capture_merged {
122 0     0   0 $status = system(
123             @Gerrit::Client::GIT, '--no-pager',
124             '--git-dir', $gitdir,
125             'notes', '--ref',
126             'Gerrit-Client-reviews', 'show',
127             $revision
128             );
129 0         0 };
130 0         0 _debug_print "git notes for $revision: status: $status, notes: $score\n";
131 0         0 return (0 == $status);
132             }
133              
134             # Flag a commit as reviewed in persistent storage; it won't be
135             # reviewed again.
136             sub _mark_commit_reviewed {
137 0     0   0 my ( $self, $event ) = @_;
138 0         0 my $gitdir = $self->_gitdir($event);
139              
140 0         0 my $revision = $event->{patchSet}{revision};
141 0         0 my $status;
142             my $output = capture_merged {
143 0     0   0 $status =
144             system( @Gerrit::Client::GIT, '--git-dir', $gitdir, 'notes', '--ref',
145             'Gerrit-Client-reviews', 'append', '-m', '1', $revision, );
146 0         0 };
147 0 0       0 if ( $status != 0 ) {
148 0         0 $self->{args}{on_error}
149             ->( "problem writing git note for $revision\n$output\n" );
150             }
151             else {
152 0         0 _debug_print "marked $revision as reviewed\n";
153             }
154 0         0 return;
155             }
156              
157             sub _enqueue_event {
158 18     18   966 my ( $self, $event ) = @_;
159              
160 18 100       126 return if $self->_is_commit_reviewed( $event );
161              
162 12         177 push @{ $self->{queue} }, $event;
  12         70  
163              
164 12         29 return;
165             }
166              
167             sub _giturl {
168 160     160   393 my ( $self, $event ) = @_;
169 160         651 my $project = $event->{change}{project};
170 160         935 return "$self->{args}{ssh_url}/$project";
171             }
172              
173             sub _gitdir {
174 198     198   343 my ( $self, $event ) = @_;
175 198         838 my $project = $event->{change}{project};
176 198         1526 return "$self->{args}{workdir}/$project/git";
177             }
178              
179             sub _ensure_git_cloned {
180 74     74   230 my ( $self, $event, $out ) = @_;
181              
182 74         576 my $ref = $event->{patchSet}{ref};
183 74         635 my $project = $event->{change}{project};
184              
185 74         443 my $gitdir = $self->_gitdir($event);
186 74         307 my $giturl = $self->_giturl($event);
187              
188             # ensure only one event owns the cloning process for a given dir
189 74 100 100     1426 if ($self->{git_cloning}{$gitdir} && !$event->{_git_cloning}) {
190 33 50       330 if ($self->{git_cloned}{$gitdir}) {
191 33         378 return 1;
192             }
193 0         0 push @{$out}, $event;
  0         0  
194 0         0 return 0;
195             }
196              
197 41         173 $self->{git_cloning}{$gitdir} = 1;
198 41         118 $event->{_git_cloning} = 1;
199              
200             my $cloned = $self->_ensure_cmd(
201             event => $event,
202             queue => $out,
203             name => 'git clone',
204             cmd => [ $self->_git_bare_clone_cmd( $giturl, $gitdir ) ],
205 6     6   600 onlyif => sub { !-d $gitdir },
206 41         578 counter => [ _giturl_counter($giturl), $Gerrit::Client::MAX_CONNECTIONS ],
207             );
208 41 100       827 return unless $cloned;
209              
210 35 100       217 if (!$self->{git_cloned}{$gitdir}) {
211 6         33 $self->{git_cloned}{$gitdir} = 1;
212              
213             # make sure to wake up any other event who was waiting on the clone
214 6         56 $self->_dequeue_soon();
215             }
216              
217 35 50       2253 if ( !-d $gitdir ) {
218 0         0 $self->{args}{on_error}
219             ->( "failed to clone $giturl to $gitdir\n" );
220 0         0 return;
221             }
222              
223 35         128 return 1;
224             }
225              
226             sub _ensure_git_fetched {
227 68     68   177 my ( $self, $event, $out, $in ) = @_;
228              
229 68         183 my $gitdir = $self->_gitdir($event);
230 68         224 my $giturl = $self->_giturl($event);
231 68         189 my $ref = $event->{patchSet}{ref};
232              
233 68 50 33     569 if ($event->{_have_ref} || _have_ref($gitdir, $ref)) {
234 0   0     0 $event->{_have_ref} ||= 1;
235 0         0 return 1;
236             }
237              
238             # If we're running a 'git fetch', we should try to find
239             # _all_ needed refs for the given giturl and fetch them at once
240 18 50       289 my @refs = map {
241 68         778 ($self->_giturl($_) eq $giturl) ? ($_->{patchSet}{ref}) : ()
242 68         559 } @{$in};
243              
244             return $self->_ensure_cmd(
245             event => $event,
246             queue => $out,
247             name => 'git fetch',
248             counter => [ _giturl_counter($giturl), $Gerrit::Client::MAX_CONNECTIONS ],
249             'lock' => \$GITDIR_FETCHING{$gitdir},
250 12     12   50 onlyif => sub { !_have_ref( $gitdir, $ref ) },
251 68         841 cmd =>
252             [ $self->_git_fetch_cmd( $giturl, $gitdir, $ref, @refs ) ],
253             );
254             }
255              
256             sub _ensure_git_workdir_uptodate {
257 56     56   263 my ( $self, $event, $out ) = @_;
258              
259 56         574 my $project = $event->{change}{project};
260 56         512 my $ref = $event->{patchSet}{ref};
261 56         448 my $gitdir = $self->_gitdir($event);
262              
263 56         497 alias my $workdir = $event->{_workdir};
264              
265             # avoid creating temporary directory etc if we can't run processes yet
266 56 50 100     716 if (!$workdir && ($WORKER_COUNTER||0) >= $Gerrit::Client::MAX_FORKS) {
      66        
267 0         0 push @{$out}, $event;
  0         0  
268 0         0 return;
269             }
270              
271             $workdir ||=
272 56   66     1382 File::Temp->newdir("$self->{args}{workdir}/$project/work.XXXXXX");
273              
274 56         8606 my $bare = !$self->{args}{git_work_tree};
275              
276             return
277             unless $self->_ensure_cmd(
278             event => $event,
279             queue => $out,
280             name => 'git clone for workdir',
281             cmd => [ $bare
282             ? $self->_git_bare_clone_cmd( $gitdir, $workdir )
283             : $self->_git_clone_cmd( $gitdir, $workdir ) ],
284 12 50   12   80 onlyif => sub { !-d( $bare ? "$workdir/objects" : "$workdir/.git") },
285 56 50       925 counter => [ \$WORKER_COUNTER, $Gerrit::Client::MAX_FORKS ],
    100          
286             );
287              
288             return
289 44 50       626 unless $self->_ensure_cmd(
    100          
290             event => $event,
291             queue => $out,
292             name => 'git fetch for workdir',
293             cmd => [ $self->_git_fetch_cmd( 'origin', $bare ? $workdir : "$workdir/.git", $ref ) ],
294             wd => $workdir,
295             counter => [ \$WORKER_COUNTER, $Gerrit::Client::MAX_FORKS ],
296             );
297              
298 32 50       583 return $self->_ensure_cmd(
299             event => $event,
300             queue => $out,
301             name => 'git reset for workdir',
302             cmd => [ $self->_git_reset_cmd( $ref, $bare ? '--soft' : '--hard' ) ],
303             wd => $workdir,
304             counter => [ \$WORKER_COUNTER, $Gerrit::Client::MAX_FORKS ],
305             );
306             }
307              
308             sub _ensure_cmd {
309 250     250   17253 my ( $self, %args ) = @_;
310              
311 250         564 my $event = $args{event};
312 250         459 my $name = $args{name};
313              
314             # capture output by default so that we can include it in error messages
315 250 100       1026 if ( !exists( $args{saveoutput} ) ) {
316 241         563 $args{saveoutput} = 1;
317             }
318              
319 250         999 my $donekey = "_cmd_${name}_done";
320 250         495 my $cvkey = "_cmd_${name}_cv";
321 250         696 my $statuskey = "_cmd_${name}_status";
322 250         752 my $outputkey = "_cmd_${name}_output";
323              
324 250 100       1802 return 1 if ( $event->{$donekey} );
325              
326 60   100 28   464 my $onlyif = $args{onlyif} || sub { 1 };
  28         102  
327 60         190 my $queue = $args{queue};
328              
329 60         97 my $weakself = $self;
330 60         6480 weaken($weakself);
331              
332 60         498 alias my $cmdcv = $event->{$cvkey};
333 60         111 my $cmd = $args{cmd};
334 60         245 my $cmdstr;
335             {
336 60         87 local $LIST_SEPARATOR = '] [';
  60         155  
337 60         113 $cmdstr = "[@{$cmd}]";
  60         767  
338             }
339              
340 60 100       396 if ( !$cmdcv ) {
341              
342             # not done and not started; only needs doing if 'onlyif' returns false
343 58 50       131 if ( !$onlyif->() ) {
344 0         0 $event->{$donekey} = 1;
345 0         0 return 1;
346             }
347              
348             # Don't run the command if it counts as a connection and we'd have
349             # too many
350 58 50       645 my ($counter, $count_max) = @{ $args{counter} || [] };
  58         490  
351 58         292 my $uncounter;
352 58 50       150 if ($counter) {
353 58 50 100     755 if ( ($$counter||0) >= $count_max ) {
354 0         0 _debug_print(
355             "$cmdstr: delaying execution, would surpass limit\n");
356 0         0 push @{$queue}, $event;
  0         0  
357 0         0 return;
358             }
359 58         275 $$counter++;
360 58     58   1026 $uncounter = guard { $$counter-- };
  58         714  
361             }
362              
363 58         169 my $lock = $args{lock};
364 58         141 my $unlock;
365 58 100       170 if ($lock) {
366 12 50       67 if ($$lock) {
367 0         0 _debug_print(
368             "$cmdstr: delaying execution, lock held elsewhere\n");
369 0         0 push @{$queue}, $event;
  0         0  
370 0         0 return;
371             }
372 12         30 $$lock++;
373 12     12   92 $unlock = guard { $$lock-- };
  12         137  
374             }
375              
376 58     134   392 my $printoutput = sub { _debug_print( "$cmdstr: ", @_ ) };
  134         714324  
377 58         129 my $handleoutput = $printoutput;
378              
379 58 100       215 if ( $args{saveoutput} ) {
380             $handleoutput = sub {
381 126     126   479605 $printoutput->(@_);
382 126 100       7943 $event->{$outputkey} .= $_[0] if $_[0];
383 54         403 };
384             }
385              
386 58         659 my %run_cmd_args = (
387             '>' => $handleoutput,
388             '2>' => $handleoutput,
389             );
390              
391 58 100       244 if ( $args{wd} ) {
392             $run_cmd_args{on_prepare} = sub {
393 0 0   0   0 chdir( $args{wd} ) || warn __PACKAGE__ . ": chdir $args{wd}: $!";
394 28         701 };
395             }
396              
397 58         1107 $cmdcv = AnyEvent::Util::run_cmd( $cmd, %run_cmd_args, );
398             $cmdcv->cb(
399             sub {
400 58     58   6145 my ($cv) = @_;
401 58         127 undef $uncounter;
402 58         810 undef $unlock;
403 58 50       479 return unless $weakself;
404              
405 58         566 my $status = $cv->recv();
406 58 50 33     955 if ( $status && !$args{allownonzero} ) {
407 0 0       0 $self->{args}{on_error}->( "$name exited with status $status\n"
408             . ( $event->{$outputkey} ? $event->{$outputkey} : q{} ) );
409             }
410             else {
411 58         503 $event->{$donekey} = 1;
412             }
413 58         713 $event->{$statuskey} = $status;
414 58         452 $weakself->_dequeue_soon();
415             }
416 58         247054 );
417 58         1621 push @{$queue}, $event;
  58         664  
418 58         8583 return;
419             }
420              
421 2 50       161 if ( !$cmdcv->ready ) {
422 2         20 push @{$queue}, $event;
  2         6  
423 2         22 return;
424             }
425              
426 0         0 $self->{args}{on_error}->("dropped event due to failed command: $cmdstr\n");
427 0         0 return;
428             }
429              
430             sub _do_cb_sub {
431 4     4   13 my ( $self, $sub, $event ) = @_;
432              
433 4         8 my $returned;
434             my $run = sub {
435 4     4   91 local $CWD = $event->{_workdir};
436 4         431 $returned = $sub->( $event->{change}, $event->{patchSet} );
437 4         35 };
438              
439 4         11 my $output;
440 4 50       19 if ($self->{args}{review}) {
441 0         0 $output = &capture_merged( $run );
442             } else {
443 4         11 $run->();
444             }
445              
446             return {
447 4         934 returned => $returned,
448             output => $output
449             };
450             }
451              
452             sub _do_cb_forksub {
453 7     7   24 my ( $self, $sub, $event, $queue ) = @_;
454              
455 7         21 my $weakself = $self;
456 7         35 weaken($weakself);
457              
458 7 100       33 if ($event->{_forksub_result}) {
459 3         30 return $event->{_forksub_result};
460             }
461              
462 4 50       27 if ( $event->{_forked} ) {
463 0         0 push @{$queue}, $event;
  0         0  
464 0         0 return;
465             }
466              
467 4         26 $event->{_forked} = 1;
468             &fork_call(
469             \&_do_cb_sub,
470             $self,
471             $sub,
472             $event,
473             sub {
474 4 100   4   9249 return unless $weakself;
475              
476 3         21 my ($result) = $_[0];
477 3 50       22 if (!$result) {
478 0 0       0 if ($@) {
479 0         0 $result = {output => $@};
480             } else {
481 0         0 $result = {output => $!};
482             }
483             }
484 3         21 $event->{_forksub_result} = $result;
485 3         53 $weakself->_dequeue_soon();
486             }
487 4         183 );
488 4         14685 push @{$queue}, $event;
  4         101  
489 4         178 return;
490             }
491              
492             sub _do_cb_cmd {
493 9     9   27 my ( $self, $cmd, $event, $out ) = @_;
494              
495 9 50       44 return if ( $event->{_done} );
496              
497 9         56 my $project = $event->{change}{project};
498 9         25 my $ref = $event->{patchSet}{ref};
499              
500 9 100       45 if ( !$event->{_cmd} ) {
501 4 50 33     58 if ( $cmd && ref($cmd) eq 'CODE' ) {
502 0         0 $cmd = [ $cmd->( $event->{change}, $event->{patchSet} ) ];
503             }
504 4         25 $event->{_cmd} = $cmd;
505 4         23 local $LIST_SEPARATOR = '] [';
506 4         18 _debug_print "on_patchset_cmd for $project $ref: [@{$cmd}]\n";
  4         46  
507             }
508              
509 9 100       146 return unless $self->_ensure_cmd(
510             event => $event,
511             queue => $out,
512             name => 'on_patchset_cmd',
513             cmd => $event->{_cmd},
514             wd => $event->{_workdir},
515             saveoutput => $self->{args}{review},
516             allownonzero => 1,
517             counter => [ \$WORKER_COUNTER, $Gerrit::Client::MAX_FORKS ],
518             );
519              
520 3         25 my $score = 0;
521 3         10 my $output = $event->{_cmd_on_patchset_cmd_output};
522 3         8 my $status = $event->{_cmd_on_patchset_cmd_status};
523              
524 3 50       38 if ($status == -1) {
    50          
525             # exited abnormally; treat as neutral score
526             } elsif ($status & 127) {
527             # exited due to signal; treat as neutral score,
528             # append signal to output
529 0         0 $output .= "\n[exited due to signal ".($status&127)."]\n";
530             } else {
531             # exited normally; exit code is score
532 3         10 $score = $status >> 8;
533             # interpret exit code as signed
534 3 50       21 if ($score > 127) {
535 0         0 $score = $score - 256;
536             }
537             }
538              
539             return {
540 3         40 score => $score,
541             output => $output
542             };
543             }
544              
545             sub _do_callback {
546 20     20   53 my ( $self, $event, $out ) = @_;
547              
548 20         43 my $ref;
549             my $result;
550              
551 20 100       301 if ( $ref = $self->{args}{on_patchset} ) {
    100          
    50          
552 4         30 $result = $self->_do_cb_sub( $ref, $event );
553             }
554             elsif ( $ref = $self->{args}{on_patchset_fork} ) {
555 7         62 $result = $self->_do_cb_forksub( $ref, $event, $out );
556             }
557             elsif ( $ref = $self->{args}{on_patchset_cmd} ) {
558 9         67 $result = $self->_do_cb_cmd( $ref, $event, $out );
559             }
560              
561 20 100       567 return unless $result;
562              
563 10 50       40 if ($Gerrit::Client::DEBUG) {
564 0         0 _debug_print 'callback result: ' . Dumper($result);
565             }
566              
567             # Ensure we shan't review it again
568 10         104 $self->_mark_commit_reviewed($event);
569              
570 10         7105 my $review = $self->{args}{review};
571 10 50       134 return unless $review;
572              
573 0 0       0 if ( $review =~ m{\A\d+\z} ) {
574 0         0 $review = 'code_review';
575             }
576              
577 0 0       0 if ( my $cb = $self->{args}{on_review} ) {
578             return
579 0 0       0 unless $cb->(
580             $event->{change}, $event->{patchSet},
581             $result->{output}, $result->{score},
582             $result->{returned}
583             );
584             }
585              
586 0 0 0     0 if ( !$result->{output} && !$result->{score} && !$result->{returned}) {
      0        
587             # no review to be done
588 0         0 return;
589             }
590              
591 0         0 my (%review_args) = (
592             message => $result->{output},
593             project => $event->{change}{project},
594             branch => $event->{change}{branch},
595             change => $event->{change}{id},
596             );
597              
598 0         0 for my $arg (qw(ssh_url http_url http_auth_cb)) {
599 0         0 $review_args{$arg} = $self->{args}{$arg};
600             }
601              
602 0 0       0 if ($result->{returned}) {
603 0 0       0 if (ref($result->{returned}) eq 'HASH') {
604 0         0 $review_args{reviewInput} = $result->{returned};
605             } else {
606 0         0 $review_args{$review} = $result->{returned};
607             }
608             }
609              
610             Gerrit::Client::review(
611 0         0 $event->{patchSet}{revision},
612             %review_args
613             );
614             }
615              
616             sub _dequeue_soon {
617 85     85   206 my ($self) = @_;
618 85         282 my $weakself = $self;
619 85         406 weaken($weakself);
620             $self->{_dequeue_timer} ||= AE::timer( .1, 0,
621             sub {
622 57 50   57   4792996 return unless $weakself;
623 57         653 delete $weakself->{_dequeue_timer};
624 57         407 $weakself->_dequeue();
625             }
626 85   66     2552 );
627             }
628              
629             sub _dequeue {
630 57     57   216 my ($self) = @_;
631              
632 57 50       378 if ($Gerrit::Client::DEBUG) {
633 0         0 _debug_print 'queue before processing: ', Dumper( $self->{queue} );
634             }
635              
636 57         154 my $weakself = $self;
637 57         361 weaken($weakself);
638              
639 57 50       246 my @queue = @{ $self->{queue} || [] };
  57         686  
640 57         135 my @newqueue;
641 57         281 while (my $event = shift @queue) {
642 74 100       767 next unless $self->_ensure_git_cloned( $event, \@newqueue );
643 68 100       1107 next unless $self->_ensure_git_fetched( $event, \@newqueue, \@queue );
644 56 100       592 next unless $self->_ensure_git_workdir_uptodate( $event, \@newqueue );
645 20         256 $self->_do_callback( $event, \@newqueue );
646             }
647              
648 57         282 $self->{queue} = \@newqueue;
649              
650 57 50       1731 if ($Gerrit::Client::DEBUG) {
651 0         0 _debug_print 'queue after processing: ', Dumper( $self->{queue} );
652             }
653              
654 57         100982 return;
655             }
656              
657             1;