Skip Menu |

This queue is for tickets about the Net-SNMP CPAN distribution.

Report information
The Basics
Id: 70586
Status: rejected
Worked: 11 min
Priority: 0/
Queue: Net-SNMP

People
Owner: dtown [...] cpan.org
Requestors: BitCard [...] ResonatorSoft.org
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: v6.0.1
Fixed in: (no value)



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 # ============================================================================
The intent of the _event_handle() method is to create a balance between handling the "timer" events and the "i/o" events. By biasing towards the "i/o" events the "timer" events end up being starved, leading to accepting late responses when they should have really timed out. The "timer" has in a sense be suspended, to allow for more "i/o". Also, the _event_handle() method was designed to mimic the processing of a single event. When integrating into another event loop this method is called by snmp_dispatch_once(). The current balance of "timer" and "i/o" event processing has been based upon many variations over the past releases of the module.
On Tue Nov 01 22:57:13 2011, DTOWN wrote: Show quoted text
> The intent of the _event_handle() method is to create a balance between > handling the "timer" events and the "i/o" events. By biasing towards > the "i/o" events the "timer" events end up being starved, leading to > accepting late responses when they should have really timed out. The > "timer" has in a sense be suspended, to allow for more "i/o".
Are we talking about timeouts or actual "timed" events via -delay? In terms of the former, the MsgID is already properly removed and the received packet gets thrown out, so timeouts still happen. Also, this is sort of a Erlang-C problem, like a call center. When things are normal, everything gets a proper balance, in a standard send/receive cycle. When things are overloaded, things get REALLY overloaded. The receive queue gets overloaded, yet in the old code, it keeps sending requests, when then get replies that it can't handle. This is just a vicious cycle that crushes the process under its own weight, as requests time out everywhere and causes a big mess. This is just trying to keep things more "normal" than not. With this patch, it might grab two packets instead of one before sending another request, but that balances out the send/receives, so that it can send what it can handle in replies. With the old code, just by grabbing one packet instead of two, that problem grows into that cycle of timeouts. Show quoted text
> Also, the _event_handle() method was designed to mimic the processing of > a single event. When integrating into another event loop this method is > called by snmp_dispatch_once().
You already HAVE another event loop. If all of the connections were open via TCP, then the _event_handle would process through EACH ONE before returning out of the sub. If you sent 75 requests at the same time, and they all happen to hit the bucket at the same time (not unreasonable, given that it may be the same OID), then nothing else is going to get sent until the buffer is cleared. So, is it a bug that UDP doesn't act the same way, or is it a bug that TCP acts this way now? Also, snmp_dispatch_once doesn't tell me anything about select times, either. I've tried to peer at select times outside of the module, but I ended up with inaccuracies between the select call and going in the module. Besides, I really shouldn't be messing with $Dispatcher->{_descriptors} objects, as those are supposed to be internal, anyway. Hence the patch. Show quoted text
> The current balance of "timer" and "i/o" event processing has been based > upon many variations over the past releases of the module.
And I understand the hesitation to implement new changes to the event processing. But, know that I'm not trying to jury-rig the code to work with just my needs, but making it work for everybody. Try to keep an open-mind on these things. I tried approaching this from outside the module first before I realized that the most logical thing to do was to implement a patch. I'm about done with my DBD::SNMP module, and I'd like to keep from overloading practically the entire Dispatcher.pm module when I submit it to CPAN.
Considering we are probably going nowhere with this conversation, let me approach this with a different angle: What kind of tests would be necessary to prove out the different use cases and make sure everything acts predictably? I don't want you to add the patch in because I'm being persistent, but because you're comfortable with the results.
Rejected based upon my reply on Tue Nov 01 22:57:13 2011.