Subject: | Receive buffer emptying patch |
This implements the following:
* Changed the behavior of the _event_handle sub to completely process
all of the receive buffers (using instant/zero-second select calls)
before continuing back to sending requests.
* Adds some extra debugging statements for timings
Okay, now we are starting to get into deeper changes into Dispatcher.
However, this is a small set of changes with a huge impact to large
requests. This makes sense, since it's just processing what is already
in its queue. If the dispatcher is stuck continuing to process receive
packets, then it has no business sending new requests, anyway.
The current process flows like this:
S = Send request, R = Receive packet
SRSRSRSRSRSR....
The new one is (approx.):
SRSRSRRSRRSRSRSRRRSRSR...
In most cases, the new method is not going to be processing that many
receive packets, since it's keeping up with traffic. Generally just 1-3
packets per cycle. However, with the old method, if the buffer is
starting to build, it will continue to build until hosts timeout, or
worse, the OS starts to drop the packets.
A second argument is that this behavior already exists for TCP
connections. Look here:
for (keys %{$this->{_descriptors}}) {
if (vec $rout, $_, 1) {
DEBUG_INFO('descriptor [%d] ready for read', $_);
$this->_callback_execute(@{$this->{_descriptors}->{$_}}[0,1]);
}
}
This is from the current code. You've got a loop around EACH descriptor
that has data and the Dispatcher is already processing the callback for
EACH one before it ends the loop and goes back to sending. Problem is
that poor ol' UDP is stuck with using the same fileno, per Transport.pm
rules. (It'll keep getting "reused {type} socket" debug messages for
every session opened.) In most cases, you'll end up with every host and
every request all using the same fileno.
So, really, this is a bug fix to make UDP behave more like TCP in the
event loop. And if it makes the Dispatcher more efficient at TCP, then
so be it; that's a bonus.
Subject: | RecvBuffer.patch.txt |
diff -bcdr /usr/local/share/perl/5.10.1/Net/SNMP/Dispatcher.pm Net/SNMP/Dispatcher.pm
*** /usr/local/share/perl/5.10.1/Net/SNMP/Dispatcher.pm 2010-09-09 20:02:45.000000000 -0400
--- Net/SNMP/Dispatcher.pm 2011-08-01 19:59:40.000000000 -0400
***************
*** 593,607 ****
# specified by the caller.
$timeout = $event->[_TIME] - $time;
DEBUG_INFO('event %s, timeout = %.04f', $this->_event_info($event), $timeout);
}
}
# Check the file descriptors for activity.
!
! my $nfound = select(my $rout = $this->{_rin}, undef, undef, $timeout);
if (!defined $nfound || $nfound < 0) {
--- 649,665 ----
# specified by the caller.
$timeout = $event->[_TIME] - $time;
DEBUG_INFO('event %s, timeout = %.04f', $this->_event_info($event), $timeout);
}
}
# Check the file descriptors for activity.
! my $nfound = 0;
! do {
! my $stime = time();
! $nfound = select(my $rout = $this->{_rin}, undef, undef, $timeout);
if (!defined $nfound || $nfound < 0) {
***************
*** 613,631 ****
--- 671,708 ----
} elsif ($nfound > 0) {
+ DEBUG_INFO('found ready descriptors after %.04fs, timeout = %.04f', time() - $stime, $timeout);
+
# Find out which file descriptors have data ready for reading.
if (defined $rout) {
for (keys %{$this->{_descriptors}}) {
if (vec $rout, $_, 1) {
DEBUG_INFO('descriptor [%d] ready for read', $_);
+ $stime = time();
$this->_callback_execute(@{$this->{_descriptors}->{$_}}[0,1]);
+ DEBUG_INFO('total receiving packet processing took %.04fs', time() - $stime);
}
}
}
}
+ # If any receiving data was found, keep instant polling to see if there is
+ # anything else in the socket buffers. If so, keep running through the
+ # receiving data until its clear before any more new events are sent
+ # through the pipe. As soon as the dispatcher has to wait a millisecond more
+ # than instant, return out and the dispatcher will eventually return to
+ # processing the event lists.
+
+ # This provides a heathly balance between fast polling, and keeping the
+ # dispatcher from getting overloaded.
+
+ $timeout = 0 if ($nfound);
+
+ } while ($nfound);
+ DEBUG_INFO('socket buffer empty, total event processing = %.04fs, timeout = %.04f', time() - $time, $timeout);
+
return TRUE;
}
diff -bcdr /usr/local/share/perl/5.10.1/Net/SNMP.pm Net/SNMP.pm
*** /usr/local/share/perl/5.10.1/Net/SNMP.pm 2010-09-09 20:02:45.000000000 -0400
--- Net/SNMP.pm 2011-08-01 10:12:13.000000000 -0400
***************
*** 91,96 ****
--- 91,118 ----
the corresponding ObjectSyntax. The undefined value is returned if there has
been a failure and the C<error()> method may be used to determine the reason.
+ =head2 Multiple Non-blocking Objects for Multiple Hosts
+
+ Multiple non-blocking sessions can be created to query multiple hosts at the
+ same time. Each object is different and can have different options, but they
+ are all tied to the same dispatcher. This means that the dispatcher will
+ process all requests from all hosts when the event loop is started, and won't
+ return until everything has replied or timed out.
+
+ Prior to v6.10, Net::SNMP would send requests as fast as possible, even if the
+ transport buffers could not handle the amount of data returning back. This was
+ especially true when communicating with multiple hosts on the traditional UDP
+ port. Data was checked and processed as requests were being sent, but only one
+ packet at a time.
+
+ With the improved dispatcher code, new requests are send to the various hosts
+ until data is detected on the transport buffers. Then the data is processed
+ until the buffers are empty again. This pattern is repeated until the queue
+ is empty. This balance between sending and receiving keeps requests flowing
+ quickly, but mitigates against overloading. This allows for dispatch queues
+ with as many requests and/or hosts as possible, as long as the Net::SNMP
+ client has the CPU/RAM resources to process them.
+
=cut
# ============================================================================