right_agent 0.5.1 → 0.5.10
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 +3 -13
- data/lib/right_agent/actors/agent_manager.rb +78 -4
- data/lib/right_agent/agent.rb +81 -4
- data/lib/right_agent/agent_config.rb +17 -1
- data/lib/right_agent/agent_tags_manager.rb +2 -2
- data/lib/right_agent/broker_client.rb +32 -34
- data/lib/right_agent/command/agent_manager_commands.rb +16 -0
- data/lib/right_agent/command/command_constants.rb +0 -9
- data/lib/right_agent/dispatcher.rb +6 -3
- data/lib/right_agent/ha_broker_client.rb +63 -14
- data/lib/right_agent/log.rb +1 -1
- data/lib/right_agent/minimal.rb +43 -0
- data/lib/right_agent/monkey_patches/amqp_patch.rb +91 -182
- data/lib/right_agent/packets.rb +10 -5
- data/lib/right_agent/platform.rb +8 -0
- data/lib/right_agent/platform/darwin.rb +14 -0
- data/lib/right_agent/platform/linux.rb +23 -0
- data/lib/right_agent/platform/windows.rb +31 -0
- data/lib/right_agent/scripts/agent_controller.rb +16 -8
- data/lib/right_agent/scripts/agent_deployer.rb +6 -0
- data/lib/right_agent/scripts/log_level_manager.rb +4 -5
- data/lib/right_agent/scripts/stats_manager.rb +9 -1
- data/lib/right_agent/sender.rb +623 -371
- data/lib/right_agent/stats_helper.rb +15 -1
- data/lib/right_agent/tracer.rb +1 -1
- data/right_agent.gemspec +14 -15
- data/spec/agent_config_spec.rb +9 -0
- data/spec/agent_spec.rb +154 -18
- data/spec/broker_client_spec.rb +171 -170
- data/spec/dispatcher_spec.rb +24 -8
- data/spec/ha_broker_client_spec.rb +55 -33
- data/spec/monkey_patches/amqp_patch_spec.rb +12 -0
- data/spec/packets_spec.rb +2 -0
- data/spec/sender_spec.rb +140 -69
- data/spec/stats_helper_spec.rb +5 -0
- metadata +54 -53
data/lib/right_agent/packets.rb
CHANGED
@@ -380,7 +380,8 @@ module RightScale
|
|
380
380
|
# Packet for a work request for an actor node that has no result, i.e., one-way request
|
381
381
|
class Push < Packet
|
382
382
|
|
383
|
-
attr_accessor :from, :scope, :payload, :type, :token, :selector, :target, :persistent, :
|
383
|
+
attr_accessor :from, :scope, :payload, :type, :token, :selector, :target, :persistent, :confirm,
|
384
|
+
:expires_at, :tags
|
384
385
|
|
385
386
|
DEFAULT_OPTIONS = {:selector => :any}
|
386
387
|
|
@@ -397,6 +398,8 @@ module RightScale
|
|
397
398
|
# :target(String):: Target recipient
|
398
399
|
# :persistent(Boolean):: Indicates if this request should be saved to persistent storage
|
399
400
|
# by the AMQP broker
|
401
|
+
# :confirm(Boolean):: Whether require confirmation response from mapper containing targets
|
402
|
+
# to which request was published but not necessarily delivered
|
400
403
|
# :expires_at(Integer|nil):: Time in seconds in Unix-epoch when this request expires and
|
401
404
|
# is to be ignored by the receiver; value 0 means never expire; defaults to 0
|
402
405
|
# :tags(Array(Symbol)):: List of tags to be used for selecting target for this request
|
@@ -416,6 +419,7 @@ module RightScale
|
|
416
419
|
@selector = :any if ["least_loaded", "random"].include?(@selector.to_s)
|
417
420
|
@target = opts[:target]
|
418
421
|
@persistent = opts[:persistent]
|
422
|
+
@confirm = opts[:confirm]
|
419
423
|
@expires_at = opts[:expires_at] || 0
|
420
424
|
@tags = opts[:tags] || []
|
421
425
|
@version = version
|
@@ -448,10 +452,11 @@ module RightScale
|
|
448
452
|
# (Push):: New packet
|
449
453
|
def self.create(o)
|
450
454
|
i = o['data']
|
451
|
-
new(i['type'], i['payload'], { :from
|
452
|
-
:token
|
453
|
-
:target
|
454
|
-
:
|
455
|
+
new(i['type'], i['payload'], { :from => self.compatible(i['from']), :scope => i['scope'],
|
456
|
+
:token => i['token'], :selector => i['selector'],
|
457
|
+
:target => self.compatible(i['target']), :persistent => i['persistent'],
|
458
|
+
:confirm => i['confirm'], :expires_at => i['expires_at'],
|
459
|
+
:tags => i['tags']},
|
455
460
|
i['version'] || [DEFAULT_VERSION, DEFAULT_VERSION], o['size'])
|
456
461
|
end
|
457
462
|
|
data/lib/right_agent/platform.rb
CHANGED
@@ -234,6 +234,14 @@ module RightScale
|
|
234
234
|
platform_service(:rng)
|
235
235
|
end
|
236
236
|
|
237
|
+
# Platform process facilities.
|
238
|
+
#
|
239
|
+
# === Return
|
240
|
+
# (Object):: Platform-specific process facilities object
|
241
|
+
def process
|
242
|
+
platform_service(:process)
|
243
|
+
end
|
244
|
+
|
237
245
|
private
|
238
246
|
|
239
247
|
# Load platform specific implementation
|
@@ -222,6 +222,20 @@ module RightScale
|
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
225
|
+
class Process
|
226
|
+
# queries resident set size (current working set size in Windows).
|
227
|
+
#
|
228
|
+
# === Parameters
|
229
|
+
# pid(Fixnum):: process ID or nil for current process
|
230
|
+
#
|
231
|
+
# === Return
|
232
|
+
# result(Fixnum):: current set size in KB
|
233
|
+
def resident_set_size(pid=nil)
|
234
|
+
pid = $$ unless pid
|
235
|
+
return `ps -o rss= -p #{pid}`.to_i
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
225
239
|
end # Platform
|
226
240
|
|
227
241
|
end # RightScale
|
@@ -75,6 +75,15 @@ module RightScale
|
|
75
75
|
@flavor =~ /suse/
|
76
76
|
end
|
77
77
|
|
78
|
+
# Is this machine running rhel?
|
79
|
+
#
|
80
|
+
# === Return
|
81
|
+
# true:: If Linux flavor is rhel
|
82
|
+
# false:: Otherwise
|
83
|
+
def rhel?
|
84
|
+
@flavor =~ /redhatenterpriseserver/
|
85
|
+
end
|
86
|
+
|
78
87
|
class Filesystem
|
79
88
|
|
80
89
|
# Is given command available in the PATH?
|
@@ -263,6 +272,20 @@ module RightScale
|
|
263
272
|
end
|
264
273
|
end
|
265
274
|
|
275
|
+
class Process
|
276
|
+
# queries resident set size (current working set size in Windows).
|
277
|
+
#
|
278
|
+
# === Parameters
|
279
|
+
# pid(Fixnum):: process ID or nil for current process
|
280
|
+
#
|
281
|
+
# === Return
|
282
|
+
# result(Fixnum):: current set size in KB
|
283
|
+
def resident_set_size(pid=nil)
|
284
|
+
pid = $$ unless pid
|
285
|
+
return `ps -o rss= -p #{pid}`.to_i
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
266
289
|
end # Platform
|
267
290
|
|
268
291
|
end # RightScale
|
@@ -1183,6 +1183,37 @@ EOF
|
|
1183
1183
|
end
|
1184
1184
|
end
|
1185
1185
|
|
1186
|
+
class Process
|
1187
|
+
include ::Windows::Process
|
1188
|
+
|
1189
|
+
@@get_process_memory_info = nil
|
1190
|
+
|
1191
|
+
# see PROCESS_MEMORY_COUNTERS structure: "http://msdn.microsoft.com/en-us/library/ms684877%28VS.85%29.aspx"
|
1192
|
+
SIZEOF_PROCESS_MEMORY_COUNTERS = 10 * 4
|
1193
|
+
|
1194
|
+
# queries resident set size (current working set size in Windows).
|
1195
|
+
#
|
1196
|
+
# === Parameters
|
1197
|
+
# pid(Fixnum):: process ID or nil for current process
|
1198
|
+
#
|
1199
|
+
# === Return
|
1200
|
+
# result(Fixnum):: current set size in KB
|
1201
|
+
def resident_set_size(pid=nil)
|
1202
|
+
@@get_process_memory_info = ::Win32::API.new("GetProcessMemoryInfo", 'LPL', 'B', 'psapi') unless @@get_process_memory_info
|
1203
|
+
|
1204
|
+
# FIX: call OpenProcess and ensure proper access and close if given PID.
|
1205
|
+
raise NotImplementedError.new("pid != nil not yet implemented") if pid
|
1206
|
+
process_handle = GetCurrentProcess()
|
1207
|
+
process_memory_counters = "\0" * SIZEOF_PROCESS_MEMORY_COUNTERS
|
1208
|
+
result = @@get_process_memory_info.call(process_handle, process_memory_counters, process_memory_counters.size)
|
1209
|
+
# note that the 'B' return type is a Fixnum (i.e. not TrueClass or FalseClass) of 'zero' on failure or 'non-zero' on success
|
1210
|
+
raise ::RightScale::Win32Error.new("Failed to get resident set size for process") if 0 == result
|
1211
|
+
|
1212
|
+
# current .WorkingSetSize (bytes) is equivalent of Linux' ps resident set size (KB)
|
1213
|
+
return process_memory_counters[12..16].unpack("L")[0] / 1024 # bytes to KB
|
1214
|
+
end
|
1215
|
+
end
|
1216
|
+
|
1186
1217
|
protected
|
1187
1218
|
|
1188
1219
|
# internal class for querying OS version, etc.
|
@@ -73,10 +73,9 @@
|
|
73
73
|
|
74
74
|
require 'rubygems'
|
75
75
|
require 'optparse'
|
76
|
-
require '
|
77
|
-
require File.
|
78
|
-
require File.
|
79
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'common_parser'))
|
76
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'minimal'))
|
77
|
+
require File.normalize_path(File.join(File.dirname(__FILE__), 'usage'))
|
78
|
+
require File.normalize_path(File.join(File.dirname(__FILE__), 'common_parser'))
|
80
79
|
|
81
80
|
module RightScale
|
82
81
|
|
@@ -115,6 +114,7 @@ module RightScale
|
|
115
114
|
# === Return
|
116
115
|
# true:: Always return true
|
117
116
|
def control(options)
|
117
|
+
|
118
118
|
# Initialize directory settings
|
119
119
|
AgentConfig.cfg_dir = options[:cfg_dir]
|
120
120
|
AgentConfig.pid_dir = options[:pid_dir]
|
@@ -169,7 +169,7 @@ module RightScale
|
|
169
169
|
# === Return
|
170
170
|
# options(Hash):: Parsed options
|
171
171
|
def parse_args
|
172
|
-
options = {}
|
172
|
+
options = {:thin_command_client => false}
|
173
173
|
|
174
174
|
opts = OptionParser.new do |opts|
|
175
175
|
parse_common(opts, options)
|
@@ -194,7 +194,7 @@ module RightScale
|
|
194
194
|
options[:pid_file] = file
|
195
195
|
options[:action] = 'kill'
|
196
196
|
end
|
197
|
-
|
197
|
+
|
198
198
|
opts.on("-K", "--killall") do
|
199
199
|
options[:action] = 'killall'
|
200
200
|
end
|
@@ -228,7 +228,7 @@ module RightScale
|
|
228
228
|
|
229
229
|
opts.on("-f", "--foreground") do
|
230
230
|
options[:daemonize] = false
|
231
|
-
#Squelch Ruby VM warnings about various things
|
231
|
+
#Squelch Ruby VM warnings about various things
|
232
232
|
$VERBOSE = nil
|
233
233
|
end
|
234
234
|
|
@@ -249,6 +249,14 @@ module RightScale
|
|
249
249
|
exit 0 if e.is_a?(SystemExit)
|
250
250
|
fail(e.message, print_usage = true)
|
251
251
|
end
|
252
|
+
|
253
|
+
# allow specific arguments to use a thin command client for faster
|
254
|
+
# execution (on Windows, etc.)
|
255
|
+
unless options[:thin_command_client]
|
256
|
+
# require full right_agent for any commands which do not specify thin
|
257
|
+
# command client.
|
258
|
+
require File.normalize_path(File.join(File.dirname(__FILE__), '..', '..', 'right_agent'))
|
259
|
+
end
|
252
260
|
resolve_identity(options)
|
253
261
|
options
|
254
262
|
end
|
@@ -344,7 +352,7 @@ module RightScale
|
|
344
352
|
end
|
345
353
|
true
|
346
354
|
end
|
347
|
-
|
355
|
+
|
348
356
|
# Stop agent process
|
349
357
|
#
|
350
358
|
# === Parameters
|
@@ -34,6 +34,7 @@
|
|
34
34
|
# --vhost, -v VHOST Set agent AMQP virtual host
|
35
35
|
# --host, -h HOST Set AMQP broker host
|
36
36
|
# --port, -P PORT Set AMQP broker port
|
37
|
+
# --heartbeat, -b SEC Set number of seconds between AMQP broker connection heartbeats, 0 means disable
|
37
38
|
# --prefetch COUNT Set maximum requests AMQP broker is to prefetch before current is ack'd
|
38
39
|
# --http-proxy PROXY Use a proxy for all agent-originated HTTP traffic
|
39
40
|
# --http-no-proxy NOPROXY Comma-separated list of proxy exceptions (e.g. metadata server)
|
@@ -186,6 +187,10 @@ module RightScale
|
|
186
187
|
options[:prefetch] = count.to_i
|
187
188
|
end
|
188
189
|
|
190
|
+
opts.on('-b', '--heartbeat SEC') do |sec|
|
191
|
+
options[:heartbeat] = sec.to_i
|
192
|
+
end
|
193
|
+
|
189
194
|
opts.on('-o', '--options OPT') do |e|
|
190
195
|
fail("Invalid option definition #{e}' (use '=' to separate name and value)") unless e.include?('=')
|
191
196
|
key, val = e.split(/=/)
|
@@ -282,6 +287,7 @@ module RightScale
|
|
282
287
|
cfg[:port] = options[:port] if options[:port]
|
283
288
|
cfg[:host] = options[:host] if options[:host]
|
284
289
|
cfg[:prefetch] = options[:prefetch] || 1
|
290
|
+
cfg[:heartbeat] = options[:heartbeat] if options[:heartbeat]
|
285
291
|
cfg[:time_to_live] = options[:time_to_live] || 60
|
286
292
|
cfg[:retry_timeout] = options[:retry_timeout] || 2 * 60
|
287
293
|
cfg[:retry_interval] = options[:retry_interval] || 15
|
@@ -94,8 +94,8 @@ module RightScale
|
|
94
94
|
opts = OptionParser.new do |opts|
|
95
95
|
|
96
96
|
opts.on('-l', '--log-level LEVEL') do |l|
|
97
|
-
fail("Invalid log level '#{l}'") unless AgentManager::LEVELS.include?(l.to_sym)
|
98
|
-
options[:level] = l
|
97
|
+
fail("Invalid log level '#{l}'") unless AgentManager::LEVELS.include?(l.downcase.to_sym)
|
98
|
+
options[:level] = l.downcase
|
99
99
|
end
|
100
100
|
|
101
101
|
opts.on("-c", "--cfg-dir DIR") do |d|
|
@@ -136,12 +136,11 @@ module RightScale
|
|
136
136
|
def request_log_level(agent_name, command, options)
|
137
137
|
res = false
|
138
138
|
config_options = AgentConfig.agent_options(agent_name)
|
139
|
-
unless config_options.empty?
|
140
|
-
listen_port = config_options[:listen_port]
|
139
|
+
unless config_options.empty? || (listen_port = config_options[:listen_port]).nil?
|
141
140
|
fail("Could not retrieve #{agent_name} agent listen port") unless listen_port
|
142
141
|
client = CommandClient.new(listen_port, config_options[:cookie])
|
143
142
|
begin
|
144
|
-
client.send_command(command, options[:verbose]) do |level|
|
143
|
+
client.send_command(command, options[:verbose], timeout = 5) do |level|
|
145
144
|
puts "Agent #{agent_name} log level: #{level.to_s.upcase}"
|
146
145
|
end
|
147
146
|
res = true
|
@@ -14,6 +14,9 @@
|
|
14
14
|
# rstat AGENT --json
|
15
15
|
# rstat AGENT --j
|
16
16
|
#
|
17
|
+
# Log details of statistics retrieval
|
18
|
+
# rstat AGENT -v
|
19
|
+
#
|
17
20
|
# === Usage:
|
18
21
|
# rstat [AGENT] [options]
|
19
22
|
#
|
@@ -21,6 +24,7 @@
|
|
21
24
|
# --reset, -r As part of gathering the stats from an agent also reset the stats
|
22
25
|
# --timeout, -t SEC Override default timeout in seconds to wait for a response from an agent
|
23
26
|
# --json, -j Display the stats data in JSON format
|
27
|
+
# --verbose, -v Log debug information
|
24
28
|
# --cfg-dir, -c DIR Set directory containing configuration for all agents
|
25
29
|
# --help Display help
|
26
30
|
|
@@ -61,7 +65,7 @@ module RightScale
|
|
61
65
|
# === Return
|
62
66
|
# true:: Always return true
|
63
67
|
def manage(options)
|
64
|
-
init_log
|
68
|
+
init_log if options[:verbose]
|
65
69
|
AgentConfig.cfg_dir = options[:cfg_dir]
|
66
70
|
options[:timeout] ||= DEFAULT_TIMEOUT
|
67
71
|
request_stats(options)
|
@@ -92,6 +96,10 @@ module RightScale
|
|
92
96
|
options[:json] = true
|
93
97
|
end
|
94
98
|
|
99
|
+
opts.on('-v', '--verbose') do
|
100
|
+
options[:verbose] = true
|
101
|
+
end
|
102
|
+
|
95
103
|
opts.on("-c", "--cfg-dir DIR") do |d|
|
96
104
|
options[:cfg_dir] = d
|
97
105
|
end
|
data/lib/right_agent/sender.rb
CHANGED
@@ -30,40 +30,504 @@ module RightScale
|
|
30
30
|
|
31
31
|
include StatsHelper
|
32
32
|
|
33
|
-
#
|
34
|
-
|
33
|
+
# Request that is waiting for a response
|
34
|
+
class PendingRequest
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
# (Symbol) Kind of send request
|
37
|
+
attr_reader :kind
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
# (Time) Time when request message was received
|
40
|
+
attr_reader :receive_time
|
41
|
+
|
42
|
+
# (Proc) Block to be activated when response is received
|
43
|
+
attr_reader :response_handler
|
44
|
+
|
45
|
+
# (String) Token for parent request in a retry situation
|
46
|
+
attr_accessor :retry_parent
|
47
|
+
|
48
|
+
def initialize(kind, receive_time, response_handler)
|
49
|
+
@kind = kind
|
50
|
+
@receive_time = receive_time
|
51
|
+
@response_handler = response_handler
|
52
|
+
@retry_parent = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
end # PendingRequest
|
56
|
+
|
57
|
+
# Cache for requests that are waiting for a response
|
58
|
+
# Automatically deletes push requests when get too old
|
59
|
+
# Retains non-push requests until explicitly deleted
|
60
|
+
class PendingRequests < Hash
|
61
|
+
|
62
|
+
# Kinds of send requests
|
63
|
+
REQUEST_KINDS = [:send_retryable_request, :send_persistent_request]
|
64
|
+
|
65
|
+
# Kinds of send pushes
|
66
|
+
PUSH_KINDS = [:send_push, :send_persistent_push]
|
67
|
+
|
68
|
+
# Maximum number of seconds to retain send pushes in cache
|
69
|
+
MAX_PUSH_AGE = 2 * 60
|
70
|
+
|
71
|
+
# Minimum number of seconds between push cleanups
|
72
|
+
MIN_CLEANUP_INTERVAL = 15
|
73
|
+
|
74
|
+
# Create cache
|
75
|
+
def initialize
|
76
|
+
@last_cleanup = Time.now
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
# Store pending request
|
81
|
+
#
|
82
|
+
# === Parameters
|
83
|
+
# token(String):: Generated message identifier
|
84
|
+
# request(PendingRequest):: Pending request
|
85
|
+
#
|
86
|
+
# === Return
|
87
|
+
# (PendingRequest):: Stored request
|
88
|
+
def []=(token, request)
|
89
|
+
now = Time.now
|
90
|
+
if (now - @last_cleanup) > MIN_CLEANUP_INTERVAL
|
91
|
+
self.reject! { |t, r| PUSH_KINDS.include?(r.kind) && (now - r.receive_time) > MAX_PUSH_AGE }
|
92
|
+
@last_cleanup = now
|
93
|
+
end
|
94
|
+
super
|
95
|
+
end
|
96
|
+
|
97
|
+
# Select cache entries of the given kinds
|
98
|
+
#
|
99
|
+
# === Parameters
|
100
|
+
# kinds(Array):: Kind of requests to be included
|
101
|
+
#
|
102
|
+
# === Return
|
103
|
+
# (Hash):: Requests of specified kind
|
104
|
+
def kind(kinds)
|
105
|
+
self.reject { |t, r| !kinds.include?(r.kind) }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get age of youngest pending request
|
109
|
+
#
|
110
|
+
# === Return
|
111
|
+
# age(Integer):: Age of youngest request
|
112
|
+
def youngest_age
|
113
|
+
now = Time.now
|
114
|
+
age = nil
|
115
|
+
self.each_value do |r|
|
116
|
+
seconds = (now - r.receive_time).to_i
|
117
|
+
age = seconds if age.nil? || seconds < age
|
118
|
+
end
|
119
|
+
age
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get age of oldest pending request
|
123
|
+
#
|
124
|
+
# === Return
|
125
|
+
# age(Integer):: Age of oldest request
|
126
|
+
def oldest_age
|
127
|
+
now = Time.now
|
128
|
+
age = nil
|
129
|
+
self.each_value do |r|
|
130
|
+
seconds = (now - r.receive_time).to_i
|
131
|
+
age = seconds if age.nil? || seconds > age
|
132
|
+
end
|
133
|
+
age
|
134
|
+
end
|
135
|
+
|
136
|
+
end # PendingRequests
|
137
|
+
|
138
|
+
# Queue for storing requests while disconnected from broker and then sending
|
139
|
+
# them when successfully reconnect
|
140
|
+
class OfflineHandler
|
141
|
+
|
142
|
+
# Maximum seconds to wait before starting flushing offline queue when disabling offline mode
|
143
|
+
MAX_QUEUE_FLUSH_DELAY = 2 * 60
|
144
|
+
|
145
|
+
# Maximum number of offline queued requests before triggering restart vote
|
146
|
+
MAX_QUEUED_REQUESTS = 1000
|
147
|
+
|
148
|
+
# Number of seconds that should be spent in offline mode before triggering a restart vote
|
149
|
+
RESTART_VOTE_DELAY = 15 * 60
|
150
|
+
|
151
|
+
# (Symbol) Current queue state with possible values:
|
152
|
+
# Value Description Action Next state
|
153
|
+
# :created Queue created init :initializing
|
154
|
+
# :initializing Agent still initializing start :running
|
155
|
+
# :running Queue has been started disable when offline :flushing
|
156
|
+
# :flushing Sending queued requests enable :running
|
157
|
+
# :terminating Agent terminating
|
158
|
+
attr_reader :state
|
159
|
+
|
160
|
+
# (Symbol) Current offline handling mode with possible values:
|
161
|
+
# Value Description
|
162
|
+
# :initializing Agent still initializing
|
163
|
+
# :online Agent connected to broker
|
164
|
+
# :offline Agent disconnected from broker
|
165
|
+
attr_reader :mode
|
166
|
+
|
167
|
+
# (Array) Offline queue
|
168
|
+
attr_accessor :queue
|
169
|
+
|
170
|
+
# Create offline queue
|
171
|
+
#
|
172
|
+
# === Parameters
|
173
|
+
# restart_callback(Proc):: Callback that is activated on each restart vote with votes being initiated
|
174
|
+
# by offline queue exceeding MAX_QUEUED_REQUESTS
|
175
|
+
# offline_stats(ActivityStats):: Offline queue tracking statistics
|
176
|
+
def initialize(restart_callback, offline_stats)
|
177
|
+
@restart_vote = restart_callback
|
178
|
+
@restart_vote_timer = nil
|
179
|
+
@restart_vote_count = 0
|
180
|
+
@offline_stats = offline_stats
|
181
|
+
@state = :created
|
182
|
+
@mode = :initializing
|
183
|
+
@queue = []
|
184
|
+
end
|
185
|
+
|
186
|
+
# Initialize the offline queue
|
187
|
+
# All requests sent prior to running this initialization are queued
|
188
|
+
# and then are sent once this initialization has run
|
189
|
+
# All requests following this call and prior to calling start
|
190
|
+
# are prepended to the request queue
|
191
|
+
#
|
192
|
+
# === Return
|
193
|
+
# true:: Always return true
|
194
|
+
def init
|
195
|
+
@state = :initializing if @state == :created
|
196
|
+
true
|
197
|
+
end
|
41
198
|
|
42
|
-
|
43
|
-
|
199
|
+
# Switch to online mode and send all buffered messages
|
200
|
+
#
|
201
|
+
# === Return
|
202
|
+
# true:: Always return true
|
203
|
+
def start
|
204
|
+
if @state == :initializing
|
205
|
+
@state = :running
|
206
|
+
flush unless @mode == :offline
|
207
|
+
@mode = :online if @mode == :initializing
|
208
|
+
end
|
209
|
+
true
|
210
|
+
end
|
211
|
+
|
212
|
+
# Is agent currently offline?
|
213
|
+
#
|
214
|
+
# === Return
|
215
|
+
# (Boolean):: true if agent offline, otherwise false
|
216
|
+
def offline?
|
217
|
+
@mode == :offline || @state == :created
|
218
|
+
end
|
219
|
+
|
220
|
+
# In request queueing mode?
|
221
|
+
#
|
222
|
+
# === Return
|
223
|
+
# (Boolean):: true if should queue request, otherwise false
|
224
|
+
def queueing?
|
225
|
+
offline? && @state != :flushing
|
226
|
+
end
|
227
|
+
|
228
|
+
# Switch to offline mode
|
229
|
+
# In this mode requests are queued in memory rather than sent to the mapper
|
230
|
+
# Idempotent
|
231
|
+
#
|
232
|
+
# === Return
|
233
|
+
# true:: Always return true
|
234
|
+
def enable
|
235
|
+
if offline?
|
236
|
+
if @state == :flushing
|
237
|
+
# If we were in offline mode then switched back to online but are still in the
|
238
|
+
# process of flushing the in-memory queue and are now switching to offline mode
|
239
|
+
# again then stop the flushing
|
240
|
+
@state = :running
|
241
|
+
end
|
242
|
+
else
|
243
|
+
Log.info("[offline] Disconnect from broker detected, entering offline mode")
|
244
|
+
Log.info("[offline] Messages will be queued in memory until connection to broker is re-established")
|
245
|
+
@offline_stats.update
|
246
|
+
@queue ||= [] # ensure queue is valid without losing any messages when going offline
|
247
|
+
@mode = :offline
|
248
|
+
start_timer
|
249
|
+
end
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
253
|
+
# Switch back to sending requests to mapper after in-memory queue gets flushed
|
254
|
+
# Idempotent
|
255
|
+
#
|
256
|
+
# === Return
|
257
|
+
# true:: Always return true
|
258
|
+
def disable
|
259
|
+
if offline? && @state != :created
|
260
|
+
Log.info("[offline] Connection to broker re-established")
|
261
|
+
@offline_stats.finish
|
262
|
+
cancel_timer
|
263
|
+
@state = :flushing
|
264
|
+
# Wait a bit to avoid flooding the mapper
|
265
|
+
EM.add_timer(rand(MAX_QUEUE_FLUSH_DELAY)) { flush }
|
266
|
+
end
|
267
|
+
true
|
268
|
+
end
|
269
|
+
|
270
|
+
# Queue given request in memory
|
271
|
+
#
|
272
|
+
# === Parameters
|
273
|
+
# request(Hash):: Request to be stored
|
274
|
+
#
|
275
|
+
# === Return
|
276
|
+
# true:: Always return true
|
277
|
+
def queue_request(kind, type, payload, target, callback)
|
278
|
+
request = {:kind => kind, :type => type, :payload => payload, :target => target, :callback => callback}
|
279
|
+
Log.info("[offline] Queuing request: #{request.inspect}")
|
280
|
+
vote_to_restart if (@restart_vote_count += 1) >= MAX_QUEUED_REQUESTS
|
281
|
+
if @state == :initializing
|
282
|
+
# We are in the initialization callback, requests should be put at the head of the queue
|
283
|
+
@queue.unshift(request)
|
284
|
+
else
|
285
|
+
@queue << request
|
286
|
+
end
|
287
|
+
true
|
288
|
+
end
|
289
|
+
|
290
|
+
# Prepare for agent termination
|
291
|
+
#
|
292
|
+
# === Return
|
293
|
+
# true:: Always return true
|
294
|
+
def terminate
|
295
|
+
@state = :terminating
|
296
|
+
cancel_timer
|
297
|
+
true
|
298
|
+
end
|
44
299
|
|
45
|
-
|
46
|
-
MAX_QUEUED_REQUESTS = 1000
|
300
|
+
protected
|
47
301
|
|
48
|
-
|
49
|
-
|
302
|
+
# Send any requests that were queued while in offline mode
|
303
|
+
# Do this asynchronously to allow for agents to respond to requests
|
304
|
+
# Once all in-memory requests have been flushed, switch off offline mode
|
305
|
+
#
|
306
|
+
# === Return
|
307
|
+
# true:: Always return true
|
308
|
+
def flush
|
309
|
+
if @state == :flushing
|
310
|
+
Log.info("[offline] Starting to flush request queue of size #{@queue.size}") unless @mode == :initializing
|
311
|
+
unless @queue.empty?
|
312
|
+
r = @queue.shift
|
313
|
+
if r[:callback]
|
314
|
+
Sender.instance.__send__(r[:kind], r[:type], r[:payload], r[:target]) { |result| r[:callback].call(result) }
|
315
|
+
else
|
316
|
+
Sender.instance.__send__(r[:kind], r[:type], r[:payload], r[:target])
|
317
|
+
end
|
318
|
+
end
|
319
|
+
if @queue.empty?
|
320
|
+
Log.info("[offline] Request queue flushed, resuming normal operations") unless @mode == :initializing
|
321
|
+
@mode = :online
|
322
|
+
@state = :running
|
323
|
+
else
|
324
|
+
EM.next_tick { flush }
|
325
|
+
end
|
326
|
+
end
|
327
|
+
true
|
328
|
+
end
|
50
329
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
330
|
+
# Vote for restart and reset trigger
|
331
|
+
#
|
332
|
+
# === Parameters
|
333
|
+
# timer_trigger(Boolean):: true if vote was triggered by timer, false if it
|
334
|
+
# was triggered by number of messages in in-memory queue
|
335
|
+
#
|
336
|
+
# === Return
|
337
|
+
# true:: Always return true
|
338
|
+
def vote_to_restart(timer_trigger = false)
|
339
|
+
if @restart_vote
|
340
|
+
@restart_vote.call
|
341
|
+
if timer_trigger
|
342
|
+
start_timer
|
343
|
+
else
|
344
|
+
@restart_vote_count = 0
|
345
|
+
end
|
346
|
+
end
|
347
|
+
true
|
348
|
+
end
|
349
|
+
|
350
|
+
# Start restart vote timer
|
351
|
+
#
|
352
|
+
# === Return
|
353
|
+
# true:: Always return true
|
354
|
+
def start_timer
|
355
|
+
if @restart_vote && @state != :terminating
|
356
|
+
@restart_vote_timer ||= EM::Timer.new(RESTART_VOTE_DELAY) { vote_to_restart(timer_trigger = true) }
|
357
|
+
end
|
358
|
+
true
|
359
|
+
end
|
360
|
+
|
361
|
+
# Cancel restart vote timer
|
362
|
+
#
|
363
|
+
# === Return
|
364
|
+
# true:: Always return true
|
365
|
+
def cancel_timer
|
366
|
+
if @restart_vote_timer
|
367
|
+
@restart_vote_timer.cancel
|
368
|
+
@restart_vote_timer = nil
|
369
|
+
@restart_vote_count = 0
|
370
|
+
end
|
371
|
+
true
|
372
|
+
end
|
373
|
+
|
374
|
+
end # OfflineHandler
|
375
|
+
|
376
|
+
# Broker connectivity checker
|
377
|
+
# Checks connectivity when requested
|
378
|
+
class ConnectivityChecker
|
379
|
+
|
380
|
+
# Minimum number of seconds between restarts of the inactivity timer
|
381
|
+
MIN_RESTART_INACTIVITY_TIMER_INTERVAL = 60
|
382
|
+
|
383
|
+
# Number of seconds to wait for ping response from a mapper when checking connectivity
|
384
|
+
PING_TIMEOUT = 30
|
385
|
+
|
386
|
+
# (EM::Timer) Timer while waiting for mapper ping response
|
387
|
+
attr_accessor :ping_timer
|
388
|
+
|
389
|
+
def initialize(sender, check_interval, ping_stats, exception_stats)
|
390
|
+
@sender = sender
|
391
|
+
@check_interval = check_interval
|
392
|
+
@ping_timer = nil
|
393
|
+
@ping_stats = ping_stats
|
394
|
+
@exception_stats = exception_stats
|
395
|
+
@last_received = Time.now
|
396
|
+
@message_received_callbacks = []
|
397
|
+
restart_inactivity_timer if @check_interval > 0
|
398
|
+
end
|
399
|
+
|
400
|
+
# Update the time this agent last received a request or response message
|
401
|
+
# and restart the inactivity timer thus deferring the next connectivity check
|
402
|
+
# Also forward this message receipt notification to any callbacks that have registered
|
403
|
+
#
|
404
|
+
# === Block
|
405
|
+
# Optional block without parameters that is activated when a message is received
|
406
|
+
#
|
407
|
+
# === Return
|
408
|
+
# true:: Always return true
|
409
|
+
def message_received(&callback)
|
410
|
+
if block_given?
|
411
|
+
@message_received_callbacks << callback
|
412
|
+
else
|
413
|
+
@message_received_callbacks.each { |c| c.call }
|
414
|
+
if @check_interval > 0
|
415
|
+
now = Time.now
|
416
|
+
if (now - @last_received) > MIN_RESTART_INACTIVITY_TIMER_INTERVAL
|
417
|
+
@last_received = now
|
418
|
+
restart_inactivity_timer
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
true
|
423
|
+
end
|
424
|
+
|
425
|
+
# Check whether broker connection is usable by pinging a mapper via that broker
|
426
|
+
# Attempt to reconnect if ping does not respond in PING_TIMEOUT seconds
|
427
|
+
# Ignore request if already checking a connection
|
428
|
+
# Only to be called from primary thread
|
429
|
+
#
|
430
|
+
# === Parameters
|
431
|
+
# id(String):: Identity of specific broker to use to send ping, defaults to any
|
432
|
+
# currently connected broker
|
433
|
+
#
|
434
|
+
# === Return
|
435
|
+
# true:: Always return true
|
436
|
+
def check(id = nil)
|
437
|
+
unless @terminating || @ping_timer || (id && !@sender.broker.connected?(id))
|
438
|
+
@ping_timer = EM::Timer.new(PING_TIMEOUT) do
|
439
|
+
begin
|
440
|
+
@ping_stats.update("timeout")
|
441
|
+
@ping_timer = nil
|
442
|
+
Log.warning("Mapper ping via broker #{id} timed out after #{PING_TIMEOUT} seconds, attempting to reconnect")
|
443
|
+
host, port, index, priority, _ = @sender.broker.identity_parts(id)
|
444
|
+
@sender.agent.connect(host, port, index, priority, force = true)
|
445
|
+
rescue Exception => e
|
446
|
+
Log.error("Failed to reconnect to broker #{id}", e, :trace)
|
447
|
+
@exception_stats.track("ping timeout", e)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
handler = lambda do |_|
|
452
|
+
begin
|
453
|
+
if @ping_timer
|
454
|
+
@ping_stats.update("success")
|
455
|
+
@ping_timer.cancel
|
456
|
+
@ping_timer = nil
|
457
|
+
end
|
458
|
+
rescue Exception => e
|
459
|
+
Log.error("Failed to cancel mapper ping", e, :trace)
|
460
|
+
@exception_stats.track("cancel ping", e)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
request = Request.new("/mapper/ping", nil, {:from => @sender.identity, :token => AgentIdentity.generate})
|
464
|
+
@sender.pending_requests[request.token] = PendingRequest.new(:send_persistent_request, Time.now, handler)
|
465
|
+
ids = [id] if id
|
466
|
+
id = @sender.publish(request, ids).first
|
467
|
+
end
|
468
|
+
true
|
469
|
+
end
|
470
|
+
|
471
|
+
# Prepare for agent termination
|
472
|
+
#
|
473
|
+
# === Return
|
474
|
+
# true:: Always return true
|
475
|
+
def terminate
|
476
|
+
@terminating = true
|
477
|
+
@check_interval = 0
|
478
|
+
if @ping_timer
|
479
|
+
@ping_timer.cancel
|
480
|
+
@ping_timer = nil
|
481
|
+
end
|
482
|
+
if @inactivity_timer
|
483
|
+
@inactivity_timer.cancel
|
484
|
+
@inactivity_timer = nil
|
485
|
+
end
|
486
|
+
true
|
487
|
+
end
|
488
|
+
|
489
|
+
protected
|
490
|
+
|
491
|
+
# Start timer that waits for inactive messaging period to end before checking connectivity
|
492
|
+
#
|
493
|
+
# === Return
|
494
|
+
# true:: Always return true
|
495
|
+
def restart_inactivity_timer
|
496
|
+
@inactivity_timer.cancel if @inactivity_timer
|
497
|
+
@inactivity_timer = EM::Timer.new(@check_interval) do
|
498
|
+
begin
|
499
|
+
check
|
500
|
+
rescue Exception => e
|
501
|
+
Log.error("Failed connectivity check", e, :trace)
|
502
|
+
@exception_stats.track("check connectivity", e)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
true
|
506
|
+
end
|
507
|
+
|
508
|
+
end # ConnectivityChecker
|
509
|
+
|
510
|
+
# Factor used on each retry iteration to achieve exponential backoff
|
511
|
+
RETRY_BACKOFF_FACTOR = 4
|
512
|
+
|
513
|
+
# (PendingRequests) Requests waiting for a response
|
59
514
|
attr_accessor :pending_requests
|
60
515
|
|
516
|
+
# (OfflineHandler) Handler for requests when disconnected from broker
|
517
|
+
attr_reader :offline_handler
|
518
|
+
|
519
|
+
# (ConnectivityChecker) Broker connection checker
|
520
|
+
attr_reader :connectivity_checker
|
521
|
+
|
61
522
|
# (HABrokerClient) High availability AMQP broker client
|
62
523
|
attr_accessor :broker
|
63
524
|
|
64
525
|
# (String) Identity of the associated agent
|
65
526
|
attr_reader :identity
|
66
527
|
|
528
|
+
# (Agent) Associated agent
|
529
|
+
attr_reader :agent
|
530
|
+
|
67
531
|
# Accessor for use by actor
|
68
532
|
#
|
69
533
|
# === Return
|
@@ -82,6 +546,8 @@ module RightScale
|
|
82
546
|
# agent(Agent):: Reference to agent
|
83
547
|
# :offline_queueing(Boolean):: Whether to queue request if currently not connected to any brokers,
|
84
548
|
# also requires agent invocation of initialize_offline_queue and start_offline_queue methods below
|
549
|
+
# :ping_interval(Integer):: Minimum number of seconds since last message receipt to ping the mapper
|
550
|
+
# to check connectivity, defaults to 0 meaning do not ping
|
85
551
|
# :restart_callback(Proc):: Callback that is activated on each restart vote with votes being initiated
|
86
552
|
# by offline queue exceeding MAX_QUEUED_REQUESTS or by repeated failures to access mapper when online
|
87
553
|
# :retry_timeout(Numeric):: Maximum number of seconds to retry request before give up
|
@@ -98,52 +564,19 @@ module RightScale
|
|
98
564
|
@broker = @agent.broker
|
99
565
|
@secure = @options[:secure]
|
100
566
|
@single_threaded = @options[:single_threaded]
|
101
|
-
@queueing_mode = :initializing
|
102
|
-
@queue_running = false
|
103
|
-
@queue_initializing = false
|
104
|
-
@queue = []
|
105
|
-
@restart_vote_count = 0
|
106
567
|
@retry_timeout = nil_if_zero(@options[:retry_timeout])
|
107
568
|
@retry_interval = nil_if_zero(@options[:retry_interval])
|
108
|
-
@ping_interval = @options[:ping_interval] || 0
|
109
569
|
|
110
570
|
# Only to be accessed from primary thread
|
111
|
-
@pending_requests =
|
112
|
-
@pending_ping = nil
|
571
|
+
@pending_requests = PendingRequests.new
|
113
572
|
|
114
573
|
reset_stats
|
115
|
-
@
|
116
|
-
@
|
117
|
-
restart_inactivity_timer if @ping_interval > 0
|
574
|
+
@offline_handler = OfflineHandler.new(@options[:restart_callback], @offline_stats)
|
575
|
+
@connectivity_checker = ConnectivityChecker.new(self, @options[:ping_interval] || 0, @ping_stats, @exception_stats)
|
118
576
|
@@instance = self
|
119
577
|
end
|
120
578
|
|
121
|
-
#
|
122
|
-
# and restart the inactivity timer thus deferring the next connectivity check
|
123
|
-
# Also forward this message receipt notification to any callbacks that have registered
|
124
|
-
#
|
125
|
-
# === Block
|
126
|
-
# Optional block without parameters that is activated when a message is received
|
127
|
-
#
|
128
|
-
# === Return
|
129
|
-
# true:: Always return true
|
130
|
-
def message_received(&callback)
|
131
|
-
if block_given?
|
132
|
-
@message_received_callbacks << callback
|
133
|
-
else
|
134
|
-
@message_received_callbacks.each { |c| c.call }
|
135
|
-
if @ping_interval > 0
|
136
|
-
now = Time.now.to_i
|
137
|
-
if (now - @last_received) > MIN_RESTART_INACTIVITY_TIMER_INTERVAL
|
138
|
-
@last_received = now
|
139
|
-
restart_inactivity_timer
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
true
|
144
|
-
end
|
145
|
-
|
146
|
-
# Initialize the offline queue (should be called once)
|
579
|
+
# Initialize the offline queue
|
147
580
|
# All requests sent prior to running this initialization are queued if offline
|
148
581
|
# queueing is enabled and then are sent once this initialization has run
|
149
582
|
# All requests following this call and prior to calling start_offline_queue
|
@@ -152,11 +585,7 @@ module RightScale
|
|
152
585
|
# === Return
|
153
586
|
# true:: Always return true
|
154
587
|
def initialize_offline_queue
|
155
|
-
|
156
|
-
@queue_running = true
|
157
|
-
@queue_initializing = true
|
158
|
-
end
|
159
|
-
true
|
588
|
+
@offline_handler.init if @options[:offline_queueing]
|
160
589
|
end
|
161
590
|
|
162
591
|
# Switch offline queueing to online mode and flush all buffered messages
|
@@ -164,12 +593,38 @@ module RightScale
|
|
164
593
|
# === Return
|
165
594
|
# true:: Always return true
|
166
595
|
def start_offline_queue
|
167
|
-
if @
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
596
|
+
@offline_handler.start if @options[:offline_queueing]
|
597
|
+
end
|
598
|
+
|
599
|
+
# Switch to offline mode
|
600
|
+
# In this mode requests are queued in memory rather than sent to the mapper
|
601
|
+
# Idempotent
|
602
|
+
#
|
603
|
+
# === Return
|
604
|
+
# true:: Always return true
|
605
|
+
def enable_offline_mode
|
606
|
+
@offline_handler.enable if @options[:offline_queueing]
|
607
|
+
end
|
608
|
+
|
609
|
+
# Switch back to sending requests to mapper after in memory queue gets flushed
|
610
|
+
# Idempotent
|
611
|
+
#
|
612
|
+
# === Return
|
613
|
+
# true:: Always return true
|
614
|
+
def disable_offline_mode
|
615
|
+
@offline_handler.disable if @options[:offline_queueing]
|
616
|
+
end
|
617
|
+
|
618
|
+
# Update the time this agent last received a request or response message
|
619
|
+
# Also forward this message receipt notification to any callbacks that have registered
|
620
|
+
#
|
621
|
+
# === Block
|
622
|
+
# Optional block without parameters that is activated when a message is received
|
623
|
+
#
|
624
|
+
# === Return
|
625
|
+
# true:: Always return true
|
626
|
+
def message_received(&callback)
|
627
|
+
@connectivity_checker.message_received(&callback)
|
173
628
|
end
|
174
629
|
|
175
630
|
# Send a request to a single target or multiple targets with no response expected other
|
@@ -194,9 +649,11 @@ module RightScale
|
|
194
649
|
# defaults to :any
|
195
650
|
#
|
196
651
|
# === Block
|
197
|
-
# Optional block used to process routing
|
198
|
-
# result(Result):: Response with an OperationResult of RETRY, NON_DELIVERY, or ERROR,
|
199
|
-
#
|
652
|
+
# Optional block used to process routing responses asynchronously with the following parameter:
|
653
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
654
|
+
# with an initial SUCCESS response containing the targets to which the mapper published the
|
655
|
+
# request and any additional responses indicating any failures to actually route the request
|
656
|
+
# to those targets, use RightScale::OperationResult.from_results to decode
|
200
657
|
#
|
201
658
|
# === Return
|
202
659
|
# true:: Always return true
|
@@ -227,9 +684,11 @@ module RightScale
|
|
227
684
|
# defaults to :any
|
228
685
|
#
|
229
686
|
# === Block
|
230
|
-
# Optional block used to process routing
|
231
|
-
# result(Result):: Response with an OperationResult of RETRY, NON_DELIVERY, or ERROR,
|
232
|
-
#
|
687
|
+
# Optional block used to process routing responses asynchronously with the following parameter:
|
688
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
689
|
+
# with an initial SUCCESS response containing the targets to which the mapper published the
|
690
|
+
# request and any additional responses indicating any failures to actually route the request
|
691
|
+
# to those targets, use RightScale::OperationResult.from_results to decode
|
233
692
|
#
|
234
693
|
# === Return
|
235
694
|
# true:: Always return true
|
@@ -316,17 +775,17 @@ module RightScale
|
|
316
775
|
if response.is_a?(Result)
|
317
776
|
if result = OperationResult.from_results(response)
|
318
777
|
if result.non_delivery?
|
319
|
-
@
|
778
|
+
@non_delivery_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
320
779
|
elsif result.error?
|
321
|
-
@
|
780
|
+
@result_error_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
322
781
|
end
|
323
|
-
@
|
782
|
+
@result_stats.update(result.status)
|
324
783
|
else
|
325
|
-
@
|
784
|
+
@result_stats.update(response.results.nil? ? "nil" : response.results)
|
326
785
|
end
|
327
786
|
|
328
787
|
if handler = @pending_requests[token]
|
329
|
-
if result && result.non_delivery? && handler
|
788
|
+
if result && result.non_delivery? && handler.kind == :send_retryable_request &&
|
330
789
|
[OperationResult::TARGET_NOT_CONNECTED, OperationResult::TTL_EXPIRATION].include?(result.content)
|
331
790
|
# Log and ignore so that timeout retry mechanism continues
|
332
791
|
# Leave purging of associated request until final response, i.e., success response or retry timeout
|
@@ -343,95 +802,52 @@ module RightScale
|
|
343
802
|
true
|
344
803
|
end
|
345
804
|
|
346
|
-
#
|
347
|
-
#
|
348
|
-
# Idempotent
|
805
|
+
# Publish request
|
806
|
+
# Use mandatory flag to request return of message if it cannot be delivered
|
349
807
|
#
|
350
|
-
# ===
|
351
|
-
#
|
352
|
-
|
353
|
-
if offline?
|
354
|
-
if @flushing_queue
|
355
|
-
# If we were in offline mode then switched back to online but are still in the
|
356
|
-
# process of flushing the in memory queue and are now switching to offline mode
|
357
|
-
# again then stop the flushing
|
358
|
-
@stop_flushing_queue = true
|
359
|
-
end
|
360
|
-
else
|
361
|
-
Log.info("[offline] Disconnect from broker detected, entering offline mode")
|
362
|
-
Log.info("[offline] Messages will be queued in memory until connection to broker is re-established")
|
363
|
-
@offlines.update
|
364
|
-
@queue ||= [] # ensure queue is valid without losing any messages when going offline
|
365
|
-
@queueing_mode = :offline
|
366
|
-
@restart_vote_timer ||= EM::Timer.new(RESTART_VOTE_DELAY) { vote_to_restart(timer_trigger=true) }
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
# Switch back to sending requests to mapper after in memory queue gets flushed
|
371
|
-
# Idempotent
|
808
|
+
# === Parameters
|
809
|
+
# request(Push|Request):: Packet to be sent
|
810
|
+
# ids(Array|nil):: Identity of specific brokers to choose from, or nil if any okay
|
372
811
|
#
|
373
812
|
# === Return
|
374
|
-
#
|
375
|
-
def
|
376
|
-
|
377
|
-
|
378
|
-
@
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
813
|
+
# ids(Array):: Identity of brokers published to
|
814
|
+
def publish(request, ids = nil)
|
815
|
+
begin
|
816
|
+
exchange = {:type => :fanout, :name => "request", :options => {:durable => true, :no_declare => @secure}}
|
817
|
+
ids = @broker.publish(exchange, request, :persistent => request.persistent, :mandatory => true,
|
818
|
+
:log_filter => [:tags, :target, :tries, :persistent], :brokers => ids)
|
819
|
+
rescue HABrokerClient::NoConnectedBrokers => e
|
820
|
+
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e)
|
821
|
+
ids = []
|
822
|
+
rescue Exception => e
|
823
|
+
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e, :trace)
|
824
|
+
@exception_stats.track("publish", e, request)
|
825
|
+
ids = []
|
385
826
|
end
|
386
|
-
|
387
|
-
end
|
388
|
-
|
389
|
-
# Get age of youngest pending request
|
390
|
-
#
|
391
|
-
# === Return
|
392
|
-
# age(Integer|nil):: Age in seconds of youngest request, or nil if no pending requests
|
393
|
-
def request_age
|
394
|
-
time = Time.now
|
395
|
-
age = nil
|
396
|
-
@pending_requests.each_value do |request|
|
397
|
-
seconds = time - request[:receive_time]
|
398
|
-
age = seconds.to_i if age.nil? || seconds < age
|
399
|
-
end
|
400
|
-
age
|
827
|
+
ids
|
401
828
|
end
|
402
829
|
|
403
830
|
# Take any actions necessary to quiesce mapper interaction in preparation
|
404
831
|
# for agent termination but allow message receipt to continue
|
405
832
|
#
|
406
833
|
# === Return
|
407
|
-
# (Array):: Number of pending requests and age of youngest request
|
834
|
+
# (Array):: Number of pending non-push requests and age of youngest request
|
408
835
|
def terminate
|
409
|
-
@
|
410
|
-
@
|
411
|
-
|
412
|
-
|
413
|
-
@pending_ping = nil
|
414
|
-
end
|
415
|
-
if @timer
|
416
|
-
@timer.cancel
|
417
|
-
@timer = nil
|
418
|
-
end
|
419
|
-
if @restart_vote_timer
|
420
|
-
@restart_vote_timer.cancel
|
421
|
-
@restart_vote_timer = nil
|
422
|
-
end
|
423
|
-
[@pending_requests.size, request_age]
|
836
|
+
@offline_handler.terminate
|
837
|
+
@connectivity_checker.terminate
|
838
|
+
pending = @pending_requests.kind(PendingRequests::REQUEST_KINDS)
|
839
|
+
[pending.size, pending.youngest_age]
|
424
840
|
end
|
425
841
|
|
426
|
-
# Create displayable dump of unfinished request information
|
842
|
+
# Create displayable dump of unfinished non-push request information
|
427
843
|
# Truncate list if there are more than 50 requests
|
428
844
|
#
|
429
845
|
# === Return
|
430
846
|
# info(Array(String)):: Receive time and token for each request in descending time order
|
431
847
|
def dump_requests
|
432
848
|
info = []
|
433
|
-
@pending_requests.each do |token, request|
|
434
|
-
info << "#{request
|
849
|
+
@pending_requests.kind(PendingRequests::REQUEST_KINDS).each do |token, request|
|
850
|
+
info << "#{request.receive_time.localtime} <#{token}>"
|
435
851
|
end
|
436
852
|
info.sort.reverse
|
437
853
|
info = info[0..49] + ["..."] if info.size > 50
|
@@ -458,7 +874,8 @@ module RightScale
|
|
458
874
|
# with percentage breakdown per kind, or nil if none
|
459
875
|
# "requests"(Hash|nil):: Request activity stats with keys "total", "percent", "last", and "rate"
|
460
876
|
# with percentage breakdown per request type, or nil if none
|
461
|
-
# "requests pending"(Hash|nil):: Number of requests waiting for response and age of oldest,
|
877
|
+
# "requests pending"(Hash|nil):: Number of requests waiting for response and age of oldest,
|
878
|
+
# or nil if none
|
462
879
|
# "response time"(Float):: Average number of seconds to respond to a request recently
|
463
880
|
# "result errors"(Hash|nil):: Error result activity stats with keys "total", "percent", "last",
|
464
881
|
# and 'rate' with percentage breakdown per error, or nil if none
|
@@ -467,25 +884,28 @@ module RightScale
|
|
467
884
|
# "retries"(Hash|nil):: Retry activity stats with keys "total", "percent", "last", and "rate"
|
468
885
|
# with percentage breakdown per request type, or nil if none
|
469
886
|
def stats(reset = false)
|
470
|
-
offlines = @
|
471
|
-
offlines.merge!("duration" => @
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
887
|
+
offlines = @offline_stats.all
|
888
|
+
offlines.merge!("duration" => @offline_stats.avg_duration) if offlines
|
889
|
+
if @pending_requests.size > 0
|
890
|
+
pending = {}
|
891
|
+
pending["pushes"] = @pending_requests.kind(PendingRequests::PUSH_KINDS).size
|
892
|
+
requests = @pending_requests.kind(PendingRequests::REQUEST_KINDS)
|
893
|
+
if (pending["requests"] = requests.size) > 0
|
894
|
+
pending["oldest age"] = requests.oldest_age
|
895
|
+
end
|
476
896
|
end
|
477
897
|
stats = {
|
478
|
-
"exceptions" => @
|
479
|
-
"non-deliveries" => @
|
898
|
+
"exceptions" => @exception_stats.stats,
|
899
|
+
"non-deliveries" => @non_delivery_stats.all,
|
480
900
|
"offlines" => offlines,
|
481
|
-
"pings" => @
|
901
|
+
"pings" => @ping_stats.all,
|
482
902
|
"request kinds" => @request_kinds.all,
|
483
|
-
"requests" => @
|
484
|
-
"requests pending" =>
|
485
|
-
"response time" => @
|
486
|
-
"result errors" => @
|
487
|
-
"results" => @
|
488
|
-
"retries" => @
|
903
|
+
"requests" => @request_stats.all,
|
904
|
+
"requests pending" => pending,
|
905
|
+
"response time" => @request_stats.avg_duration,
|
906
|
+
"result errors" => @result_error_stats.all,
|
907
|
+
"results" => @result_stats.all,
|
908
|
+
"retries" => @retry_stats.all
|
489
909
|
}
|
490
910
|
reset_stats if reset
|
491
911
|
stats
|
@@ -498,15 +918,15 @@ module RightScale
|
|
498
918
|
# === Return
|
499
919
|
# true:: Always return true
|
500
920
|
def reset_stats
|
501
|
-
@
|
502
|
-
@
|
503
|
-
@
|
504
|
-
@
|
505
|
-
@
|
506
|
-
@
|
507
|
-
@
|
921
|
+
@ping_stats = ActivityStats.new
|
922
|
+
@retry_stats = ActivityStats.new
|
923
|
+
@request_stats = ActivityStats.new
|
924
|
+
@result_stats = ActivityStats.new
|
925
|
+
@result_error_stats = ActivityStats.new
|
926
|
+
@non_delivery_stats = ActivityStats.new
|
927
|
+
@offline_stats = ActivityStats.new(measure_rate = false)
|
508
928
|
@request_kinds = ActivityStats.new(measure_rate = false)
|
509
|
-
@
|
929
|
+
@exception_stats = ExceptionStats.new(@agent, @options[:exception_callback])
|
510
930
|
true
|
511
931
|
end
|
512
932
|
|
@@ -528,9 +948,11 @@ module RightScale
|
|
528
948
|
# defaults to :any
|
529
949
|
#
|
530
950
|
# === Block
|
531
|
-
# Optional block used to process routing
|
532
|
-
# result(Result):: Response with an OperationResult of RETRY, NON_DELIVERY, or ERROR,
|
533
|
-
#
|
951
|
+
# Optional block used to process routing responses asynchronously with the following parameter:
|
952
|
+
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
953
|
+
# with an initial SUCCESS response containing the targets to which the mapper published the
|
954
|
+
# request and any additional responses indicating any failures to actually route the request
|
955
|
+
# to those targets, use RightScale::OperationResult.from_results to decode
|
534
956
|
#
|
535
957
|
# === Return
|
536
958
|
# true:: Always return true
|
@@ -540,10 +962,10 @@ module RightScale
|
|
540
962
|
def build_push(kind, type, payload = nil, target = nil, &callback)
|
541
963
|
validate_target(target, allow_selector = true)
|
542
964
|
if should_queue?
|
543
|
-
queue_request(
|
965
|
+
@offline_handler.queue_request(kind, type, payload, target, callback)
|
544
966
|
else
|
545
967
|
method = type.split('/').last
|
546
|
-
received_at = @
|
968
|
+
received_at = @request_stats.update(method)
|
547
969
|
push = Push.new(type, payload)
|
548
970
|
push.from = @identity
|
549
971
|
push.token = AgentIdentity.generate
|
@@ -556,11 +978,10 @@ module RightScale
|
|
556
978
|
end
|
557
979
|
push.persistent = kind == :send_persistent_push
|
558
980
|
@request_kinds.update((push.selector == :all ? kind.to_s.sub(/push/, "fanout") : kind.to_s)[5..-1])
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
} if callback
|
981
|
+
if callback
|
982
|
+
push.confirm = true
|
983
|
+
@pending_requests[push.token] = PendingRequest.new(kind, received_at, callback)
|
984
|
+
end
|
564
985
|
publish(push)
|
565
986
|
end
|
566
987
|
true
|
@@ -594,12 +1015,12 @@ module RightScale
|
|
594
1015
|
def build_request(kind, type, payload, target, &callback)
|
595
1016
|
validate_target(target, allow_selector = false)
|
596
1017
|
if should_queue?
|
597
|
-
queue_request(
|
1018
|
+
@offline_handler.queue_request(kind, type, payload, target, callback)
|
598
1019
|
else
|
599
1020
|
method = type.split('/').last
|
600
1021
|
token = AgentIdentity.generate
|
601
1022
|
non_duplicate = kind == :send_persistent_request
|
602
|
-
received_at = @
|
1023
|
+
received_at = @request_stats.update(method, token)
|
603
1024
|
@request_kinds.update(kind.to_s[5..-1])
|
604
1025
|
|
605
1026
|
# Using next_tick to ensure on primary thread since using @pending_requests
|
@@ -617,10 +1038,7 @@ module RightScale
|
|
617
1038
|
end
|
618
1039
|
request.expires_at = Time.now.to_i + @options[:time_to_live] if !non_duplicate && @options[:time_to_live] && @options[:time_to_live] != 0
|
619
1040
|
request.persistent = non_duplicate
|
620
|
-
@pending_requests[token] =
|
621
|
-
:response_handler => callback,
|
622
|
-
:receive_time => received_at,
|
623
|
-
:request_kind => kind}
|
1041
|
+
@pending_requests[token] = PendingRequest.new(kind, received_at, callback)
|
624
1042
|
if non_duplicate
|
625
1043
|
publish(request)
|
626
1044
|
else
|
@@ -628,7 +1046,7 @@ module RightScale
|
|
628
1046
|
end
|
629
1047
|
rescue Exception => e
|
630
1048
|
Log.error("Failed to send #{type} #{kind.to_s}", e, :trace)
|
631
|
-
@
|
1049
|
+
@exception_stats.track(kind.to_s, e, request)
|
632
1050
|
end
|
633
1051
|
end
|
634
1052
|
end
|
@@ -695,7 +1113,7 @@ module RightScale
|
|
695
1113
|
ids = publish(request)
|
696
1114
|
|
697
1115
|
if @retry_interval && @retry_timeout && parent && !ids.empty?
|
698
|
-
interval = [(@retry_interval * multiplier) + (@
|
1116
|
+
interval = [(@retry_interval * multiplier) + (@request_stats.avg_duration || 0), @retry_timeout - elapsed].min
|
699
1117
|
EM.add_timer(interval) do
|
700
1118
|
begin
|
701
1119
|
if handler = @pending_requests[parent]
|
@@ -704,52 +1122,27 @@ module RightScale
|
|
704
1122
|
if elapsed < @retry_timeout
|
705
1123
|
request.tries << request.token
|
706
1124
|
request.token = AgentIdentity.generate
|
707
|
-
@pending_requests[parent]
|
1125
|
+
@pending_requests[parent].retry_parent = parent if count == 1
|
708
1126
|
@pending_requests[request.token] = @pending_requests[parent]
|
709
1127
|
publish_with_timeout_retry(request, parent, count, multiplier * RETRY_BACKOFF_FACTOR, elapsed)
|
710
|
-
@
|
1128
|
+
@retry_stats.update(request.type.split('/').last)
|
711
1129
|
else
|
712
1130
|
Log.warning("RE-SEND TIMEOUT after #{elapsed.to_i} seconds for #{request.to_s([:tags, :target, :tries])}")
|
713
1131
|
result = OperationResult.non_delivery(OperationResult::RETRY_TIMEOUT)
|
714
|
-
@
|
1132
|
+
@non_delivery_stats.update(result.content)
|
715
1133
|
handle_response(Result.new(request.token, request.reply_to, result, @identity))
|
716
1134
|
end
|
717
|
-
|
1135
|
+
@connectivity_checker.check(ids.first) if count == 1
|
718
1136
|
end
|
719
1137
|
rescue Exception => e
|
720
1138
|
Log.error("Failed retry for #{request.token}", e, :trace)
|
721
|
-
@
|
1139
|
+
@exception_stats.track("retry", e, request)
|
722
1140
|
end
|
723
1141
|
end
|
724
1142
|
end
|
725
1143
|
true
|
726
1144
|
end
|
727
1145
|
|
728
|
-
# Publish request
|
729
|
-
# Use mandatory flag to request return of message if it cannot be delivered
|
730
|
-
#
|
731
|
-
# === Parameters
|
732
|
-
# request(Push|Request):: Packet to be sent
|
733
|
-
# ids(Array|nil):: Identity of specific brokers to choose from, or nil if any okay
|
734
|
-
#
|
735
|
-
# === Return
|
736
|
-
# ids(Array):: Identity of brokers published to
|
737
|
-
def publish(request, ids = nil)
|
738
|
-
begin
|
739
|
-
exchange = {:type => :fanout, :name => "request", :options => {:durable => true, :no_declare => @secure}}
|
740
|
-
ids = @broker.publish(exchange, request, :persistent => request.persistent, :mandatory => true,
|
741
|
-
:log_filter => [:tags, :target, :tries, :persistent], :brokers => ids)
|
742
|
-
rescue HABrokerClient::NoConnectedBrokers => e
|
743
|
-
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e)
|
744
|
-
ids = []
|
745
|
-
rescue Exception => e
|
746
|
-
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e, :trace)
|
747
|
-
@exceptions.track("publish", e, request)
|
748
|
-
ids = []
|
749
|
-
end
|
750
|
-
ids
|
751
|
-
end
|
752
|
-
|
753
1146
|
# Deliver the response and remove associated request(s) from pending
|
754
1147
|
# Use defer thread instead of primary if not single threaded, consistent with dispatcher,
|
755
1148
|
# so that all shared data is accessed from the same thread
|
@@ -763,113 +1156,22 @@ module RightScale
|
|
763
1156
|
# === Return
|
764
1157
|
# true:: Always return true
|
765
1158
|
def deliver(response, handler)
|
766
|
-
@
|
1159
|
+
@request_stats.finish(handler.receive_time, response.token)
|
767
1160
|
|
768
|
-
@pending_requests.delete(response.token)
|
769
|
-
if parent = handler
|
770
|
-
@pending_requests.reject! { |k, v| k == parent || v
|
1161
|
+
@pending_requests.delete(response.token) if PendingRequests::REQUEST_KINDS.include?(handler.kind)
|
1162
|
+
if parent = handler.retry_parent
|
1163
|
+
@pending_requests.reject! { |k, v| k == parent || v.retry_parent == parent }
|
771
1164
|
end
|
772
1165
|
|
773
|
-
if handler
|
1166
|
+
if handler.response_handler
|
774
1167
|
EM.__send__(@single_threaded ? :next_tick : :defer) do
|
775
1168
|
begin
|
776
|
-
handler
|
777
|
-
rescue Exception => e
|
778
|
-
Log.error("Failed processing response {response.to_s([])}", e, :trace)
|
779
|
-
@exceptions.track("response", e, response)
|
780
|
-
end
|
781
|
-
end
|
782
|
-
end
|
783
|
-
true
|
784
|
-
end
|
785
|
-
|
786
|
-
# Check whether broker connection is usable by pinging a mapper via that broker
|
787
|
-
# Attempt to reconnect if ping does not respond in PING_TIMEOUT seconds
|
788
|
-
# Ignore request if already checking a connection
|
789
|
-
# Only to be called from primary thread
|
790
|
-
#
|
791
|
-
# === Parameters
|
792
|
-
# id(String):: Identity of specific broker to use to send ping, defaults to any
|
793
|
-
# currently connected broker
|
794
|
-
#
|
795
|
-
# === Return
|
796
|
-
# true:: Always return true
|
797
|
-
def check_connection(id = nil)
|
798
|
-
unless @terminating || @pending_ping || (id && !@broker.connected?(id))
|
799
|
-
@pending_ping = EM::Timer.new(PING_TIMEOUT) do
|
800
|
-
begin
|
801
|
-
@pings.update("timeout")
|
802
|
-
@pending_ping = nil
|
803
|
-
Log.warning("Mapper ping via broker #{id} timed out after #{PING_TIMEOUT} seconds, attempting to reconnect")
|
804
|
-
host, port, index, priority, _ = @broker.identity_parts(id)
|
805
|
-
@agent.connect(host, port, index, priority, force = true)
|
806
|
-
rescue Exception => e
|
807
|
-
Log.error("Failed to reconnect to broker #{id}", e, :trace)
|
808
|
-
@exceptions.track("ping timeout", e)
|
809
|
-
end
|
810
|
-
end
|
811
|
-
|
812
|
-
handler = lambda do |_|
|
813
|
-
begin
|
814
|
-
if @pending_ping
|
815
|
-
@pings.update("success")
|
816
|
-
@pending_ping.cancel
|
817
|
-
@pending_ping = nil
|
818
|
-
end
|
1169
|
+
handler.response_handler.call(response)
|
819
1170
|
rescue Exception => e
|
820
|
-
Log.error("Failed
|
821
|
-
@
|
1171
|
+
Log.error("Failed processing response #{response.to_s([])}", e, :trace)
|
1172
|
+
@exception_stats.track("response", e, response)
|
822
1173
|
end
|
823
1174
|
end
|
824
|
-
|
825
|
-
request = Request.new("/mapper/ping", nil, {:from => @identity, :token => AgentIdentity.generate})
|
826
|
-
@pending_requests[request.token] = {:response_handler => handler, :receive_time => Time.now}
|
827
|
-
ids = [id] if id
|
828
|
-
id = publish(request, ids).first
|
829
|
-
end
|
830
|
-
true
|
831
|
-
end
|
832
|
-
|
833
|
-
# Vote for restart and reset trigger
|
834
|
-
#
|
835
|
-
# === Parameters
|
836
|
-
# timer_trigger(Boolean):: true if vote was triggered by timer, false if it
|
837
|
-
# was triggered by number of messages in in-memory queue
|
838
|
-
#
|
839
|
-
# === Return
|
840
|
-
# true:: Always return true
|
841
|
-
def vote_to_restart(timer_trigger)
|
842
|
-
if restart_vote = @options[:restart_callback]
|
843
|
-
restart_vote.call
|
844
|
-
if timer_trigger
|
845
|
-
@restart_vote_timer = EM::Timer.new(RESTART_VOTE_DELAY) { vote_to_restart(timer_trigger = true) }
|
846
|
-
else
|
847
|
-
@restart_vote_count = 0
|
848
|
-
end
|
849
|
-
end
|
850
|
-
true
|
851
|
-
end
|
852
|
-
|
853
|
-
# Is agent currently offline?
|
854
|
-
#
|
855
|
-
# === Return
|
856
|
-
# offline(Boolean):: true if agent is disconnected or not initialized
|
857
|
-
def offline?
|
858
|
-
offline = @queueing_mode == :offline || !@queue_running
|
859
|
-
end
|
860
|
-
|
861
|
-
# Start timer that waits for inactive messaging period to end before checking connectivity
|
862
|
-
#
|
863
|
-
# === Return
|
864
|
-
# true:: Always return true
|
865
|
-
def restart_inactivity_timer
|
866
|
-
@timer.cancel if @timer
|
867
|
-
@timer = EM::Timer.new(@ping_interval) do
|
868
|
-
begin
|
869
|
-
check_connection
|
870
|
-
rescue Exception => e
|
871
|
-
Log.error("Failed connectivity check", e, :trace)
|
872
|
-
end
|
873
1175
|
end
|
874
1176
|
true
|
875
1177
|
end
|
@@ -879,57 +1181,7 @@ module RightScale
|
|
879
1181
|
# === Return
|
880
1182
|
# (Boolean):: true if should queue request, otherwise false
|
881
1183
|
def should_queue?
|
882
|
-
@options[:offline_queueing] &&
|
883
|
-
end
|
884
|
-
|
885
|
-
# Queue given request in memory
|
886
|
-
#
|
887
|
-
# === Parameters
|
888
|
-
# request(Hash):: Request to be stored
|
889
|
-
#
|
890
|
-
# === Return
|
891
|
-
# true:: Always return true
|
892
|
-
def queue_request(request)
|
893
|
-
Log.info("[offline] Queuing request: #{request.inspect}")
|
894
|
-
@restart_vote_count += 1 if @queue_running
|
895
|
-
vote_to_restart(timer_trigger = false) if @restart_vote_count >= MAX_QUEUED_REQUESTS
|
896
|
-
if @queue_initializing
|
897
|
-
# We are in the initialization callback, requests should be put at the head of the queue
|
898
|
-
@queue.unshift(request)
|
899
|
-
else
|
900
|
-
@queue << request
|
901
|
-
end
|
902
|
-
true
|
903
|
-
end
|
904
|
-
|
905
|
-
# Flush in memory queue of requests that were stored while in offline mode
|
906
|
-
# Do this asynchronously to allow for agents to respond to requests
|
907
|
-
# Once all in-memory requests have been flushed, switch off offline mode
|
908
|
-
#
|
909
|
-
# === Return
|
910
|
-
# true:: Always return true
|
911
|
-
def flush_queue
|
912
|
-
if @stop_flushing_queue
|
913
|
-
@stop_flushing_queue = false
|
914
|
-
@flushing_queue = false
|
915
|
-
else
|
916
|
-
Log.info("[offline] Starting to flush request queue of size #{@queue.size}") unless @queueing_mode == :initializing
|
917
|
-
unless @queue.empty?
|
918
|
-
r = @queue.shift
|
919
|
-
if r[:callback]
|
920
|
-
Sender.instance.__send__(r[:kind], r[:type], r[:payload], r[:target]) { |res| r[:callback].call(res) }
|
921
|
-
else
|
922
|
-
Sender.instance.__send__(r[:kind], r[:type], r[:payload], r[:target])
|
923
|
-
end
|
924
|
-
end
|
925
|
-
if @queue.empty?
|
926
|
-
Log.info("[offline] Request queue flushed, resuming normal operations") unless @queueing_mode == :initializing
|
927
|
-
@queueing_mode = :online
|
928
|
-
@flushing_queue = false
|
929
|
-
else
|
930
|
-
EM.next_tick { flush_queue }
|
931
|
-
end
|
932
|
-
end
|
1184
|
+
@options[:offline_queueing] && @offline_handler.queueing?
|
933
1185
|
end
|
934
1186
|
|
935
1187
|
end # Sender
|