File Coverage

blib/lib/POE/Session/YieldCC.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package POE::Session::YieldCC;
2              
3 1     1   22398 use strict;
  1         2  
  1         49  
4 1     1   4 use warnings;
  1         2  
  1         26  
5 1     1   499 use POE;
  0            
  0            
6             use Coro::State;
7              
8             our $VERSION = '0.202';
9              
10             BEGIN { *TRACE = sub () { 0 } unless defined *TRACE{CODE} }
11             BEGIN { *LEAK = sub () { 1 } unless defined *LEAK{CODE} }
12              
13             our @ISA = qw/POE::Session/;
14              
15             our $_uniq = 1;
16             sub _get_uniq { $_uniq++ }
17              
18             our $main;
19             our $last_state;
20             sub _invoke_state {
21             my $self = shift;
22             my $args = \@_; # so I can close on the args below
23              
24             # delimit the continuation stack
25             local $main = Coro::State->new;
26              
27             my $next;
28             $next = Coro::State->new(sub {
29             print " invoking the state $args->[1]\n" if TRACE;
30             $self->SUPER::_invoke_state(@$args);
31             print " invoked ok $args->[1]\n" if TRACE;
32              
33             # jump out to main, there's no need to save state
34             # $next is just discarded when _invoke_state is left
35              
36             # FACT: at this point there are no continuations into this state
37             # hence we're all done, and everything should be destroyed!
38              
39             $last_state = Coro::State->new;
40             register_object($last_state, "last_state") if LEAK;
41             $last_state->transfer($main);
42              
43             die "oops shouldn't get here"; # ie you should have discarded $next
44             });
45              
46             register_object($main, "main") if LEAK;
47             register_object($next, "next") if LEAK;
48              
49             print " pre-invoking $args->[1]\n" if TRACE;
50             $main->transfer($next);
51             print " post-invoking $args->[1]\n" if TRACE;
52              
53             $main = $next = $last_state = undef;
54             }
55              
56             sub yieldCC {
57             my ($self, $state, @args) = @_;
58             print "yieldCC! to $state\n" if TRACE;
59              
60             # this makes a continuation
61             my @retval;
62             my $save = Coro::State->new;
63             $POE::Kernel::poe_kernel->yield(
64             $state,
65             POE::Session::YieldCC::Continuation->new($save, \@retval, $self),
66             \@args,
67             );
68              
69             register_object($save, "yieldCC-save") if LEAK;
70              
71             # save the current state, and jump back out to main
72             print "jumping back out\n" if TRACE;
73             $save->transfer($main);
74              
75             return wantarray ? @retval : $retval[0];
76             }
77              
78             sub sleep {
79             my ($self, $delay) = @_;
80             # $self == the session
81              
82             my $uniq = _get_uniq;
83              
84             $poe_kernel->state(__PACKAGE__."::sleep_${uniq}" => \&_before_sleep);
85             $self->yieldCC(__PACKAGE__."::sleep_${uniq}", $delay);
86             }
87              
88             sub _before_sleep {
89             my ($cont, $args) = @_[ARG0, ARG1];
90             $_[KERNEL]->delay($cont->make_state, $$args[0]);
91             $_[KERNEL]->state($_[STATE]);
92             }
93              
94             sub wait {
95             my $self = shift;
96             my $uniq = _get_uniq;
97              
98             $poe_kernel->state(__PACKAGE__."::wait_event_${uniq}" => \&_before_wait);
99             $self->yieldCC(__PACKAGE__."::wait_event_${uniq}", @_);
100             }
101              
102             sub _before_wait {
103             my ($cont, $args) = @_[ARG0, ARG1];
104             my $state = shift @$args;
105             my $timeout = shift @$args;
106             my @post_timeout = @$args;
107              
108             my $tid;
109             my $cleanup = sub {
110             $poe_kernel->state($state);
111             $poe_kernel->alarm_remove($tid) if defined $tid;
112             $tid = undef;
113             };
114              
115             my $handle = sub {
116             return unless defined $cont;
117              
118             my $res = shift;
119             if (!$res && @post_timeout) {
120             $poe_kernel->state($state => @post_timeout);
121             } else {
122             $cleanup->();
123             }
124            
125             $cont->invoke($res, @_);
126             $cont = undef;
127             };
128              
129             $_[KERNEL]->state($state => sub { $handle->(1, @_[ARG0..$#_]) });
130              
131             if ($timeout) {
132             $_[KERNEL]->state($_[STATE]."_timeout" => sub { $handle->(0) });
133             $tid = $_[KERNEL]->delay_set($_[STATE]."_timeout", $timeout);
134             }
135              
136             $_[KERNEL]->state($_[STATE]);
137             }
138              
139             {
140             package POE::Session::YieldCC::Continuation;
141             use POE;
142             use overload
143             '&{}' => 'as_code',
144             fallback => 1;
145             use constant SELF_SAVE => 0;
146             use constant SELF_RETVAL => 1;
147             use constant SELF_SESSION => 2;
148             sub new { my $c = shift; return bless [@_], $c }
149             sub as_code { my $s = shift; return sub { $s->invoke(@_) } }
150             sub invoke {
151             my $self = shift;
152             my ($save, $retval) = @$self;
153             @$retval = @_;
154             @_ = ();
155              
156             print "continuation invoked\n" if POE::Session::YieldCC::TRACE;
157             local $main = Coro::State->new;
158             POE::Session::YieldCC::register_object($main, "continuation-main")
159             if POE::Session::YieldCC::LEAK;
160             $main->transfer($save);
161             $save = $last_state = undef;
162             print "continuation finished\n" if POE::Session::YieldCC::TRACE;
163             }
164             sub make_state {
165             my $self = shift;
166             $self->[SELF_SESSION]->_register_state(
167             "\0$self" => sub {
168             $self->invoke(@_[ARG0..$#_]);
169             $self->[SELF_SESSION]->_register_state("\0$self");
170             $self = undef;
171             }
172             );
173             return "\0$self";
174             }
175             }
176              
177             use Scalar::Util qw/weaken/;
178             our @objects;
179             our %descriptions;
180             sub register_object {
181             my $obj = shift;
182             @objects = grep defined($_), @objects;
183             push @objects, $obj;
184             weaken $_ for @objects;
185             my $description = shift;
186             $descriptions{$obj} = $description;
187             print "REGISTER $obj $description\n" if TRACE;
188             }
189             END {
190             @objects = grep defined($_), @objects;
191             if (@objects) {
192             print STDERR scalar(@objects), " objects still exist :-/\n";
193             print STDERR "$_ $descriptions{$_}\n" for sort @objects;
194             }
195             }
196              
197             1;
198              
199             __END__