ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/Impl/POE.pm
Revision: 1.13
Committed: Fri Apr 25 06:54:10 2008 UTC (16 years, 2 months ago) by root
Branch: MAIN
Changes since 1.12: +2 -0 lines
Log Message:
*** empty log message ***

File Contents

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