net-snmp2 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +18 -1
- data/examples/agent.rb +1 -0
- data/examples/inform.rb +23 -0
- data/examples/trap_handler.rb +15 -0
- data/examples/{v1_trap_session.rb → v1_trap.rb} +2 -7
- data/examples/{v2_trap_session.rb → v2_trap.rb} +2 -7
- data/history.md +17 -0
- data/lib/net-snmp2.rb +1 -1
- data/lib/net/snmp/agent/agent.rb +44 -13
- data/lib/net/snmp/agent/provider.rb +56 -1
- data/lib/net/snmp/agent/provider_dsl.rb +11 -0
- data/lib/net/snmp/debug.rb +10 -19
- data/lib/net/snmp/error.rb +13 -6
- data/lib/net/snmp/listener.rb +2 -4
- data/lib/net/snmp/message.rb +52 -25
- data/lib/net/snmp/mib/mib.rb +2 -1
- data/lib/net/snmp/mib/node.rb +1 -1
- data/lib/net/snmp/oid.rb +5 -2
- data/lib/net/snmp/pdu.rb +15 -10
- data/lib/net/snmp/repl/manager_repl.rb +20 -7
- data/lib/net/snmp/session.rb +78 -45
- data/lib/net/snmp/trap_handler/trap_handler.rb +13 -9
- data/lib/net/snmp/trap_handler/v2_inform_dsl.rb +16 -0
- data/lib/net/snmp/trap_session.rb +64 -42
- data/lib/net/snmp/version.rb +1 -1
- data/lib/net/snmp/wrapper.rb +2 -8
- data/net-snmp2.gemspec +1 -1
- data/spec/README.md +36 -40
- data/spec/async_spec.rb +92 -87
- data/spec/em_spec.rb +3 -3
- data/spec/error_spec.rb +2 -2
- data/spec/fiber_spec.rb +8 -7
- data/spec/mib_spec.rb +8 -8
- data/spec/net-snmp_spec.rb +3 -3
- data/spec/oid_spec.rb +5 -5
- data/spec/spec_helper.rb +5 -0
- data/spec/sync_spec.rb +50 -56
- data/spec/test_agent.rb +150 -0
- data/spec/test_mib.rb +6 -0
- data/spec/thread_spec.rb +4 -4
- data/spec/trap_spec.rb +3 -12
- data/spec/utility_spec.rb +4 -4
- data/spec/wrapper_spec.rb +6 -6
- metadata +11 -6
- data/lib/net/snmp/agent/request_dispatcher.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 764e833cea5160e1dcd90749dc1e34497bc19cd7
|
4
|
+
data.tar.gz: dab11ed0e33675f7d37848b71a9eea33f94584e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9af6ba3d026772a98ee3d08d988859324b53f7e552e938ec400509f59a8fd0598c2716290af3a21b1c1374d8ff81c5b64f3905d21540b17ca5b46edf79b3d67d
|
7
|
+
data.tar.gz: 232ffa5c7ffdbe94a06cae89a545bc8795dfaf9f9ba0011c81edd6c85ebdefaa6ca4ab84bbcf20236aa18c5f2375efe76f48ff3ee93ee63e0b1ecce5c3d6404e
|
data/README.md
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
net-snmp2
|
2
2
|
=========
|
3
3
|
|
4
|
-
|
4
|
+
[](http://badge.fury.io/rb/net-snmp2)
|
5
|
+
|
6
|
+
Just getting started? Checkout the [wiki](https://github.com/jbreeden/net-snmp2/wiki)
|
5
7
|
|
6
8
|
An object oriented Ruby wrapper around the C [netsnmp](http://www.net-snmp.org) libraries.
|
7
9
|
It provides classes for manager sessions, agents, pdus, varbinds, MIB inspection, and more.
|
8
10
|
|
9
11
|
The gem also includes some useful executables including:
|
12
|
+
|
10
13
|
- **net-snmp2**
|
11
14
|
+ An interactive REPL for inspecting the MIB, and acting as an SNMP manager.
|
12
15
|
+ Supports managing multiple agents at once.
|
@@ -18,6 +21,7 @@ The gem also includes some useful executables including:
|
|
18
21
|
Features
|
19
22
|
--------
|
20
23
|
|
24
|
+
* Improved Windows support over original net-snmp gem
|
21
25
|
* Supports SNMP versions 1, 2c, and 3
|
22
26
|
* Supports both synchronous and asynchronous calls
|
23
27
|
* Supports sending of snmpv1 traps and snmpv2 traps/informs using TrapSession
|
@@ -26,3 +30,16 @@ Features
|
|
26
30
|
* MIB support
|
27
31
|
* Convenience methods such as session.walk, session.get_columns, and session.table
|
28
32
|
* SNMP Agent support
|
33
|
+
* Trap handler support
|
34
|
+
|
35
|
+
Notes
|
36
|
+
-----
|
37
|
+
|
38
|
+
- TrapHandler & Agent support doesn't work on default Windows build of net-snmp libraries, due to certain functions not being exported in the dll.
|
39
|
+
+ There is a [net-snmp-clone](https://github.com/jbreeden/net-snmp-clone) repo with the fixes made if you'd like to build this from source.
|
40
|
+
+ Or, you can try my [pre-built DLL](https://github.com/jbreeden/net-snmp-clone/raw/master/win32/bin/release/netsnmp.dll) from the same repo.
|
41
|
+
+ I've submitted this as a [feature request](https://sourceforge.net/p/net-snmp/feature-requests/181/) & patch to the net-snmp team. If it works for you, please go make a comment indicating that on the feature request to help it get pulled into the core library.
|
42
|
+
- Original gem was using a different comment format... I've opted for markdown. The generated rdocs are probably garbage.
|
43
|
+
+ The [wiki](https://github.com/jbreeden/net-snmp2/wiki) has the best documentation for now.
|
44
|
+
+ All the specs are passing, so that's another good place to go for information.
|
45
|
+
+ There is also the built-in net-snmp2 program you can run. This is an interactive Pry shell that is very useful for digging around the code and getting a feel for how to do things.
|
data/examples/agent.rb
CHANGED
data/examples/inform.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << '../lib'
|
2
|
+
require 'net-snmp2'
|
3
|
+
|
4
|
+
# Initialize SNMP and give it a logger
|
5
|
+
Net::SNMP.init
|
6
|
+
Net::SNMP::Debug.logger = Logger.new(STDOUT)
|
7
|
+
Net::SNMP::Debug.logger.level = Logger::INFO
|
8
|
+
|
9
|
+
session = Net::SNMP::TrapSession.open(:peername => 'localhost', :version => '2c', :community => 'public')
|
10
|
+
|
11
|
+
extend Net::SNMP::Debug
|
12
|
+
|
13
|
+
100000.times do |i|
|
14
|
+
time "Inform ##{i}" do
|
15
|
+
puts "Inform #{i}: " + session.inform(
|
16
|
+
oid: '1.3.1.1',
|
17
|
+
uptime: 1000,
|
18
|
+
varbinds: [
|
19
|
+
{oid: '1.3.2.2', value: 'test'}
|
20
|
+
]
|
21
|
+
).to_s
|
22
|
+
end
|
23
|
+
end
|
data/examples/trap_handler.rb
CHANGED
@@ -35,6 +35,21 @@ handler = Net::SNMP::TrapHandler.new do
|
|
35
35
|
Varbinds: #{varbinds.map {|vb| "#{vb.oid.label}(#{vb.oid}) = #{vb.value}"}.join(', ')}
|
36
36
|
EOF
|
37
37
|
end
|
38
|
+
|
39
|
+
inform do
|
40
|
+
info <<-EOF
|
41
|
+
|
42
|
+
|
43
|
+
Got Inform
|
44
|
+
----------
|
45
|
+
|
46
|
+
Trap OID: #{trap_oid}
|
47
|
+
Uptime: #{uptime}
|
48
|
+
Varbinds: #{varbinds.map {|vb| "#{vb.oid.label}(#{vb.oid}) = #{vb.value}"}.join(', ')}
|
49
|
+
EOF
|
50
|
+
|
51
|
+
ok
|
52
|
+
end
|
38
53
|
end
|
39
54
|
|
40
55
|
# If the program gets the interrupt signal, tell the trap handler
|
@@ -6,19 +6,14 @@ Net::SNMP.init
|
|
6
6
|
Net::SNMP::Debug.logger = Logger.new(STDOUT)
|
7
7
|
Net::SNMP::Debug.logger.level = Logger::DEBUG
|
8
8
|
|
9
|
-
puts "Opening session"
|
10
9
|
session = Net::SNMP::TrapSession.open(:peername => 'localhost', :version => '1', :community => 'public')
|
11
10
|
|
12
|
-
puts "Sending trap"
|
13
|
-
|
14
11
|
100000.times do |i|
|
15
|
-
session.trap(
|
12
|
+
puts "#{i + 1}: " + session.trap(
|
16
13
|
enterprise: '1.3.1',
|
17
14
|
trap_type: 6,
|
18
15
|
specific_type: 1,
|
19
16
|
uptime: 1000,
|
20
17
|
agent_addr: '127.0.0.1'
|
21
|
-
)
|
18
|
+
).to_s
|
22
19
|
end
|
23
|
-
|
24
|
-
sleep 20
|
@@ -6,16 +6,11 @@ Net::SNMP.init
|
|
6
6
|
Net::SNMP::Debug.logger = Logger.new(STDOUT)
|
7
7
|
Net::SNMP::Debug.logger.level = Logger::DEBUG
|
8
8
|
|
9
|
-
puts "Opening session"
|
10
9
|
session = Net::SNMP::TrapSession.open(:peername => 'localhost', :version => '2c', :community => 'public')
|
11
10
|
|
12
|
-
puts "Sending trap"
|
13
|
-
|
14
11
|
100000.times do |i|
|
15
|
-
puts session.trap_v2(
|
12
|
+
puts "#{i + 1}: " + session.trap_v2(
|
16
13
|
oid: '1.3.1.1',
|
17
14
|
uptime: 1000
|
18
|
-
)
|
15
|
+
).to_s
|
19
16
|
end
|
20
|
-
|
21
|
-
sleep 20
|
data/history.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History
|
2
|
+
=======
|
3
|
+
|
4
|
+
Version 0.3.1
|
5
|
+
-------------
|
6
|
+
|
7
|
+
- Internal changes
|
8
|
+
+ Adding source_address & source_port to message object
|
9
|
+
* Changes the callback signature for Listener#on_message
|
10
|
+
+ Adding Message#respond method
|
11
|
+
* Used for creating a one-off session for responding to a message
|
12
|
+
and sending a single pdu over it. (Used in TrapHandler & Agent)
|
13
|
+
|
14
|
+
Version 0.3.0
|
15
|
+
-------------
|
16
|
+
|
17
|
+
- First gem release as net-snmp2
|
data/lib/net-snmp2.rb
CHANGED
@@ -21,6 +21,7 @@ require 'logger'
|
|
21
21
|
snmp/trap_handler/trap_handler
|
22
22
|
snmp/trap_handler/v1_trap_dsl
|
23
23
|
snmp/trap_handler/v2_trap_dsl
|
24
|
+
snmp/trap_handler/v2_inform_dsl
|
24
25
|
snmp/mib/mib
|
25
26
|
snmp/mib/node
|
26
27
|
snmp/mib/module
|
@@ -29,7 +30,6 @@ require 'logger'
|
|
29
30
|
snmp/agent/agent
|
30
31
|
snmp/agent/provider
|
31
32
|
snmp/agent/provider_dsl
|
32
|
-
snmp/agent/request_dispatcher
|
33
33
|
snmp/message
|
34
34
|
).each do |f|
|
35
35
|
require "#{File.dirname(__FILE__)}/net/#{f}"
|
data/lib/net/snmp/agent/agent.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
module Net::SNMP
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# + Dispatches the messages to (sub) Agents
|
7
|
-
# + Serializes the response from the subagents and sends it to the caller
|
3
|
+
# Agents delegate messages from the Net::SNMP::Listener to
|
4
|
+
# providers, which supply the actual responses to the varbinds
|
5
|
+
# in the request. See Net::SNMP::ProviderDsl
|
8
6
|
|
9
7
|
class Agent
|
10
8
|
include Debug
|
@@ -19,10 +17,16 @@ module Net::SNMP
|
|
19
17
|
@listener.on_message(&method(:process_message))
|
20
18
|
end
|
21
19
|
|
20
|
+
# This method is called with a block to define a provider
|
21
|
+
# for some subtree of the MIB. When a request comes in with varbinds
|
22
|
+
# in that providers subtree, the provider's handlers will be called
|
23
|
+
# to generate the varbind to send back in the response PDU.
|
24
|
+
#
|
25
|
+
# Arguments
|
26
|
+
#
|
27
|
+
# - oid: The root OID of the MIB subtree this provider is responsible for.
|
28
|
+
# - &block: A block, to be instance_evaled on the new Provider.
|
22
29
|
def provide(oid = :all, &block)
|
23
|
-
# Need a trailing dot on the oid so we can avoid
|
24
|
-
# considering 1.3.22 a child of 1.3.2
|
25
|
-
oid = (oid.to_sym == :all || oid.end_with?('.')) ? oid : "#{oid}."
|
26
30
|
provider = Provider.new(oid)
|
27
31
|
provider.instance_eval(&block)
|
28
32
|
|
@@ -35,13 +39,40 @@ module Net::SNMP
|
|
35
39
|
|
36
40
|
private
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
# The callback given to the Listener object for handling an SNMP message.
|
43
|
+
# Calls `dispatch`, then sends the response PDU, if one is returned.
|
44
|
+
def process_message(message)
|
45
|
+
# TODO: May want to ignore some messages (say, if the community string is wrong)
|
46
|
+
message.respond(dispatch(message))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Collects responses for the given message from the available providers
|
50
|
+
def dispatch(message)
|
51
|
+
response_pdu = message.make_response_pdu
|
52
|
+
context = ProviderDsl.new
|
53
|
+
context.message = message
|
54
|
+
context.response_pdu = response_pdu
|
55
|
+
message.pdu.varbinds.each_with_index do |vb, index|
|
56
|
+
context.varbind = vb
|
57
|
+
provider = providers.find { |p| p.provides?(vb.oid) }
|
58
|
+
if provider
|
59
|
+
if message.pdu.command == Constants::SNMP_MSG_GETBULK && index < message.pdu.non_repeaters
|
60
|
+
handler = provider.handler_for(Constants::SNMP_MSG_GETNEXT)
|
61
|
+
else
|
62
|
+
handler = provider.handler_for(message)
|
63
|
+
end
|
64
|
+
if handler
|
65
|
+
context.instance_exec(&handler)
|
66
|
+
else
|
67
|
+
warn "No handler for command: #{message.pdu.command} @ #{vb.oid}"
|
68
|
+
context.no_such_object
|
69
|
+
end
|
70
|
+
else
|
71
|
+
warn "No provider for oid: #{vb.oid}"
|
72
|
+
context.no_such_object
|
43
73
|
end
|
44
74
|
end
|
75
|
+
response_pdu
|
45
76
|
end
|
46
77
|
|
47
78
|
end
|
@@ -5,6 +5,39 @@ module Net::SNMP
|
|
5
5
|
# of the subtree provided, and handlers for the various request types.
|
6
6
|
# The handlers are executed for each varbind of the incoming message
|
7
7
|
# individually in the context of a ProviderDsl object.
|
8
|
+
#
|
9
|
+
# Clients do not create Providers directly. Instead, they call `provide` on
|
10
|
+
# an Agent, passing in a block. Within the block they use the `get`, `get_next`,
|
11
|
+
# `get_bulk`, and `set` methods to configure handlers for these request types.
|
12
|
+
# Within these handlers, the DSL methods from the ProviderDsl class can be
|
13
|
+
# used to inspect the current request.
|
14
|
+
#
|
15
|
+
# Example
|
16
|
+
#
|
17
|
+
# require 'net-snmp2'
|
18
|
+
# agent = Net::SNMP::Agent.new
|
19
|
+
# agent.provide :all do
|
20
|
+
#
|
21
|
+
# get do
|
22
|
+
# reply get_value_somehow(oid)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# set do
|
26
|
+
# reply set_value_somehow(oid)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# get_next do
|
30
|
+
# reply get_next_value_somehow(oid)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# get_bulk do
|
34
|
+
# (0..max_repetitions).each do |i|
|
35
|
+
# add get_bulk_vlue_somehow(oid, i)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# end
|
40
|
+
# agent.listen(161)
|
8
41
|
|
9
42
|
class Provider
|
10
43
|
attr_accessor :oid,
|
@@ -13,10 +46,26 @@ module Net::SNMP
|
|
13
46
|
:get_next_handler,
|
14
47
|
:get_bulk_handler
|
15
48
|
|
49
|
+
# Creates a new Provider with `oid` as the root of its subtree
|
16
50
|
def initialize(oid)
|
17
|
-
|
51
|
+
if oid.kind_of?(Symbol)
|
52
|
+
unless oid == :all
|
53
|
+
raise "Cannot provide symbol '#{oid}'. (Did you mean to use :all?)"
|
54
|
+
end
|
55
|
+
@oid = oid
|
56
|
+
else
|
57
|
+
# Guarantee OID is in numeric form
|
58
|
+
@oid = OID.new(oid).to_s
|
59
|
+
end
|
18
60
|
end
|
19
61
|
|
62
|
+
# Gets the handler for the given command type from this provider.
|
63
|
+
#
|
64
|
+
# Arguments
|
65
|
+
#
|
66
|
+
# - command
|
67
|
+
# + As an integer, specifies the command type a handler is needed for.
|
68
|
+
# + May also be a Message or PDU object, from which the command type is read.
|
20
69
|
def handler_for(command)
|
21
70
|
# User might be tempted to just pass in the message, or pdu,
|
22
71
|
# if so, just pluck the command off of it.
|
@@ -40,6 +89,12 @@ module Net::SNMP
|
|
40
89
|
end
|
41
90
|
end
|
42
91
|
|
92
|
+
# Returns a boolean indicating whether this provider provides
|
93
|
+
# the given `oid`
|
94
|
+
def provides?(oid)
|
95
|
+
self.oid == :all || oid.to_s =~ %r[#{self.oid.to_s}(\.|$)]
|
96
|
+
end
|
97
|
+
|
43
98
|
[:get, :set, :get_next, :get_bulk].each do |request_type|
|
44
99
|
self.class_eval %Q[
|
45
100
|
def #{request_type}(&proc)
|
@@ -38,6 +38,11 @@ module Net::SNMP
|
|
38
38
|
varbind.oid
|
39
39
|
end
|
40
40
|
|
41
|
+
# The MIB variable name of the current varbind
|
42
|
+
def variable
|
43
|
+
oid.label
|
44
|
+
end
|
45
|
+
|
41
46
|
# The OID of the current varbind being processed as a string.
|
42
47
|
def oid_str
|
43
48
|
varbind.oid.to_s
|
@@ -101,6 +106,12 @@ module Net::SNMP
|
|
101
106
|
add_varbind(oid: oid, type: Constants::SNMP_NOSUCHINSTANCE)
|
102
107
|
end
|
103
108
|
|
109
|
+
# Adds a varbind to the response indicating that the END OF MIB has been reached
|
110
|
+
def end_of_mib
|
111
|
+
oid ||= varbind.oid
|
112
|
+
add_varbind(oid: oid, type: Constants::SNMP_ENDOFMIBVIEW)
|
113
|
+
end
|
114
|
+
|
104
115
|
# Adds a varbind to the response PDU.
|
105
116
|
# MUST use this method (or one that delegates to it)
|
106
117
|
# to set response varbinds on the response_pdu to make sure
|
data/lib/net/snmp/debug.rb
CHANGED
@@ -5,31 +5,22 @@ module Debug
|
|
5
5
|
attr_accessor :logger
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Debug.logger.warn msg if Debug.logger
|
18
|
-
end
|
19
|
-
|
20
|
-
def error(msg)
|
21
|
-
Debug.logger.error msg if Debug.logger
|
22
|
-
end
|
23
|
-
|
24
|
-
def fatal(msg)
|
25
|
-
Debug.logger.fatal msg if Debug.logger
|
8
|
+
[:debug, :info, :warn, :error, :fatal].each do |log_level|
|
9
|
+
self.module_eval %Q{
|
10
|
+
def #{log_level}(msg = nil, &block)
|
11
|
+
if Debug.logger && (Debug.logger.level <= Logger::#{log_level.upcase})
|
12
|
+
Debug.logger.send(:#{log_level}, msg)
|
13
|
+
block[] if block_given?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
}
|
26
17
|
end
|
27
18
|
|
28
19
|
def time(label, &block)
|
29
20
|
t_start = Time.now
|
30
21
|
block[]
|
31
22
|
t_end = Time.now
|
32
|
-
|
23
|
+
debug "#{label}: #{(t_end - t_start)*1000}ms"
|
33
24
|
end
|
34
25
|
|
35
26
|
def print_packet(packet)
|
data/lib/net/snmp/error.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Net
|
2
2
|
module SNMP
|
3
3
|
class Error < RuntimeError
|
4
|
+
include Debug
|
5
|
+
|
4
6
|
attr_accessor :status, :errno, :snmp_err, :snmp_msg
|
5
7
|
def initialize(opts = {})
|
6
8
|
@status = opts[:status]
|
@@ -14,12 +16,17 @@ module Net
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def print
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
message = <<-EOF
|
20
|
+
|
21
|
+
SNMP Error: #{self.class.to_s}
|
22
|
+
message = #{message}
|
23
|
+
status = #{@status}
|
24
|
+
errno = #{@errno}
|
25
|
+
snmp_err = #{@snmp_err}
|
26
|
+
snmp_msg = #{@snmp_msg}
|
27
|
+
EOF
|
28
|
+
|
29
|
+
error(message.gsub /^\s*/, '')
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
data/lib/net/snmp/listener.rb
CHANGED
@@ -37,13 +37,11 @@ module Net::SNMP
|
|
37
37
|
#
|
38
38
|
# The block provided will be called back for each message as follows:
|
39
39
|
#
|
40
|
-
# block[message
|
40
|
+
# block[message]
|
41
41
|
#
|
42
42
|
# Where
|
43
43
|
#
|
44
44
|
# - `message` is the parsed Net::SNMP::Message object
|
45
|
-
# - `from_address` is a string representing the address of the host sending the request
|
46
|
-
# - `from_port` is the port the host sent the request from
|
47
45
|
def on_message(&block)
|
48
46
|
@callback = block
|
49
47
|
end
|
@@ -62,7 +60,7 @@ module Net::SNMP
|
|
62
60
|
return if @killed
|
63
61
|
time "Message Processing" do
|
64
62
|
message = Message.parse(@packet)
|
65
|
-
@callback[message
|
63
|
+
@callback[message] if @callback
|
66
64
|
end
|
67
65
|
rescue Timeout::Error => timeout
|
68
66
|
next
|