ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/Impl/POE.pm
Revision: 1.10
Committed: Fri Apr 25 04:34:30 2008 UTC (16 years, 2 months ago) by root
Branch: MAIN
Changes since 1.9: +24 -14 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::Impl::POE - AnyEvent adaptor for POE
4    
5     =head1 SYNOPSIS
6    
7     use AnyEvent;
8 root 1.2 use POE;
9 root 1.1
10     # this module gets loaded automatically as required
11    
12     =head1 DESCRIPTION
13    
14     This module provides transparent support for AnyEvent. You don't have to
15     do anything to make POE work with AnyEvent except by loading POE before
16     creating the first AnyEvent watcher.
17    
18 root 1.3 Unfortunately, POE isn't generic enough to implement a fully working
19     AnyEvent backend: POE is too badly designed, too badly documented and too
20     badly implemented.
21 root 1.2
22 root 1.3 Here are the details, and what it means to you if you want to be
23     interoperable with POE:
24 root 1.2
25     =over 4
26    
27     =item Weird messages
28    
29 root 1.4 If you only use C<run_one_timeslice> (as AnyEvent has to for it's
30     condition variables), POE will print an ugly, unsupressable, message at
31     program exit:
32 root 1.2
33     Sessions were started, but POE::Kernel's run() method was never...
34    
35     The message is correct, the question is why POE prints it in the first
36     place in a correct program (this is not a singular case though).
37    
38     The only way I found to work around this bug was to call C<<
39 root 1.5 ->run >> at AnyEvent loading time and stop the kernel immediately
40 root 1.2 again. Unfortunately, due to another design bug in POE, this cannot be
41     done (by documented means at least) without throwing away events in the
42     event queue.
43    
44     This means that you will either have to live with lost events or you have
45     to make sure to load AnyEvent early enough (this is usually not that
46     difficult in a main program, but hard in a module).
47    
48 root 1.7 =item One POE session per Event
49 root 1.2
50     AnyEvent has to create one POE::Session per event watcher, which is
51 root 1.3 immensely slow and makes watchers very large. The reason for this is
52     lacking lifetime management (mostly undocumented, too). Without one
53     session/watcher it is not possible to easily keep the kernel from running
54     endlessly.
55 root 1.2
56     =item One watcher per fd/event combo
57    
58     POE, of course, suffers from the same bug as Tk and some other badly
59     designed event models in that it doesn't support multiple watchers per
60 root 1.3 fd/poll combo. The workaround is the same as with Tk: AnyEvent::Impl::POE
61     creates a separate file descriptor to hand to POE, which isn't fast and
62     certainly not nice to your resources.
63 root 1.2
64 root 1.7 Of course, without the workaround, POE also prints ugly messages again
65     that say the program *might* be buggy.
66    
67 root 1.3 =item Timing Deficiencies
68    
69     POE manages to not have a function that returns the current time. This is
70     extremely problematic, as POE can use different time functions, which can
71     differ by more than a second. In addition, most timer functions in POE
72     want an absoltue timestamp, which is hard to create if all you have is a
73     relative time and no function to return the "current time".
74    
75     AnyEvent works around this by using relative timer fucntions, in the hope
76     that POE gets it right at least internally.
77    
78     =item Event Non-Ordering
79    
80     POE cannot guarentee the order of callback invocation for timers, and
81     usually gets it wrong. That is, if you have two timers, one timing out
82     after another, the callbacks might be called in reverse order.
83    
84     How one manages to even implement stuff that way escapes me.
85    
86     =item Child Watchers
87    
88 root 1.7 POE offers child watchers - which is a laudable thing, as few event loops
89 root 1.3 do. Unfortunately, they cannot even implement AnyEvent's simple child
90     watchers: they are not generic enough.
91    
92 root 1.6 Of course, if POE reaps an unrelated child it will also output a message
93     for it. Very professional.
94    
95 root 1.7 As a workaround, AnyEvent::Impl::POE will take advantage of undocumented
96     behaviour in POE::Kernel to catch the status fo all child processes.
97    
98     Unfortunately, POE's child handling is racy: if the child exits before the
99     handler is created (which is impossible to guarantee...), one has to wait
100     for another event to occur, which can take an indefinite time (apparently
101     POE does a busy-waiting loop every second, but this is not guarenteed
102     or documented, so in practise child status events can be delayed for a
103     second).
104    
105     How one manages to have such a glaring bug in an event loop after ten
106     years of development escapes me.
107 root 1.3
108     =item Documentation Quality
109    
110     At the time of this writing, POE was in its tenth year. Still, its
111     documentation is extremely lacking, making it impossible to implement
112 root 1.10 stuff as trivial as AnyEvent watchers without having to resort to
113 root 1.3 undocumented behaviour or features.
114    
115     For example, the POE::Kernel manpage has nice occurances of the word TODO
116 root 1.10 with an explanation of whats missing. In general, the POE manpages are
117     littered with comments like "section not yet written".
118    
119     Some other gems:
120 root 1.3
121     This allows many object methods to also be package methods.
122    
123     This is nice, but since it doesn't document I<which> methods these are,
124     this is utterly useless information.
125    
126     Terminal signals will kill sessions if they are not handled by a
127 root 1.10 "sig_handled"() call. The OS signals that usually kill or dump a
128     process are considered terminal in POE, but they never trigger a
129 root 1.3 coredump. These are: HUP, INT, QUIT and TERM.
130    
131 root 1.10 Although AnyEvent calls C<sig_handled>, removing it has no apparent
132     effects on POE handling SIGINT.
133 root 1.3
134     Furthermore, since the Kernel keeps track of everything sessions do, it
135     knows when a session has run out of tasks to perform.
136    
137     This is impossible - how does the kernel now that a session is no longer
138     watching for some (external) event (e.g. by some other session)? It
139 root 1.10 cannot, and therefore this is wrong - but you would be hard pressed to
140     find out how to work around this and tell the kernel manually about such
141     events.
142 root 1.3
143     It gets worse, though - the notion of "task" or "resource", although used
144     throughout the documentation, is not defined in a usable way. For example,
145     waiting for a timeout is considered to be a task, waiting for a signal is
146 root 1.10 not (a session that only waits for a signal is considered finished and
147     gets removed). The user is left guessing when waiting for an event counts
148     as task and when not.
149 root 1.3
150 root 1.10 One could go on endlessly - ten years, no usable documentation.
151 root 1.3
152 root 1.8 It is likely that differences between documentation, or the one or two
153 root 1.10 things I had to guess, cause unanticipated problems with this adaptor.
154 root 1.3
155     =item Bad API
156    
157     The POE API is extremely inconsistent - sometimes you have to pass a
158     session argument, sometimes it gets ignored, sometimes a session-specific
159     method must not use a session argument.
160 root 1.2
161 root 1.10 Error handling is sub-standard as well: even for programming mistakes,
162     POE does not C<croak> but, in most cases, just sets C<$!> or simply does
163     nothing at all, leading to fragile programs.
164    
165     Sometimes registering a handler uses the "eventname, parameter" ordering
166     (timeouts), sometimes it is "parameter, eventname" (signals). There is
167     little consistency overall.
168 root 1.1
169 root 1.3 =back
170 root 1.1
171 root 1.9 On the good side, AnyEvent allows you to write your modules in a 100%
172     POE-compatible way (bug-for-bug compatible even), without forcing your
173 root 1.3 module to use POE - it is still open to better event models, of which
174     there are plenty.
175 root 1.1
176     =cut
177    
178     package AnyEvent::Impl::POE;
179    
180     no warnings;
181     use strict;
182    
183     use POE;
184    
185     # have to do this to keep POE from spilling ugly messages
186     POE::Session->create (inline_states => { _start => sub { @_[KERNEL]->stop } });
187     POE::Kernel->run;
188    
189     sub io {
190     my ($class, %arg) = @_;
191     my $poll = delete $arg{poll};
192     my $cb = delete $arg{cb};
193 root 1.2
194     # cygwin requires the fh mode to be matching, unix doesn't
195     my ($pee, $mode) = $poll eq "r" ? ("select_read" , "<")
196     : $poll eq "w" ? ("select_write", ">")
197     : Carp::croak "AnyEvent->io requires poll set to either 'r' or 'w'";
198    
199     open my $fh, "$mode&" . fileno $arg{fh}
200     or die "cannot dup() filehandle: $!";
201    
202 root 1.1 my $session = POE::Session->create (
203     inline_states => {
204     _start => sub {
205 root 1.2 $_[KERNEL]->$pee ($fh => "ready");
206 root 1.1 },
207     ready => sub {
208     $cb->();
209     },
210     stop => sub {
211 root 1.2 $_[KERNEL]->$pee ($fh);
212 root 1.1 },
213     },
214     );
215     bless \\$session, AnyEvent::Impl::POE::
216     }
217    
218     sub timer {
219     my ($class, %arg) = @_;
220     my $after = delete $arg{after};
221     my $cb = delete $arg{cb};
222     my $session = POE::Session->create (
223     inline_states => {
224     _start => sub {
225     $_[KERNEL]->delay_set (timeout => $after);
226     },
227     timeout => sub {
228     $cb->();
229     },
230     stop => sub {
231     $_[KERNEL]->alarm_remove_all;
232     },
233     },
234     );
235     bless \\$session, AnyEvent::Impl::POE::
236     }
237    
238     sub signal {
239     my ($class, %arg) = @_;
240     my $signal = delete $arg{signal};
241     my $cb = delete $arg{cb};
242     my $session = POE::Session->create (
243     inline_states => {
244     _start => sub {
245     $_[KERNEL]->sig ($signal => "catch");
246     $_[KERNEL]->refcount_increment ($_[SESSION]->ID => "poe");
247     },
248     catch => sub {
249     $cb->();
250     $_[KERNEL]->sig_handled;
251     },
252     stop => sub {
253     $_[KERNEL]->refcount_decrement ($_[SESSION]->ID => "poe");
254     $_[KERNEL]->sig ($signal);
255     },
256     },
257     );
258     bless \\$session, AnyEvent::Impl::POE::
259     }
260    
261 root 1.7 sub child {
262     my ($class, %arg) = @_;
263     my $pid = delete $arg{pid};
264     my $cb = delete $arg{cb};
265     my $session = POE::Session->create (
266     inline_states => {
267     _start => sub {
268     $_[KERNEL]->sig (CHLD => "child");
269     $_[KERNEL]->refcount_increment ($_[SESSION]->ID => "poe");
270     },
271     child => sub {
272     my ($rpid, $status) = @_[ARG1, ARG2];
273    
274     $cb->($rpid, $status) if $rpid == $pid || $pid == 0;
275     },
276     stop => sub {
277     $_[KERNEL]->refcount_decrement ($_[SESSION]->ID => "poe");
278     $_[KERNEL]->sig ("CHLD");
279     },
280     },
281     );
282     bless \\$session, AnyEvent::Impl::POE::
283     }
284    
285 root 1.1 sub DESTROY {
286     POE::Kernel->post (${${$_[0]}}, "stop");
287     }
288    
289     sub one_event {
290     POE::Kernel->loop_do_timeslice;
291     }
292    
293     1;
294    
295     =head1 SEE ALSO
296    
297     L<AnyEvent>, L<POE>.
298    
299     =head1 AUTHOR
300    
301     Marc Lehmann <schmorp@schmorp.de>
302     http://home.schmorp.de/
303    
304     =cut
305