Async iterators

Marc Lehmann schmorp at schmorp.de
Mon Aug 6 18:25:55 CEST 2012


On Mon, Aug 06, 2012 at 03:03:49PM +0200, Clinton Gormley <clint at traveljury.com> wrote:
> I'm a bit clueless about event-driven programming, but I've had requests

You should look at AnyEvent::Intro, or maybe google some intros about
event-driven programming in general then.

> next() checks if there are sufficient docs in the buffer to satisfy the
> request and if not, calls refill_buffer() and loops to check again.

next() is fundamentally not event based.

> My first attempt at converting this module looks like this:
> 
> https://gist.github.com/3274257

    return if in_global_destruction;

Looks painful :)

> I know that I shouldn't be calling AnyEvent->_poll() but I don't know
> how I should be approaching this.  

Calling internal methods will not even do it if it were supported: your API
is fundamnetally not event-driven.

> One suggestion that was made to me was to expose something like:
> 
>     $cv = $s->ensure_buffer_is_ready;
>     
> which would allow the user to decide how to block, poll whatever, but
> this feels like I'm leaking implementation details.

The only way to make a blocking API event driven is to change it. For
example, like this:

   $s->next (100, sub { my ($s, @results) = ... });

> It may be that the interface to an event-driven iterator should be 
> completely different. I assume that there are well known patterns for
> doing this kind of thing in the async world, but I've not had much
> success in finding them.

Well, it's pretty easy: "don't call us, we call you", that is, instead of:

   my @results = some_function ...;

The API needs to be:

   some_function ..., sub { # called with results

Now, there are a few ways on how to make your module anyevent-aware:

1) get rid of internal calls such as _poll (which will likely break sooner
   or later) and use condvars. that means your module is not event driven,
   and only one such module can do things concurrently. if you documented
   this properly then users could employ Coro and just run your module
   inside it's own thread - you can block on condvars from multiple
   threads.

   that means no event-driven usage, no concurrency of your module with
   others, but at least it wouldn't block out event-driven things, and could
   be worked around with Coro, without your module having a dependence on
   Coro, e.g. users could just do:

      # unfortunately, $search is not event-driven, but it can coexist with
      # AnyEvent, so we use Coro to work arpund that:
      async {
         my @result = $search->next (100);
      };

   it's the worst possible solution.

2) change your api to be event-driven, by always returning "instantly" and
   instead calling a callback with results.

3) create a new api as in #2, to be used in parallel. the "old" api could
   even use the anyevent apiand block by using condvars, e.g.:

   sub next {
      my ($self, $count, $cb) = @_;

      if ($cb) {
         # event based
      } else {
         # block
         $self->next ($count, my $cv = AE::cv);
         return $cv->recv;
      }
   }:

Basically, if you want your module to coexist with others in the same
process concurrently, then you must not block, not with, nor without
anyevent. If you want to rely on your users having to use Coro to use
concurrency, then using condvars and blocking on them would do the job. If
you want to be event-driven, your API must be based on callbacks and never
wait on I/O or other events itself.

-- 
                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