=head1 NAME AnyEvent::SNMP - adaptor to integrate Net::SNMP into Anyevent. =head1 SYNOPSIS use AnyEvent::SNMP; use Net::SNMP; # just use Net::SNMP and AnyEvent as you like: # use a condvar to transfer results, this is # just an example, you can use a naked callback as well. my $cv = AnyEvent->condvar; # ... start non-blocking snmp request(s)... Net::SNMP->session (-hostname => "127.0.0.1", -community => "public", -nonblocking => 1) ->get_request (-callback => sub { $cv->send (@_) }); # ... do something else until the result is required my @result = $cv->wait; =head1 DESCRIPTION This module implements an alternative "event dispatcher" for Net::SNMP, using AnyEvent as a backend. This integrates Net::SNMP into AnyEvent: You can make non-blocking Net::SNMP calls and as long as other parts of your program also use AnyEvent (or some event loop supported by AnyEvent), they will run in parallel. Also, the Net::SNMP scheduler is very inefficient with respect to both CPU and memory usage. Most AnyEvent backends (including the pure-perl backend) fare much better than the Net::SNMP dispatcher. A potential disadvantage is that replacing the dispatcher is not at all a documented thing to do, so future changes in Net::SNP might break this module (or the many similar ones). This module does not export anything and does not require you to do anything special apart from loading it I. It is recommended but not required to load this module before C. =cut package AnyEvent::SNMP; no warnings; use strict qw(subs vars); # it is possible to do this without loading # Net::SNMP::Dispatcher, but much more awkward. use Net::SNMP::Dispatcher; sub Net::SNMP::Dispatcher::instance { AnyEvent::SNMP:: } use Net::SNMP (); use AnyEvent (); our $VERSION = '0.1'; $Net::SNMP::DISPATCHER = instance Net::SNMP::Dispatcher; our $MESSAGE_PROCESSING = $Net::SNMP::Dispatcher::MESSAGE_PROCESSING; # avoid the method call my $timer = sub { shift->timer (@_) }; AnyEvent::post_detect { $timer = AnyEvent->can ("timer") }; our $BUSY; our %TRANSPORT; # address => [count, watcher] sub _send_pdu { my ($pdu, $retries) = @_; # mostly copied from Net::SNMP::Dispatch # Pass the PDU to Message Processing so that it can # create the new outgoing message. my $msg = $MESSAGE_PROCESSING->prepare_outgoing_msg ($pdu); if (!defined $msg) { --$BUSY; # Inform the command generator about the Message Processing error. $pdu->status_information ($MESSAGE_PROCESSING->error); return; } # Actually send the message. if (!defined $msg->send) { $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id) if $pdu->expect_response; # A crude attempt to recover from temporary failures. if ($retries-- > 0 && ($!{EAGAIN} || $!{EWOULDBLOCK} || $!{ENOSPC})) { my $retry_w; $retry_w = AnyEvent->$timer (after => $pdu->timeout, cb => sub { undef $retry_w; _send_pdu ($pdu, $retries); }); } else { --$BUSY; } # Inform the command generator about the send() error. $pdu->status_information ($msg->error); return; } # Schedule the timeout handler if the message expects a response. if ($pdu->expect_response) { my $transport = $msg->transport; # register the transport unless ($TRANSPORT{$transport+0}[0]++) { $TRANSPORT{$transport+0}[1] = AnyEvent->io (fh => $transport->socket, poll => 'r', cb => sub { # Create a new Message object to receive the response my ($msg, $error) = Net::SNMP::Message->new (-transport => $transport); if (!defined $msg) { die sprintf 'Failed to create Message object [%s]', $error; } # Read the message from the Transport Layer if (!defined $msg->recv) { # for some reason, connected-oriented transports seem to need this unless ($transport->connectionless) { delete $TRANSPORT{$transport+0} unless --$TRANSPORT{$transport+0}[0]; } $msg->error; return; } # For connection-oriented Transport Domains, it is possible to # "recv" an empty buffer if reassembly is required. if (!$msg->length) { return; } # Hand the message over to Message Processing. if (!defined $MESSAGE_PROCESSING->prepare_data_elements ($msg)) { $MESSAGE_PROCESSING->error; return; } # Set the error if applicable. $msg->error ($MESSAGE_PROCESSING->error) if $MESSAGE_PROCESSING->error; # Cancel the timeout. my $rtimeout_w = $msg->timeout_id; if ($$rtimeout_w) { undef $$rtimeout_w; delete $TRANSPORT{$transport+0} unless --$TRANSPORT{$transport+0}[0]; --$BUSY; } # Notify the command generator to process the response. $msg->process_response_pdu; }); } #####d# timeout_id, wtf? $msg->timeout_id (\(my $rtimeout_w = AnyEvent->$timer (after => $pdu->timeout, cb => sub { my $rtimeout_w = $msg->timeout_id; if ($$rtimeout_w) { undef $$rtimeout_w; delete $TRANSPORT{$transport+0} unless --$TRANSPORT{$transport+0}[0]; } if ($retries--) { _send_pdu ($pdu, $retries); } else { --$BUSY; $MESSAGE_PROCESSING->msg_handle_delete ($pdu->msg_id); $pdu->status_information ("No response from remote host '%s'", $pdu->hostname); } }) )); } else { --$BUSY; } } sub send_pdu($$$) { my (undef, $pdu, $delay) = @_; ++$BUSY; if ($delay > 0) { my $delay_w; $delay_w = AnyEvent->$timer (after => $delay, cb => sub { undef $delay_w; _send_pdu ($pdu, $pdu->retries); }); return 1; } _send_pdu $pdu, $pdu->retries; 1 } sub activate($) { AnyEvent->one_event while $BUSY; } sub one_event($) { die; } =head1 SEE ALSO L, L, L. =head1 AUTHOR Marc Lehmann http://home.schmorp.de/ =cut 1