ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/Impl/POE.pm
Revision: 1.18
Committed: Sat May 31 13:38:01 2008 UTC (16 years, 1 month ago) by root
Branch: MAIN
CVS Tags: rel-4_151, rel-4_152, rel-4_14, rel-4_15, rel-4_13, rel-4_12, rel-4_161, rel-4_160
Changes since 1.17: +6 -6 lines
Log Message:
indent 3

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