right_agent 0.10.13 → 0.13.5
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.
- data/lib/right_agent.rb +2 -0
- data/lib/right_agent/actor.rb +45 -10
- data/lib/right_agent/actor_registry.rb +5 -5
- data/lib/right_agent/actors/agent_manager.rb +4 -4
- data/lib/right_agent/agent.rb +97 -37
- data/lib/right_agent/agent_tag_manager.rb +1 -2
- data/lib/right_agent/command/command_io.rb +1 -3
- data/lib/right_agent/command/command_runner.rb +9 -3
- data/lib/right_agent/dispatched_cache.rb +110 -0
- data/lib/right_agent/dispatcher.rb +119 -180
- data/lib/right_agent/history.rb +136 -0
- data/lib/right_agent/log.rb +6 -3
- data/lib/right_agent/monkey_patches/ruby_patch.rb +0 -1
- data/lib/right_agent/pid_file.rb +1 -1
- data/lib/right_agent/platform.rb +2 -2
- data/lib/right_agent/platform/linux.rb +8 -1
- data/lib/right_agent/platform/windows.rb +1 -1
- data/lib/right_agent/sender.rb +57 -41
- data/right_agent.gemspec +4 -4
- data/spec/actor_registry_spec.rb +7 -8
- data/spec/actor_spec.rb +87 -24
- data/spec/agent_spec.rb +107 -8
- data/spec/command/command_runner_spec.rb +12 -1
- data/spec/dispatched_cache_spec.rb +142 -0
- data/spec/dispatcher_spec.rb +110 -129
- data/spec/history_spec.rb +234 -0
- data/spec/idempotent_request_spec.rb +1 -1
- data/spec/log_spec.rb +15 -0
- data/spec/operation_result_spec.rb +4 -2
- data/spec/platform/darwin_spec.rb +13 -0
- data/spec/platform/linux_spec.rb +38 -0
- data/spec/platform/platform_spec.rb +46 -51
- data/spec/platform/windows_spec.rb +13 -0
- data/spec/sender_spec.rb +81 -38
- metadata +12 -9
- data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +0 -45
- data/spec/platform/darwin.rb +0 -11
- data/spec/platform/linux.rb +0 -23
- data/spec/platform/windows.rb +0 -11
@@ -9,8 +9,6 @@
|
|
9
9
|
# License Agreement between RightScale.com, Inc. and
|
10
10
|
# the licensee.
|
11
11
|
|
12
|
-
require 'singleton'
|
13
|
-
|
14
12
|
module RightScale
|
15
13
|
|
16
14
|
# Class which allows listening for data and sending data on sockets
|
@@ -18,7 +16,7 @@ module RightScale
|
|
18
16
|
# the agent without having to go through RabbitMQ.
|
19
17
|
class CommandIO
|
20
18
|
|
21
|
-
include
|
19
|
+
include RightSupport::Ruby::EasySingleton
|
22
20
|
|
23
21
|
# ensure uniqueness of handler to avoid confusion.
|
24
22
|
raise "#{ServerInputHandler.name} is already defined" if defined?(ServerInputHandler)
|
@@ -24,7 +24,7 @@ module RightScale
|
|
24
24
|
|
25
25
|
# Run commands exposed by an agent.
|
26
26
|
# External processes can send commands through a socket with the specified port.
|
27
|
-
# Command runner accepts connections and
|
27
|
+
# Command runner accepts connections and unserializes commands using YAML.
|
28
28
|
# Each command is expected to be a hash containing the :name and :options keys.
|
29
29
|
class CommandRunner
|
30
30
|
class << self
|
@@ -42,6 +42,8 @@ module RightScale
|
|
42
42
|
# increment and retry if port already taken
|
43
43
|
# identity(String):: Agent identity
|
44
44
|
# commands(Hash):: Commands exposed by agent
|
45
|
+
# fiber_pool(NB::FiberPool):: Pool of initialized fibers to be used for executing
|
46
|
+
# received commands in non-blocking fashion
|
45
47
|
#
|
46
48
|
# === Block
|
47
49
|
# If a block is provided, this method will yield after all setup has been completed,
|
@@ -54,7 +56,7 @@ module RightScale
|
|
54
56
|
#
|
55
57
|
# === Raise
|
56
58
|
# (RightScale::Exceptions::Application):: If +start+ has already been called and +stop+ hasn't since
|
57
|
-
def self.start(socket_port, identity, commands)
|
59
|
+
def self.start(socket_port, identity, commands, fiber_pool = nil)
|
58
60
|
cmd_options = nil
|
59
61
|
@listen_port = socket_port
|
60
62
|
|
@@ -65,7 +67,11 @@ module RightScale
|
|
65
67
|
if cmd_cookie == @cookie
|
66
68
|
cmd_name = c[:name].to_sym
|
67
69
|
if commands.include?(cmd_name)
|
68
|
-
|
70
|
+
if fiber_pool
|
71
|
+
fiber_pool.spawn { commands[cmd_name].call(c, conn) }
|
72
|
+
else
|
73
|
+
commands[cmd_name].call(c, conn)
|
74
|
+
end
|
69
75
|
else
|
70
76
|
Log.warning("Unknown command '#{cmd_name}', known commands: #{commands.keys.join(', ')}")
|
71
77
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
# Cache for requests that have been dispatched recently
|
26
|
+
# This cache is intended for use in checking for duplicate requests
|
27
|
+
# Since this is a local cache, it is not usable for requests received from a shared queue
|
28
|
+
class DispatchedCache
|
29
|
+
|
30
|
+
# Maximum number of seconds to retain a dispatched request in cache
|
31
|
+
# This must be greater than the maximum possible retry timeout to avoid
|
32
|
+
# duplicate execution of a request
|
33
|
+
MAX_AGE = 12 * 60 * 60
|
34
|
+
|
35
|
+
# Initialize cache
|
36
|
+
#
|
37
|
+
# === Parameters
|
38
|
+
# identity(String):: Serialized identity of agent
|
39
|
+
def initialize(identity)
|
40
|
+
@identity = identity
|
41
|
+
@cache = {}
|
42
|
+
@lru = []
|
43
|
+
@max_age = MAX_AGE
|
44
|
+
end
|
45
|
+
|
46
|
+
# Store dispatched request token in cache unless from shared queue
|
47
|
+
#
|
48
|
+
# === Parameters
|
49
|
+
# token(String):: Generated message identifier
|
50
|
+
# shared_queue(String|nil):: Name of shared queue if being dispatched from a shared queue
|
51
|
+
#
|
52
|
+
# === Return
|
53
|
+
# true:: Always return true
|
54
|
+
def store(token, shared_queue)
|
55
|
+
if token && shared_queue.nil?
|
56
|
+
now = Time.now.to_i
|
57
|
+
if @cache.has_key?(token)
|
58
|
+
@cache[token] = now
|
59
|
+
@lru.push(@lru.delete(token))
|
60
|
+
else
|
61
|
+
@cache[token] = now
|
62
|
+
@lru.push(token)
|
63
|
+
@cache.delete(@lru.shift) while (now - @cache[@lru.first]) > @max_age
|
64
|
+
end
|
65
|
+
end
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
# Determine whether request has already been serviced
|
70
|
+
#
|
71
|
+
# === Parameters
|
72
|
+
# token(String):: Generated message identifier
|
73
|
+
#
|
74
|
+
# === Return
|
75
|
+
# (String|nil):: Identity of agent that already serviced request, or nil if none
|
76
|
+
def serviced_by(token)
|
77
|
+
if @cache[token]
|
78
|
+
@cache[token] = Time.now.to_i
|
79
|
+
@lru.push(@lru.delete(token))
|
80
|
+
@identity
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get local cache statistics
|
85
|
+
#
|
86
|
+
# === Return
|
87
|
+
# stats(Hash|nil):: Current statistics, or nil if cache empty
|
88
|
+
# "local total"(Integer):: Total number in local cache, or nil if none
|
89
|
+
# "local max age"(String):: Time since oldest local cache entry created or updated
|
90
|
+
def stats
|
91
|
+
if (s = size) > 0
|
92
|
+
now = Time.now.to_i
|
93
|
+
{
|
94
|
+
"local total" => s,
|
95
|
+
"local max age" => RightSupport::Stats.elapsed(now - @cache[@lru.first])
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Get local cache size
|
101
|
+
#
|
102
|
+
# === Return
|
103
|
+
# (Integer):: Number of cache entries
|
104
|
+
def size
|
105
|
+
@cache.size
|
106
|
+
end
|
107
|
+
|
108
|
+
end # DispatchedCache
|
109
|
+
|
110
|
+
end # RightScale
|
@@ -28,82 +28,6 @@ module RightScale
|
|
28
28
|
# Response queue name
|
29
29
|
RESPONSE_QUEUE = "response"
|
30
30
|
|
31
|
-
# Cache for requests that have been dispatched recently
|
32
|
-
# This cache is intended for use in checking for duplicate requests
|
33
|
-
class Dispatched
|
34
|
-
|
35
|
-
# Maximum number of seconds to retain a dispatched request in cache
|
36
|
-
# This must be greater than the maximum possible retry timeout to avoid
|
37
|
-
# duplicate execution of a request
|
38
|
-
MAX_AGE = 12 * 60 * 60
|
39
|
-
|
40
|
-
# Initialize cache
|
41
|
-
def initialize
|
42
|
-
@cache = {}
|
43
|
-
@lru = []
|
44
|
-
end
|
45
|
-
|
46
|
-
# Store dispatched request token in cache
|
47
|
-
#
|
48
|
-
# === Parameters
|
49
|
-
# token(String):: Generated message identifier
|
50
|
-
#
|
51
|
-
# === Return
|
52
|
-
# true:: Always return true
|
53
|
-
def store(token)
|
54
|
-
now = Time.now.to_i
|
55
|
-
if @cache.has_key?(token)
|
56
|
-
@cache[token] = now
|
57
|
-
@lru.push(@lru.delete(token))
|
58
|
-
else
|
59
|
-
@cache[token] = now
|
60
|
-
@lru.push(token)
|
61
|
-
@cache.delete(@lru.shift) while (now - @cache[@lru.first]) > MAX_AGE
|
62
|
-
end
|
63
|
-
true
|
64
|
-
end
|
65
|
-
|
66
|
-
# Fetch request
|
67
|
-
#
|
68
|
-
# === Parameters
|
69
|
-
# token(String):: Generated message identifier
|
70
|
-
#
|
71
|
-
# === Return
|
72
|
-
# (Boolean):: true if request has been dispatched, otherwise false
|
73
|
-
def fetch(token)
|
74
|
-
if @cache[token]
|
75
|
-
@cache[token] = Time.now.to_i
|
76
|
-
@lru.push(@lru.delete(token))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Get cache size
|
81
|
-
#
|
82
|
-
# === Return
|
83
|
-
# (Integer):: Number of cache entries
|
84
|
-
def size
|
85
|
-
@cache.size
|
86
|
-
end
|
87
|
-
|
88
|
-
# Get cache statistics
|
89
|
-
#
|
90
|
-
# === Return
|
91
|
-
# stats(Hash|nil):: Current statistics, or nil if cache empty
|
92
|
-
# "total"(Integer):: Total number in cache, or nil if none
|
93
|
-
# "oldest"(Integer):: Number of seconds since oldest cache entry created or updated
|
94
|
-
# "youngest"(Integer):: Number of seconds since youngest cache entry created or updated
|
95
|
-
def stats
|
96
|
-
if size > 0
|
97
|
-
{
|
98
|
-
"total" => size,
|
99
|
-
"oldest age" => size > 0 ? Time.now.to_i - @cache[@lru.first] : 0,
|
100
|
-
"youngest age" => size > 0 ? Time.now.to_i - @cache[@lru.last] : 0
|
101
|
-
}
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
end # Dispatched
|
106
|
-
|
107
31
|
# (ActorRegistry) Registry for actors
|
108
32
|
attr_reader :registry
|
109
33
|
|
@@ -120,13 +44,13 @@ module RightScale
|
|
120
44
|
#
|
121
45
|
# === Parameters
|
122
46
|
# agent(Agent):: Agent using this dispatcher; uses its identity, broker, registry, and following options:
|
123
|
-
# :dup_check(Boolean):: Whether to check for and reject duplicate requests, e.g., due to retries,
|
124
|
-
# but only for requests that are dispatched from non-shared queues
|
125
47
|
# :secure(Boolean):: true indicates to use Security features of RabbitMQ to restrict agents to themselves
|
126
48
|
# :single_threaded(Boolean):: true indicates to run all operations in one thread; false indicates
|
127
49
|
# to do requested work on event machine defer thread and all else, such as pings on main thread
|
128
50
|
# :threadpool_size(Integer):: Number of threads in event machine thread pool
|
129
|
-
|
51
|
+
# dispatched_cache(DispatchedCache|nil):: Cache for dispatched requests that is used for detecting
|
52
|
+
# duplicate requests, or nil if duplicate checking is disabled
|
53
|
+
def initialize(agent, dispatched_cache = nil)
|
130
54
|
@agent = agent
|
131
55
|
@broker = @agent.broker
|
132
56
|
@registry = @agent.registry
|
@@ -134,116 +58,131 @@ module RightScale
|
|
134
58
|
options = @agent.options
|
135
59
|
@secure = options[:secure]
|
136
60
|
@single_threaded = options[:single_threaded]
|
137
|
-
@dup_check = options[:dup_check]
|
138
61
|
@pending_dispatches = 0
|
139
62
|
@em = EM
|
140
63
|
@em.threadpool_size = (options[:threadpool_size] || 20).to_i
|
141
64
|
reset_stats
|
142
65
|
|
143
|
-
# Only access
|
144
|
-
@
|
66
|
+
# Only access this cache from primary thread
|
67
|
+
@dispatched_cache = dispatched_cache
|
145
68
|
end
|
146
69
|
|
147
70
|
# Dispatch request to appropriate actor for servicing
|
148
71
|
# Handle returning of result to requester including logging any exceptions
|
149
72
|
# Reject requests whose TTL has expired or that are duplicates of work already dispatched
|
150
|
-
# but do not do duplicate checking if being dispatched from a shared queue
|
151
73
|
# Work is done in background defer thread if single threaded option is false
|
74
|
+
# Acknowledge request after actor has responded
|
152
75
|
#
|
153
76
|
# === Parameters
|
154
77
|
# request(Request|Push):: Packet containing request
|
155
|
-
#
|
78
|
+
# header(AMQP::Frame::Header|nil):: Request header containing ack control
|
79
|
+
# shared_queue(String|nil):: Name of shared queue if being dispatched from a shared queue
|
156
80
|
#
|
157
81
|
# === Return
|
158
|
-
#
|
159
|
-
def dispatch(request,
|
160
|
-
|
161
|
-
|
162
|
-
prefix, method = request.type.split('/')[1..-1]
|
163
|
-
method ||= :index
|
164
|
-
actor = @registry.actor_for(prefix)
|
165
|
-
token = request.token
|
166
|
-
received_at = @requests.update(method, (token if request.kind_of?(Request)))
|
167
|
-
if actor.nil?
|
168
|
-
Log.error("No actor for dispatching request <#{request.token}> of type #{request.type}")
|
169
|
-
return nil
|
170
|
-
end
|
82
|
+
# (Result|nil):: Result from dispatched request, nil if not dispatched because dup or stale
|
83
|
+
def dispatch(request, header = nil, shared_queue = nil)
|
84
|
+
begin
|
85
|
+
ack_deferred = false
|
171
86
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
end
|
183
|
-
result = Result.new(token, request.reply_to, non_delivery, @identity, request.from, request.tries, request.persistent)
|
184
|
-
exchange = {:type => :queue, :name => RESPONSE_QUEUE, :options => {:durable => true, :no_declare => @secure}}
|
185
|
-
@broker.publish(exchange, result, :persistent => true, :mandatory => true)
|
87
|
+
# Determine which actor this request is for
|
88
|
+
prefix, method = request.type.split('/')[1..-1]
|
89
|
+
method ||= :index
|
90
|
+
method = method.to_sym
|
91
|
+
actor = @registry.actor_for(prefix)
|
92
|
+
token = request.token
|
93
|
+
received_at = @requests.update(method, (token if request.kind_of?(Request)))
|
94
|
+
if actor.nil?
|
95
|
+
Log.error("No actor for dispatching request <#{token}> of type #{request.type}")
|
96
|
+
return nil
|
186
97
|
end
|
187
|
-
|
188
|
-
end
|
98
|
+
method_idempotent = actor.class.idempotent?(method)
|
189
99
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
100
|
+
# Reject this request if its TTL has expired
|
101
|
+
if (expires_at = request.expires_at) && expires_at > 0 && received_at.to_i >= expires_at
|
102
|
+
@rejects.update("expired (#{method})")
|
103
|
+
Log.info("REJECT EXPIRED <#{token}> from #{request.from} TTL #{RightSupport::Stats.elapsed(received_at.to_i - expires_at)} ago")
|
104
|
+
if request.is_a?(Request)
|
105
|
+
# For agents that do not know about non-delivery, use error result
|
106
|
+
non_delivery = if request.recv_version < 13
|
107
|
+
OperationResult.error("Could not deliver request (#{OperationResult::TTL_EXPIRATION})")
|
108
|
+
else
|
109
|
+
OperationResult.non_delivery(OperationResult::TTL_EXPIRATION)
|
110
|
+
end
|
111
|
+
result = Result.new(token, request.reply_to, non_delivery, @identity, request.from, request.tries, request.persistent)
|
112
|
+
exchange = {:type => :queue, :name => RESPONSE_QUEUE, :options => {:durable => true, :no_declare => @secure}}
|
113
|
+
@broker.publish(exchange, result, :persistent => true, :mandatory => true)
|
114
|
+
end
|
195
115
|
return nil
|
196
116
|
end
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
117
|
+
|
118
|
+
# Reject this request if it is a duplicate
|
119
|
+
if !method_idempotent && @dispatched_cache
|
120
|
+
if by = @dispatched_cache.serviced_by(token)
|
121
|
+
@rejects.update("duplicate (#{method})")
|
122
|
+
Log.info("REJECT DUP <#{token}> serviced by #{by == @identity ? 'self' : by}")
|
201
123
|
return nil
|
202
124
|
end
|
125
|
+
request.tries.each do |t|
|
126
|
+
if by = @dispatched_cache.serviced_by(t)
|
127
|
+
@rejects.update("retry duplicate (#{method})")
|
128
|
+
Log.info("REJECT RETRY DUP <#{token}> of <#{t}> serviced by #{by == @identity ? 'self' : by}")
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
end
|
203
132
|
end
|
204
|
-
end
|
205
133
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
134
|
+
# Proc for performing request in actor
|
135
|
+
operation = lambda do
|
136
|
+
begin
|
137
|
+
@pending_dispatches += 1
|
138
|
+
@last_request_dispatch_time = received_at.to_i
|
139
|
+
@dispatched_cache.store(token, shared_queue) if !method_idempotent && @dispatched_cache
|
140
|
+
if actor.method(method).arity.abs == 1
|
141
|
+
actor.__send__(method, request.payload)
|
142
|
+
else
|
143
|
+
actor.__send__(method, request.payload, request)
|
144
|
+
end
|
145
|
+
rescue Exception => e
|
146
|
+
@pending_dispatches = [@pending_dispatches - 1, 0].max
|
147
|
+
OperationResult.error(handle_exception(actor, method, request, e))
|
216
148
|
end
|
217
|
-
rescue Exception => e
|
218
|
-
@pending_dispatches = [@pending_dispatches - 1, 0].max
|
219
|
-
handle_exception(actor, method, request, e)
|
220
149
|
end
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
150
|
+
|
151
|
+
# Proc for sending response
|
152
|
+
callback = lambda do |r|
|
153
|
+
begin
|
154
|
+
if request.kind_of?(Request)
|
155
|
+
duration = @requests.finish(received_at, token)
|
156
|
+
r = Result.new(token, request.reply_to, r, @identity, request.from, request.tries, request.persistent, duration)
|
157
|
+
exchange = {:type => :queue, :name => RESPONSE_QUEUE, :options => {:durable => true, :no_declare => @secure}}
|
158
|
+
@broker.publish(exchange, r, :persistent => true, :mandatory => true, :log_filter => [:tries, :persistent, :duration])
|
159
|
+
end
|
160
|
+
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
161
|
+
Log.error("Failed to publish result of dispatched request #{request.trace}", e)
|
162
|
+
rescue Exception => e
|
163
|
+
Log.error("Failed to publish result of dispatched request #{request.trace}", e, :trace)
|
164
|
+
@exceptions.track("publish response", e)
|
165
|
+
ensure
|
166
|
+
header.ack if header
|
167
|
+
@pending_dispatches = [@pending_dispatches - 1, 0].max
|
232
168
|
end
|
233
|
-
|
234
|
-
Log.error("Failed to publish result of dispatched request #{request.trace}", e)
|
235
|
-
rescue Exception => e
|
236
|
-
Log.error("Failed to publish result of dispatched request #{request.trace}", e, :trace)
|
237
|
-
@exceptions.track("publish response", e)
|
169
|
+
r # For unit tests
|
238
170
|
end
|
239
|
-
r # For unit tests
|
240
|
-
end
|
241
171
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
172
|
+
# Process request and send response, if any
|
173
|
+
begin
|
174
|
+
ack_deferred = true
|
175
|
+
if @single_threaded
|
176
|
+
@em.next_tick { callback.call(operation.call) }
|
177
|
+
else
|
178
|
+
@em.defer(operation, callback)
|
179
|
+
end
|
180
|
+
rescue Exception
|
181
|
+
header.ack if header
|
182
|
+
raise
|
183
|
+
end
|
184
|
+
ensure
|
185
|
+
header.ack unless ack_deferred || header.nil?
|
247
186
|
end
|
248
187
|
end
|
249
188
|
|
@@ -252,7 +191,7 @@ module RightScale
|
|
252
191
|
# === Return
|
253
192
|
# (Integer|nil):: Age in seconds of youngest dispatch, or nil if none
|
254
193
|
def dispatch_age
|
255
|
-
|
194
|
+
Time.now.to_i - @last_request_dispatch_time if @last_request_dispatch_time && @pending_dispatches > 0
|
256
195
|
end
|
257
196
|
|
258
197
|
# Get dispatcher statistics
|
@@ -262,7 +201,7 @@ module RightScale
|
|
262
201
|
#
|
263
202
|
# === Return
|
264
203
|
# stats(Hash):: Current statistics:
|
265
|
-
# "
|
204
|
+
# "dispatched cache"(Hash|nil):: Number of dispatched requests cached and age of youngest and oldest,
|
266
205
|
# or nil if empty
|
267
206
|
# "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
|
268
207
|
# "total"(Integer):: Total for category
|
@@ -282,12 +221,12 @@ module RightScale
|
|
282
221
|
}
|
283
222
|
end
|
284
223
|
stats = {
|
285
|
-
"
|
286
|
-
"exceptions"
|
287
|
-
"pending"
|
288
|
-
"rejects"
|
289
|
-
"requests"
|
290
|
-
"response time"
|
224
|
+
"dispatched cache" => (@dispatched_cache.stats if @dispatched_cache),
|
225
|
+
"exceptions" => @exceptions.stats,
|
226
|
+
"pending" => pending,
|
227
|
+
"rejects" => @rejects.all,
|
228
|
+
"requests" => @requests.all,
|
229
|
+
"response time" => @requests.avg_duration
|
291
230
|
}
|
292
231
|
reset_stats if reset
|
293
232
|
stats
|
@@ -311,30 +250,30 @@ module RightScale
|
|
311
250
|
#
|
312
251
|
# === Parameters
|
313
252
|
# actor(Actor):: Actor that failed to process request
|
314
|
-
# method(
|
253
|
+
# method(Symbol):: Name of actor method being dispatched to
|
315
254
|
# request(Packet):: Packet that dispatcher is acting upon
|
316
|
-
#
|
255
|
+
# exception(Exception):: Exception that was raised
|
317
256
|
#
|
318
257
|
# === Return
|
319
|
-
#
|
320
|
-
def handle_exception(actor, method, request,
|
321
|
-
error =
|
322
|
-
Log.error(error)
|
258
|
+
# (String):: Error description for this exception
|
259
|
+
def handle_exception(actor, method, request, exception)
|
260
|
+
error = "Could not handle #{request.type} request"
|
261
|
+
Log.error(error, exception, :trace)
|
323
262
|
begin
|
324
263
|
if actor && actor.class.exception_callback
|
325
264
|
case actor.class.exception_callback
|
326
265
|
when Symbol, String
|
327
|
-
actor.send(actor.class.exception_callback, method
|
266
|
+
actor.send(actor.class.exception_callback, method, request, exception)
|
328
267
|
when Proc
|
329
|
-
actor.instance_exec(method
|
268
|
+
actor.instance_exec(method, request, exception, &actor.class.exception_callback)
|
330
269
|
end
|
331
270
|
end
|
332
|
-
@exceptions.track(request.type,
|
333
|
-
rescue Exception =>
|
334
|
-
Log.error("Failed handling error for #{request.type}",
|
335
|
-
@exceptions.track(request.type,
|
271
|
+
@exceptions.track(request.type, exception)
|
272
|
+
rescue Exception => e
|
273
|
+
Log.error("Failed handling error for #{request.type}", e, :trace)
|
274
|
+
@exceptions.track(request.type, e) rescue nil
|
336
275
|
end
|
337
|
-
error
|
276
|
+
Log.format(error, exception)
|
338
277
|
end
|
339
278
|
|
340
279
|
end # Dispatcher
|