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   341741 use strict;
  4         5  
  4         130  
4 4     4   16 use warnings;
  4         5  
  4         164  
5 4     4   17 use Future::AsyncAwait;
  4         3  
  4         23  
6 4     4   196 use Carp qw(croak);
  4         9  
  4         4999  
7              
8              
9             sub new {
10 11     11 1 6767 my ($class, %args) = @_;
11              
12 11         16 my $app = delete $args{app};
13              
14 11         13 my @handlers;
15             push @handlers, {
16             startup => $args{startup},
17             shutdown => $args{shutdown},
18 11 100 66     54 } if $args{startup} || $args{shutdown};
19              
20 11         53 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 12 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 11 my ($self, %args) = @_;
41 8 50 66     16 return $self unless $args{startup} || $args{shutdown};
42 8         20 push @{$self->{_handlers}}, {
43             startup => $args{startup},
44             shutdown => $args{shutdown},
45 8         7 };
46 8         11 return $self;
47             }
48              
49             sub for_scope {
50 5     5 0 7 my ($class, $scope) = @_;
51 5 50 33     93 croak "scope is required" unless $scope && ref($scope) eq 'HASH';
52 5   33     19 return $scope->{'pagi.lifespan.manager'} //= $class->new;
53             }
54              
55             sub wrap {
56 2     2 1 8975 my ($class, $app, %args) = @_;
57              
58 2         10 my $self = $class->new(app => $app, %args);
59 2         9 return $self->to_app;
60             }
61              
62             sub to_app {
63 5     5 1 14 my ($self) = @_;
64              
65 5         12 my $app = $self->{app};
66 5 50       11 croak "PAGI::Lifespan->to_app requires an app" unless $app;
67              
68 10     10   107 my $wrapper = async sub {
69 10         26 my ($scope, $receive, $send) = @_;
70              
71 10   50     30 my $type = $scope->{type} // '';
72              
73 10 100       32 if ($type eq 'lifespan') {
74 4   33     20 $scope->{'pagi.lifespan.manager'} //= $self;
75 4   100     14 $scope->{state} //= {};
76 4         11 await $app->($scope, $receive, $send);
77 4         348 return await $self->handle($scope, $receive, $send);
78             }
79              
80 6         65 my $inner_scope = { %$scope };
81 6   50     55 $inner_scope->{state} //= ($self->{_state} // {});
      66        
82 6         17 $self->{_state} = $inner_scope->{state};
83              
84 6         27 await $app->($inner_scope, $receive, $send);
85 5         20 };
86              
87 5         11 return $wrapper;
88             }
89              
90 10     10 0 52 async sub handle {
91 10         17 my ($self, $scope, $receive, $send) = @_;
92 10 50 50     49 return 0 unless $scope && ($scope->{type} // '') eq 'lifespan';
      33        
93 10 50       22 return 0 if $scope->{'pagi.lifespan.handled'};
94 10         16 $scope->{'pagi.lifespan.handled'} = 1;
95              
96 10         11 my @handlers;
97 10 100       20 if (my $extra = $scope->{'pagi.lifespan.handlers'}) {
98 3         3 push @handlers, @$extra;
99             }
100 10   50     30 push @handlers, @{$self->{_handlers} // []};
  10         24  
101              
102 10   100     24 my $state = $scope->{state} //= {};
103 10         14 $self->{_state} = $state;
104              
105 10         15 while (1) {
106 18         210 my $msg = await $receive->();
107 18   50     1014 my $type = $msg->{type} // '';
108              
109 18 100       47 if ($type eq 'lifespan.startup') {
    50          
110 10         18 for my $handler (@handlers) {
111 17 100       32 next unless $handler->{startup};
112 15         16 eval { await $handler->{startup}->($state) };
  15         30  
113 15 100       472 if ($@) {
114 2         7 await $send->({
115             type => 'lifespan.startup.failed',
116             message => "$@",
117             });
118 2         53 return;
119             }
120             }
121 8         22 await $send->({ type => 'lifespan.startup.complete' });
122             }
123             elsif ($type eq 'lifespan.shutdown') {
124 8         16 for my $handler (reverse @handlers) {
125 15 100       192 next unless $handler->{shutdown};
126 11         11 eval { await $handler->{shutdown}->($state) };
  11         22  
127             }
128 8         163 await $send->({ type => 'lifespan.shutdown.complete' });
129 8         301 return 1;
130             }
131             }
132             }
133              
134             1;
135              
136             __END__