AnyEvent driver for Prima

Marc Lehmann schmorp at schmorp.de
Wed Dec 14 00:04:30 CET 2016


On Tue, Dec 13, 2016 at 08:50:35PM +0100, Dmitry Karasik <dmitry at karasik.eu.org> wrote:
> It gets a bit messy with everyone answering everone's mail, but I'll try to answer my best,
> and mix answers from different mails.

You are doing great :)

> > can't be fixed easily.  In particular, the io method should accept 
> > either a perl IO handle or a unix file descriptor number as the fd 
> > parameter.  Corion's implementation doesn't yet do that, but this can be 
> > fixed, and the AnyEvent::_dupfh undocumented function might help.
> 
> Prima doesn't accept fd, and AFAIK there's not easy perl way to extract that
> (other way around though, yes, with fileno()).

Indeed, Perl doesn't have a simple fdopen, the only way I know of to
dup-open the fd, wasting an fd.

For a C library on the other hand, the problem is trivial of course.

> to extend Prima so it can work with FDs, but it seems to me that if that is the common
> problem with event loop backends,

I could be wrong and some bug workarounds hide this for other loops, but
atm., it seems Prima is the only event loop that doesn't support it.

> > Ideally there should also be an idle method, which creates a watcher 
> > such that whenever the event loop would sleep waiting for new events to 
> > happen, the callback of the watcher is called instead of sleeping.  
> 
> Indeed there's no idle method in Prima, but could you possibly elaborate how that method
> is supposed to work? Does it fire every so-and-so seconds while a program is idle, or
> it only does so when there are no more events, and then sleeps? 

It should work like idle in other gui toolkits - basically, iff there are one
or more active idle watchers, then the event loop must not block. After
polling for events, if there are no outstanding events, the idle callbacks
should be invoked.

That means that an active idle watcher will keep the event loop from
blocking, but not from polling for and handling of events. Idle callbacks
will only be invoked when there are no other events outstanding.

> > for this: it only has the yield method of Prima::Application, which 
> > doesn't sleep if no events are available, and the go method, which 
> > doesn't return until the application exits.
> 
> I see. I wasn't aware of such a semantics, but I think I could export 
> something along application.poll() for doing this. I'll see how far I can go.
[...]
> I think I managed to implement exactly that just now: $::application->yield(1)
> should do it. The code is only on github though.

Wow, that was quick. I haven't tested it, but I hope corion will be able to
do so (from github).

If you wonder why such semantics are useful, consider a program that wants
to implement it's own event loop on top of Prima - the "poll for more
events, blocking if necessary, but return after handling them" semantics
can be used for that. Prima itself does that, when it does:

        while ( prima_one_loop_round( true, true))
                ;

> > object.)  I'd like to know if the Prima::File and Prima::Timer behave 
> > this way already.  In particular, if multiple events are queued, and 
> > then the callback for one of the events destroys the watcher for the 
> > second event, will Prima discard the second event?  If it doesn't, 
> > that's no problem, we can handle destroying the perl object specifically 
> > in the driver, but the driver doesn't currently do that.
> 
> With Prima::File it is easy - it does that. With Prima::Timer it is
> complicated, because it is not, and the only way to stop the events from
> happening is to call destroy() explicitly on the timer.

I think this question might mean something else - if you have a timer, and it
is expired (i.e. its callback would be invoked soon), will a destroy() call
keep that callback from being invokes?

Some event loops queue the callback invocation separately, so you a callback
can be invoked for a watcher that has been destroyed before.

The other problem, whether some destroy function needs to be invoked
explicity, can be easily handled within the AnyEvent implementation for
Prima (and it isn't the only event loop requiring this, Glib is another
good example for this).

> Application). With time though the design changed - I added Prima::Image
> functionality for application-less mode, and the application itself became a
> root/container for all widgets, while non-widgets, at least in theory, should
> be OK without it.

There is another question - how does one invoke the event loop without an
application, i.e. how does one call $::application->yield when there isn't an
$::application?

As I see it, it can't be done, so AnyEvent would simply "use
Prima::Application" and not have any problems with it not existing - it
doesn't seem as if Prima would work well with multiple application objects
anyway.

