in reply to Implementing AnyEvent::Ping multiple times

The code as presented is confusing to me. That may be my fault as I am no authority on event loops. However, since nobody else has replied yet it may be that they are confused as well. If you could maybe address these questions I have then it might encourage others to chip in too.

  1. You are using both IO::Async and AnyEvent but not using either of the glue modules I would expect to see (AnyEvent::Impl::IOAsync or IO::Async::Loop::AnyEvent). Could you explain this?
  2. The code appears to want to ping the same set of hosts twice on different schedules. I cannot think of any reason to do this. What is your intention here?
  3. Within each timer you are pinging 254 addresses 4 times with a 2 second timeout on each. Any target which drops the packets will therefore take 8 seconds to complete. And yet your timers are each on a hard reschedule of less than 8 seconds. Even if your code worked without blocking you could end up with a ping bomb. Again, without knowing your intention it is hard to advise further.

If you could give a clear statement of your intended algorithm it would help me (and maybe others) to be in a position to help you more.

Update: removed reference to IO::Async::Loop::AnyEvent as newer versions of AnyEvent explicitly prevent its use.

  • Comment on Re: Implementing AnyEvent::Ping multiple times

Replies are listed 'Best First'.
Re^2: Implementing AnyEvent::Ping multiple times
by mmoorreett (Acolyte) on Jun 11, 2020 at 20:11 UTC

    Thanks for the reply, sorry if this seems confusing.

    This is my first go at using event loops so I am slowly grasping the concepts and how to implement it, but i'm struggling a little, so I'm not even sure if i'm doing this properly. The main reason I started with this, was to develop a simple perl ICMP monitoring plugin for another application that would be able to ping different sets of many hosts, very quickly, across different, possibly overlapping schedules. E.g 192.168.1.x every 2 minutes and 192.168.2.x every 5 minutes. I've tried various methods and the AnyEvent::Ping module seems to be the best option for performance for asynchronous. To answer your questions:

    1. I used IO::Async::Timer::Periodic to give me an extra option on scheduling the code execution. I didn't realise that i needed AnyEvent::Impl::IOAsync, i've done some more reading and understand this now, so thanks.

    2. This is just test code, so in reality the intention is to invoke AnyEvent::Ping on different sets of hosts and different schedules, however I'm not sure how I get around the fact that AnyEvent::Ping is blocking. I just didn't bother to create another subnet as I'm still trying to understand how I will get this to work. So just ignore that for now.

    3. Yes I'm aware of that, thats me testing on how the reschedule option in Periodic works, i.e. the ping bomb you're talking about, so just ignore that for now. If this is not the best approach to achieve this then I'm open to alternatives. I also looked at using Threads but its very CPU/memory intensive compared to AnyEvent::Ping. I'm testing this on Windows using Strawberry Perl if thats relevant.

    Thanks! Mike

      Thanks for the clarifications - that helps a lot. Having played around with this now I think that the problem you are having essentially boils down to the fact that the event loop is blocking because you are telling it to and that there's no reason for you to do so.

      Firstly, in order to simplify things I have decided to concentrate solely on AnyEvent (because that's what I'm less unfamiliar with) and ditch IO::Async for this illustration. That gets rid of the need for the glue and stops the chances of 2 different event loops coming to blows. As a starting point, here are two timers running on 5 and 7 second intervals implemented purely with AnyEvent. There's a third timer there just so the thing doesn't carry on forever.

      #!/usr/bin/env perl use strict; use warnings; use AnyEvent; my $cv = AnyEvent->condvar; my @timers = ( AnyEvent->timer ( after => 2, interval => 5, cb => sub { warn "Timer 1 at " . AnyEvent->now . "\n"; } ), AnyEvent->timer ( after => 2, interval => 7, cb => sub { warn "Timer 2 at " . AnyEvent->now . "\n"; } ), AnyEvent->timer ( after => 20, cb => sub { warn "Timer 3 at " . AnyEvent->now . "\n"; $cv->send; } ), ); $cv->recv;

      If you run this you will see something along these lines:

      Timer 1 at 1591953440.37264 Timer 2 at 1591953440.37264 Timer 1 at 1591953445.37213 Timer 2 at 1591953447.37533 Timer 1 at 1591953450.36854 Timer 2 at 1591953454.38475 Timer 1 at 1591953455.36656 Timer 3 at 1591953458.37977

      So the timers seem to be working on their correct schedules. Now we can add in some pings. I've reduced the numbers here so we don't get bucketloads of output, but the principle should be the same.

      #!/usr/bin/env perl use strict; use warnings; use AnyEvent; use AnyEvent::Ping; use Data::Dumper; my @todoList = map { "192.168.100." . $_ } (1 .. 5); my $cv = AnyEvent->condvar; my @timers = ( AnyEvent->timer ( after => 2, interval => 5, cb => sub { warn "Timer 1 at " . AnyEvent->now . "\n"; ping_hosts (1, @todoList); } ), AnyEvent->timer ( after => 2, interval => 7, cb => sub { warn "Timer 2 at " . AnyEvent->now . "\n"; ping_hosts (2, @todoList); } ), AnyEvent->timer ( after => 20, cb => sub { warn "Timer 3 at " . AnyEvent->now . "\n"; $cv->send; } ), ); $cv->recv; sub ping_hosts { my ($num, @hosts) = @_; my $ping_timeout = 2; #seconds to wait for response my $ping_attempts = 4; #number of pings to issue, my $ping_pktsize = 32; #packet size of ping my $ping = AnyEvent::Ping->new (packet_size => $ping_pktsize); $ping->timeout ($ping_timeout); #$cv_ping->begin(sub { shift->send($_[0]) }); foreach my $host (@hosts) { $ping->ping ( $host, $ping_attempts, sub { print "Pinger $num: $host state:" . Dumper ($_[0]) . " +\n"; } ); } }

      I've simplified your ping loop/sub a bit and you'll notice that there is now no counter and importantly no cond_var. This is because you have no need to block here if all you are doing is firing off the pings. Now when we run this it runs to completion with the pings sent on the intended schedule.

      This is my first go at using event loops so I am slowly grasping the concepts and how to implement it, but i'm struggling a little, so I'm not even sure if i'm doing this properly.

      The concepts are tough - there's no doubt about that. I don't do much functional programming and even less on event loops so it was interesting to approach this from an equally inexpert standpoint. My simplistic approach with these is just not to block unless it's really necessary. That's not always easy to do when you are coming from a procedural background but it has helped me through when tackling this sort of task.

      I hope this is useful for you and wish you luck with the rest of your project.

        Not sure if my reply got posted properly.

        Thanks again for your help

        I did try something similar to this, but the problem I have is that I need the ping_hosts() sub to complete before the timer executes again, essentially blocking the timer. Thats why I had left $cv->recv; inside the ping_hosts() sub. Sorry I may have not made this clear.

        From my basic understanding of event loops, this approach isn't going to work as there could be n+1 schedules blocking, I think I need to do something like whats described in http://www.perladvent.org/2014/2014-12-24.html replacing $cv->recv; with $cv->cb($callback); I'll keep plugging away but any assistance is much appreciated!