Can I synchronously relinquish control to the event loop?

Marc Lehmann schmorp at schmorp.de
Tue Feb 28 06:17:22 CET 2012


On Mon, Feb 27, 2012 at 03:28:18PM -0500, Fulko Hew <fulko.hew at gmail.com> wrote:
> so intuitively I'd expect the flow to go:

assuming that the code you haven't shown reads a request and writes
results, then the flow will indeed go like this - you cna use
printf-debugging to see that you always wait for a request until you write
the response.

> but instead I see on Wireshark something similar to:

thats probably because the requests come in one "block", and your code
then handles all in one go. event loops generally optimise for efficiency,
not latency.

if your code is fast w.r.t network, then your responses are probably big,
and the kernel can't send them fast enough, so starts queuing (and so will
anyevent).

if your code is slow w.r.t. the network, then the problem is likely requests
arriving faster than you can process them. this is likely the scenario you
have,

anyevent (and event loops) do not know anything of your requests, and do not
impose (or guarantee) a specific ordering on how they are processed.

that is, if you need a specific ordering, you have to write code for that,
which depends very much on what you want to achieve

for example, you could push register and allocate "requests" on different
arrays and then let a scheduler coro handle the thing:

   allocate:
      push @all, "allocate";
      $delayed ||= AE::timer 0.01, 0, \&scheduler;

   register:
      push @reg, "register";
      $delayed ||= AE::timer 0.01, 0, \&scheduler;

   sub scheduler {
      undef $delayed;

      while (@all || @reg) {
         handle_allocate shift @all if @all;
         handle_allocate shift @reg if @reg;
      }
   }

In the scheduler you can impose any "pattern" you want.

the 0.01 delay can be tuned to your needs - if 100 allocate and register
requests arrive within 0.02s of each other, then the delay needs to be
increased.

> I found that I _could_ get things to 'interleave' by placing a
> Coro::AnyEvent::sleep 0.001;  following each of my allocate request

remember that you *HAVE* to make sure you don't call this in an event
callback, such as a push_read callback.

> thereby (artificially slowing things down) allowing one or both ends
> to 'process' and thereby also allowing the response to be
> 'received and processed'. (calling idle or poll didn't affect the flow)

Thats basically what you have to do in some way.

With coro, you have the additional option of using the scheduler solution,
above, by running the scheduler in a separate thread, and instead of using a
delay to ensure you receive all requests, you could do:

      while (@all || @reg) {
         handle_allocate shift @all if @all;
         handle_allocate shift @reg if @reg;
         Coro::AnyEvent::poll;
      }

That is, after some potentially slow processing, you would poll for new
requests before continuing to handle them.

In this case you could reduce the delay to 0 (basically, after receiving a
request, wake up the processing thread with no delay).

You can't do this with anyevent at all, because anyevent isn't itself an
event loop, and not all loops support recursion, so you would have to find an
event loop that supports recursion and in your code, recurse into the event
loop in the while loop, e.g. with EV:

   EV::run EV::LOOP_NOWAIT;

Or you can be a good citizen and write your code in an event-based fashion.

In other words, you expetc a certain pattern in how requests are handled,
and AnyEvent (or the underlying event loop) cannot know this patterm so
you have to write your own code to ensure the ordering you need.

>   sub wait_for_on_drain {
> >      my ($handle) = @_;
> >
> >      $handle->on_drain (Coro::rouse_cb);
> >      Coro::rouse_wait;
> >      $handle->on_drain (undef);
> >   }
> >
> > ... snip ...
> 
> This and the other example didn't affect the apparent processing flow
> either.

That means the bottleneck is your code, and not the network -
Anyevent::Handle tries to write replies instantly by default (autocork),
so likely, the write queue is always empty.

> I haven't tried this yet, but...

Remember you *must not* block the event loop thread, i.e. you *must not*
block in push_read clalbacks, or AE::io callbacks, or any callbacks from
the event loop.

Few event loops support that, and even for those few that support it, Coro
only manages one extra event loop, so you will hit problems with the right
request/response pattern.

> In the mean time what I did do was code up something that would:
> - queue up my requests, and then one at a time...
>   - peel them off a list, and
>   - daisy-chain the 'register' to the end of the 'allocate response'
> processing, and
>   - daisy-chain the 'next allocate' onto the end of the 'register response'
> processing.

absolutely - you want a specific pattern, you need to write code to
implement it.

> I'll look and see if async { ...} alters the flow.

The async is not a sugegtion to alter the flow, the async is required to
avoid memory corruption and deadlocks. Repeat after me: "I must not block
the event loop".

My first reply tried to solve this problem:

   I write data faster than the network can handle it, so anyevent queues
   it and thus delays it until all requests have been processed.

This is not your problem. My current reply tries to solve this problem:

   I need a specific pattern in handling requests/responses from different
   handles.

And I hope that actually *is* your problem, because between all the
"allocates" and "receives" and "responses" I am still not sure what your
program tries to do :)

Just keep this in mind: Do not block the current thread in an event loop
callback.

-- 
                The choice of a       Deliantra, the free code+content MORPG
      -----==-     _GNU_              http://www.deliantra.net
      ----==-- _       generation
      ---==---(_)__  __ ____  __      Marc Lehmann
      --==---/ / _ \/ // /\ \/ /      schmorp at schmorp.de
      -=====/_/_//_/\_,_/ /_/\_\



More information about the anyevent mailing list