> > The Prima::Timer watcher is always in periodic repeating mode.  If the 
> > timeout is small, is it possible that the timeout fires twice before the 
> > event loop gets a chance to execute the callback the first time?  
> 
> This for sure won't happen for x11 backend, but for win32 backend I cannot
> answer that. The timer is implemented using standard SetTimer() function, which
> might or might not send more events in the system message queue. I just wrote a
> script to test this on my Windows 7, all seems ok, but I cannot give you a
> guarantee.
> 
> Also, the windows timer is rather imprecise, and can also eat up events (and it
> does, actually, the missed events do not accumulate).

While not being good, that's not an issue for AnyEvent, because AnyEvent
only has to guarantee that the callback is invoked after the timer
expires, so if timeouts are missed, an AnyEvent program cannot detect
that. That is, any number of invocations can be clumped together.

> > Corion's implementation of the timer calls the stop method of the timer 
> > from the callback, but I'd like to know if that's enough to make sure 
> > that the callback can't be called again.
> 
> stop() will depend on win32 message queue, which seems to do the right thing,
> but if you want the bullet proof code, call destroy() instead.

Corion: that means you need a wrapper class that explicitly calls destroy
in it's DESTROY function (AnyEvent::Impl::Glib could provide an example).

On Tue, Dec 13, 2016 at 09:48:00PM +0100, Dmitry Karasik <dmitry at karasik.eu.org> wrote:
> > So, the only thing that would be nice if Prima provided it would be a form
> > of idle watchers, because AnyEvent cannot emulate this correctly.
> 
> No it does actually - because I never used them, and don't know how they are
> supposed to work (called once? called always?). The info I google just now
> suggests that it is run every iteration of event loop, and that seems kind of
> an overkill, but again probably there's something I don't understand.

Most (not all) event loops that implement them implement them as outlined
earlier.

They are usually used for low-priority jobs that can be delayed if the
program is busy otherwise. Some event loops combine them with timers, so
you can do sth. like "call this callback if you have nothing else to do,
but do not wait for more than 10 seconds".

They are basically background jobs for event loops - if you have a CPU
intense job, you can run it in many steps from an idle watcher, running at
full speed when the app is otherwise idle, and not running at all when the
app handles events, such as user input.

There are a few other uses for them, such as keeping the event loop from
blocking but still polling for events, but they are rather specific.

When an event loop doesn't support idle watchers, then AnyEvent implements
them with timers, but that of course isn't the same thing, as idle
callbacks will neither run at full speed when theyx could, nor at low
speed when the app is busy.

> > The difficulties (and the added value of AnyEvent) is that we need to find
> > out about all the little details, such as whether Prima supports multiple
> > io watchers per fh
> 
> It does. 

That is also rare for event loop! :)

> > whether it supports fd,
> 
> No, but can be added - but if AnyEvent::_dupfd can be used, I'd vote for that instead.

AnyEvent can dup the fd internally - the only drawback is that it is very
slow (it needs to be done every time a watcher is created) and doubles the
number of fd's a program uses for AnyEvent watchers.

AnyEvent only does this for event loops that do not support multiple
watchers (which is inefficient, AnyEvent could handle it internally - but
it is a simple solution).

Since nobody will likely use Prima for high performance network servers,
this problem might be less of an issue, as the occasional http download
probably isn't much of an issue.

> Yes. On a simple level, all callbacks are killed as soon as the object that hosts them
> is killed too.

So the only thing we need to do is explicitly kill them with destroy(),
rather then relying on perl's destructor.

Out of curiosity, does Prima keep an internal reference, or what happens
when perl calls DESTROYm on those objects?

> For more fine-grained implmenentation, one can un-registed callbacks individually,
> but one has to register them explicitly first, and save the resulting handle to address them later.

Ah, ok, things are more complicated - with the object hosting them you
mean e.g. $::application? Can there be more than one such objects? If
yes, is there a way to find the main object that hosts the event loop
somehow, or does one always have to abuse the main namespace and use
$::application?

> > what time timers are relative to and probably
> 
> They are not relative to anything, a Prima::Timer->new( timeout => 100 ) fires up 10 times a second,
> imprecisely :)

Hmm, that's not good - how does one make a delay then? What you are saying
basically is:

   Prima::Timer->new( timeout => 10000 )

can basically fire anytime, even after a millisecond? Is there a way to
get something that fires only after "x" seconds/millseconds? How does one
implement delays in Prima? Network timeouts? There surely must be a way to
get a timer relative to some other time?

If there really isn't, then things are bleak. AnyEvent could possibly
work around that by just recreating timers in the hope that eventually,
one will delay enough, but that only works if the event loop is imprecise
enough for the delays to actually work.

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