File Coverage

lib/PAGI/Lifespan.pm
Criterion Covered Total %
statement 79 80 98.7
branch 20 26 76.9
condition 19 35 54.2
subroutine 13 14 92.8
pod 4 9 44.4
total 135 164 82.3


line stmt bran cond sub pod time code
1             package PAGI::Lifespan;
2              
3 4     4   451097 use strict;
  4         9  
  4         157  
4 4     4   24 use warnings;
  4         7  
  4         266  
5 4     4   19 use Future::AsyncAwait;
  4         5  
  4         30  
6 4     4   227 use Carp qw(croak);
  4         8  
  4         6734  
7              
8              
9             sub new {
10 11     11 1 8985 my ($class, %args) = @_;
11              
12 11         22 my $app = delete $args{app};
13              
14 11         19 my @handlers;
15             push @handlers, {
16             startup => $args{startup},
17             shutdown => $args{shutdown},
18 11 100 66     84 } if $args{startup} || $args{shutdown};
19              
20 11         75 return bless {
21             app => $app,
22             _handlers => \@handlers,
23             _state => undef,
24             }, $class;
25             }
26              
27 0     0 1 0 sub state { shift->{_state} }
28              
29             sub on_startup {
30 2     2 0 31 my ($self, $cb) = @_;
31 2         4 return $self->register(startup => $cb);
32             }
33              
34             sub on_shutdown {
35 2     2 0 11 my ($self, $cb) = @_;
36 2         3 return $self->register(shutdown => $cb);
37             }
38              
39             sub register {
40 8     8 0 18 my ($self, %args) = @_;
41 8 50 66     23 return $self unless $args{startup} || $args{shutdown};
42 8         31 push @{$self->{_handlers}}, {
43             startup => $args{startup},
44             shutdown => $args{shutdown},
45 8         11 };
46 8         17 return $self;
47             }
48              
49             sub for_scope {
50 5     5 0 13 my ($class, $scope) = @_;
51 5 50 33     174 croak "scope is required" unless $scope && ref($scope) eq 'HASH';
52 5   33     75 return $scope->{'pagi.lifespan.manager'} //= $class->new;
53             }
54              
55             sub wrap {
56 2     2 1 9388 my ($class, $app, %args) = @_;
57              
58 2         8 my $self = $class->new(app => $app, %args);
59 2         7 return $self->to_app;
60             }
61              
62             sub to_app {
63 5     5 1 15 my ($self) = @_;
64              
65 5         12 my $app = $self->{app};
66 5 50       7 croak "PAGI::Lifespan->to_app requires an app" unless $app;
67              
68 10     10   109 my $wrapper = async sub {
69 10         22 my ($scope, $receive, $send) = @_;
70              
71 10   50     27 my $type = $scope->{type} // '';
72              
73 10 100       29 if ($type eq 'lifespan') {
74 4   33     16 $scope->{'pagi.lifespan.manager'} //= $self;
75 4   100     14 $scope->{state} //= {};
76 4         8 await $app->($scope, $receive, $send);
77 4         285 return await $self->handle($scope, $receive, $send);
78             }
79              
80 6         33 my $inner_scope = { %$scope };
81 6   50     25 $inner_scope->{state} //= ($self->{_state} // {});
      66        
82 6         14 $self->{_state} = $inner_scope->{state};
83              
84 6         21 await $app->($inner_scope, $receive, $send);
85 5         20 };
86              
87 5         11 return $wrapper;
88             }
89              
90 10     10 0 25 async sub handle {
91 10         22 my ($self, $scope, $receive, $send) = @_;
92 10 50 50     59 return 0 unless $scope && ($scope->{type} // '') eq 'lifespan';
      33        
93 10 50       31 return 0 if $scope->{'pagi.lifespan.handled'};
94 10         21 $scope->{'pagi.lifespan.handled'} = 1;
95              
96 10         12 my @handlers;
97 10 100       29 if (my $extra = $scope->{'pagi.lifespan.handlers'}) {
98 3         9 push @handlers, @$extra;
99             }
100 10   50     18 push @handlers, @{$self->{_handlers} // []};
  10         31  
101              
102 10   100     31 my $state = $scope->{state} //= {};
103 10         20 $self->{_state} = $state;
104              
105 10         18 while (1) {
106 18         284 my $msg = await $receive->();
107 18   50     1522 my $type = $msg->{type} // '';
108              
109 18 100       62 if ($type eq 'lifespan.startup') {
    50          
110 10         22 for my $handler (@handlers) {
111 17 100       41 next unless $handler->{startup};
112 15         23 eval { await $handler->{startup}->($state) };
  15         39  
113 15 100       696 if ($@) {
114 2         11 await $send->({
115             type => 'lifespan.startup.failed',
116             message => "$@",
117             });
118 2         77 return;
119             }
120             }
121 8         31 await $send->({ type => 'lifespan.startup.complete' });
122             }
123             elsif ($type eq 'lifespan.shutdown') {
124 8         19 for my $handler (reverse @handlers) {
125 15 100       296 next unless $handler->{shutdown};
126 11         19 eval { await $handler->{shutdown}->($state) };
  11         28  
127             }
128 8         280 await $send->({ type => 'lifespan.shutdown.complete' });
129 8         326 return 1;
130             }
131             }
132             }
133              
134             1;
135              
136             __END__