gearman-ruby 3.0.8 → 4.0.2
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 +8 -8
- data/.travis.yml +5 -0
- data/CHANGELOG.md +6 -4
- data/README.md +111 -0
- data/examples/client.rb +1 -2
- data/examples/client_reverse_nohost.rb +30 -0
- data/examples/{client_reverse.rb → client_reverse_wait.rb} +9 -11
- data/examples/worker.rb +8 -5
- data/examples/worker_reverse_string.rb +12 -17
- data/gearman-ruby.gemspec +3 -5
- data/lib/gearman.rb +17 -77
- data/lib/gearman/client.rb +129 -147
- data/lib/gearman/connection.rb +158 -0
- data/lib/gearman/connection_pool.rb +131 -0
- data/lib/gearman/exceptions.rb +24 -0
- data/lib/gearman/logging.rb +19 -0
- data/lib/gearman/packet.rb +61 -0
- data/lib/gearman/task.rb +1 -1
- data/lib/gearman/task_set.rb +67 -0
- data/lib/gearman/version.rb +1 -1
- data/lib/gearman/worker.rb +185 -412
- data/lib/gearman/worker/ability.rb +55 -0
- data/lib/gearman/worker/callbacks.rb +39 -0
- data/lib/gearman/worker/job.rb +44 -0
- data/spec/client_spec.rb +32 -20
- data/spec/connection_pool_spec.rb +55 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/task_spec.rb +10 -0
- data/spec/taskset_spec.rb +2 -2
- metadata +18 -37
- data/HOWTO +0 -146
- data/README +0 -9
- data/TODO +0 -8
- data/VERSION.yml +0 -4
- data/examples/calculus_client.rb +0 -39
- data/examples/calculus_worker.rb +0 -45
- data/examples/client.php +0 -23
- data/examples/client_background.rb +0 -14
- data/examples/client_data.rb +0 -16
- data/examples/client_epoch.rb +0 -23
- data/examples/client_exception.rb +0 -19
- data/examples/client_prefix.rb +0 -17
- data/examples/gearman_environment.sh +0 -25
- data/examples/scale_image.rb +0 -31
- data/examples/scale_image_worker.rb +0 -34
- data/examples/server.rb +0 -15
- data/examples/worker_data.rb +0 -16
- data/examples/worker_exception.rb +0 -14
- data/examples/worker_prefix.rb +0 -25
- data/examples/worker_reverse_to_file.rb +0 -18
- data/examples/worker_signals.rb +0 -36
- data/lib/gearman/taskset.rb +0 -293
- data/lib/gearman/util.rb +0 -211
- data/spec/util_spec.rb +0 -67
data/lib/gearman/version.rb
CHANGED
data/lib/gearman/worker.rb
CHANGED
@@ -1,475 +1,248 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'set'
|
4
|
-
|
5
|
-
require '
|
4
|
+
|
5
|
+
require 'gearman/worker/callbacks'
|
6
|
+
require 'gearman/worker/ability'
|
7
|
+
require 'gearman/worker/job'
|
6
8
|
|
7
9
|
module Gearman
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
# == Usage
|
15
|
-
# require 'gearman'
|
16
|
-
#
|
17
|
-
# w = Gearman::Worker.new('127.0.0.1')
|
18
|
-
#
|
19
|
-
# # Add a handler for a "sleep" function that takes a single argument, the
|
20
|
-
# # number of seconds to sleep before reporting success.
|
21
|
-
# w.add_ability('sleep') do |data,job|
|
22
|
-
# seconds = data
|
23
|
-
# (1..seconds.to_i).each do |i|
|
24
|
-
# sleep 1
|
25
|
-
# # Report our progress to the job server every second.
|
26
|
-
# job.report_status(i, seconds)
|
27
|
-
# end
|
28
|
-
# # Report success.
|
29
|
-
# true
|
30
|
-
# end
|
31
|
-
# loop { w.work }
|
32
|
-
class Worker
|
33
|
-
# = Ability
|
34
|
-
#
|
35
|
-
# == Description
|
36
|
-
# Information about an ability that we possess.
|
37
|
-
class Ability
|
11
|
+
class Worker
|
12
|
+
include Logging
|
13
|
+
include Callbacks
|
14
|
+
|
38
15
|
##
|
39
|
-
# Create a new
|
16
|
+
# Create a new worker.
|
40
17
|
#
|
41
|
-
# @param
|
42
|
-
# @param
|
43
|
-
def initialize(
|
44
|
-
@
|
45
|
-
@
|
18
|
+
# @param job_servers "host:port"; either a single server or an array
|
19
|
+
# @param opts hash of additional options
|
20
|
+
def initialize(job_servers=nil, opts={})
|
21
|
+
@abilities = {}
|
22
|
+
@client_id = opts[:client_id] || generate_id
|
23
|
+
@connection_pool = ConnectionPool.new(job_servers)
|
24
|
+
@network_timeout_sec = opts[:network_timeout_sec] || 10
|
25
|
+
@reconnect_sec = opts[:reconnect_sec] || 30
|
26
|
+
@status = :preparing
|
27
|
+
@worker_enabled = true
|
28
|
+
|
29
|
+
# Add callback for when connections occur -- register abilities and send client id
|
30
|
+
@connection_pool.on_connection do |connection|
|
31
|
+
connection.send_update(Packet.pack_request(:set_client_id, @client_id))
|
32
|
+
@abilities.each do |func_name, ability|
|
33
|
+
announce_ability(func_name, ability.timeout, connection)
|
34
|
+
end
|
35
|
+
end
|
46
36
|
end
|
47
|
-
|
37
|
+
|
38
|
+
attr_accessor :client_id, :reconnect_sec, :network_timeout_sec, :worker_enabled, :status
|
48
39
|
|
49
40
|
##
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
def run(data, job)
|
55
|
-
@block.call(data, job)
|
41
|
+
# @return A random string of 30 characters from a-z
|
42
|
+
def generate_id
|
43
|
+
chars = ('a'..'z').to_a
|
44
|
+
Array.new(30) { chars[rand(chars.size)] }.join
|
56
45
|
end
|
57
|
-
|
58
|
-
end
|
59
46
|
|
60
|
-
# = Job
|
61
|
-
#
|
62
|
-
# == Description
|
63
|
-
# Interface to allow a worker to report information to a job server.
|
64
|
-
class Job
|
65
47
|
##
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@socket = sock
|
73
|
-
@handle = handle
|
48
|
+
# Generate CAN_DO (or CAN_DO_TIMEOUT) packet and submit it
|
49
|
+
def announce_ability(func_name, timeout, connection)
|
50
|
+
cmd = timeout ? :can_do_timeout : :can_do
|
51
|
+
arg = timeout ? "#{func_name}\0#{timeout.to_s}" : func_name
|
52
|
+
connection.send_update(Packet.pack_request(cmd, arg))
|
53
|
+
logger.debug "Announced ability #{func_name}"
|
74
54
|
end
|
75
55
|
|
76
56
|
##
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
57
|
+
# Add a new ability, announcing it to job servers.
|
58
|
+
#
|
59
|
+
# The passed-in block of code will be executed for jobs of this function
|
60
|
+
# type. It'll receive two arguments, the data supplied by the client and
|
61
|
+
# a Job object. If it returns nil or false, the server will be informed
|
62
|
+
# that the job has failed; otherwise the return value of the block will
|
63
|
+
# be passed back to the client in String form.
|
64
|
+
#
|
65
|
+
# @param func_name function name (without prefix)
|
66
|
+
# @param timeout the server will give up on us if we don't finish
|
67
|
+
# a task in this many seconds
|
68
|
+
# @param block Block to associate with the function
|
69
|
+
def add_ability(func_name, timeout=nil, &block)
|
70
|
+
@abilities[func_name] = Ability.new(func_name, block, timeout)
|
71
|
+
@connection_pool.with_all_connections do |connection|
|
72
|
+
announce_ability(func_name, timeout, connection)
|
73
|
+
end
|
83
74
|
end
|
84
75
|
|
85
76
|
##
|
86
|
-
#
|
87
|
-
def
|
88
|
-
|
89
|
-
Util.send_request(@socket, req)
|
90
|
-
self
|
77
|
+
# Callback for after an ability runs
|
78
|
+
def after_ability(func, &block)
|
79
|
+
abilities[func].after_complete(block)
|
91
80
|
end
|
92
81
|
|
93
82
|
##
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
83
|
+
# Let job servers know that we're no longer able to do something via CANT_DO
|
84
|
+
#
|
85
|
+
# @param func function name
|
86
|
+
def remove_ability(func)
|
87
|
+
@abilities.delete(func)
|
88
|
+
req = Packet.pack_request(:cant_do, func)
|
89
|
+
@connection_pool.with_all_connections do |connection|
|
90
|
+
connection.send_update(req)
|
91
|
+
end
|
99
92
|
end
|
100
|
-
end
|
101
|
-
|
102
|
-
module Callbacks
|
103
93
|
|
104
|
-
|
105
|
-
|
94
|
+
##
|
95
|
+
# Handle a job_assign packet.
|
96
|
+
#
|
97
|
+
# @param data data in the packet
|
98
|
+
# @param connection Connection where the data originated
|
99
|
+
def handle_job_assign(data, connection)
|
100
|
+
handle, func, data = data.split("\0", 3)
|
101
|
+
|
102
|
+
if not func
|
103
|
+
logger.error "Ignoring JOB_ASSIGN with no function from #{connection}"
|
104
|
+
return false
|
105
|
+
end
|
106
106
|
|
107
|
-
|
108
|
-
|
107
|
+
if not handle
|
108
|
+
logger.error "Ignoring JOB_ASSIGN with no job handle from #{connection}"
|
109
|
+
return false
|
109
110
|
end
|
110
111
|
|
111
|
-
|
112
|
-
callback = instance_variable_get("@__on_#{event}")
|
113
|
-
return unless callback
|
112
|
+
logger.info "Got JOB_ASSIGN with handle #{handle} and #{data.size} byte(s) from #{connection}"
|
114
113
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
114
|
+
ability = @abilities[func]
|
115
|
+
|
116
|
+
if ability == nil
|
117
|
+
logger.error "Ignoring JOB_ASSIGN for unsupported function #{func} with handle #{handle} from #{connection}"
|
118
|
+
connection.send_update(Packet.pack_request(:work_fail, handle))
|
119
|
+
return false
|
120
120
|
end
|
121
|
-
end
|
122
121
|
|
123
|
-
|
122
|
+
exception = nil
|
123
|
+
begin
|
124
|
+
ret = ability.run(data, Job.new(connection, handle))
|
125
|
+
rescue Exception => e
|
126
|
+
exception = e
|
127
|
+
logger.debug "Exception: #{e}\n#{e.backtrace.join("\n")}\n"
|
128
|
+
end
|
124
129
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
##
|
139
|
-
# Create a new worker.
|
140
|
-
#
|
141
|
-
# @param job_servers "host:port"; either a single server or an array
|
142
|
-
# @param opts hash of additional options
|
143
|
-
def initialize(job_servers=nil, opts={})
|
144
|
-
chars = ('a'..'z').to_a
|
145
|
-
@client_id = Array.new(30) { chars[rand(chars.size)] }.join
|
146
|
-
@sockets = {} # "host:port" -> Socket
|
147
|
-
@abilities = {} # "funcname" -> Ability
|
148
|
-
@after_abilities = {} # "funcname" -> Ability
|
149
|
-
@bad_servers = [] # "host:port"
|
150
|
-
@servers_mutex = Mutex.new
|
151
|
-
%w{client_id reconnect_sec
|
152
|
-
network_timeout_sec}.map {|s| s.to_sym }.each do |k|
|
153
|
-
instance_variable_set "@#{k}", opts[k]
|
154
|
-
opts.delete k
|
155
|
-
end
|
156
|
-
if opts.size > 0
|
157
|
-
raise InvalidArgsError,
|
158
|
-
'Invalid worker args: ' + opts.keys.sort.join(', ')
|
159
|
-
end
|
160
|
-
@reconnect_sec = 30 if not @reconnect_sec
|
161
|
-
@network_timeout_sec = 5 if not @network_timeout_sec
|
162
|
-
@worker_enabled = true
|
163
|
-
@status = :preparing
|
164
|
-
self.job_servers = job_servers if job_servers
|
165
|
-
start_reconnect_thread
|
166
|
-
end
|
167
|
-
attr_accessor :client_id, :reconnect_sec, :network_timeout_sec, :bad_servers, :worker_enabled, :status
|
130
|
+
packets = if ret && exception.nil?
|
131
|
+
logger.debug "Sending WORK_COMPLETE for #{handle} with #{ret.to_s.size} byte(s) to #{connection}"
|
132
|
+
run_work_complete_callback
|
133
|
+
[Packet.pack_request(:work_complete, "#{handle}\0#{ret.to_s}")]
|
134
|
+
elsif exception.nil?
|
135
|
+
logger.debug "Sending WORK_FAIL for #{handle} to #{connection}"
|
136
|
+
run_work_fail_callback
|
137
|
+
[Packet.pack_request(:work_fail, handle)]
|
138
|
+
elsif exception
|
139
|
+
logger.debug "Sending WORK_EXCEPTION for #{handle} to #{connection}"
|
140
|
+
run_work_exception_callback
|
141
|
+
[Packet.pack_request(:work_exception, "#{handle}\0#{exception.message}")]
|
142
|
+
end
|
168
143
|
|
169
|
-
|
170
|
-
|
171
|
-
Thread.new do
|
172
|
-
loop do
|
173
|
-
@servers_mutex.synchronize do
|
174
|
-
# If there are any failed servers, try to reconnect to them.
|
175
|
-
if not @bad_servers.empty?
|
176
|
-
update_job_servers(@sockets.keys + @bad_servers)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
sleep @reconnect_sec
|
144
|
+
packets.each do |packet|
|
145
|
+
connection.send_update(packet)
|
180
146
|
end
|
181
|
-
end.run
|
182
|
-
end
|
183
147
|
|
184
|
-
|
185
|
-
servers = nil
|
186
|
-
@servers_mutex.synchronize do
|
187
|
-
servers = @sockets.keys + @bad_servers
|
148
|
+
true
|
188
149
|
end
|
189
|
-
servers
|
190
|
-
end
|
191
150
|
|
192
|
-
##
|
193
|
-
# Connect to job servers to be used by this worker.
|
194
|
-
#
|
195
|
-
# @param servers "host:port"; either a single server or an array
|
196
|
-
def job_servers=(servers)
|
197
|
-
@servers_mutex.synchronize do
|
198
|
-
update_job_servers(servers)
|
199
|
-
end
|
200
|
-
end
|
201
151
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
@
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
@sockets[server] = connect(server)
|
224
|
-
rescue NetworkError
|
225
|
-
@bad_servers << server
|
226
|
-
Util.logger.info "GearmanRuby: Unable to connect to #{server}"
|
227
|
-
end
|
152
|
+
##
|
153
|
+
# Handle a message for the worker
|
154
|
+
#
|
155
|
+
# @param type Packet type (NO_JOB, JOB_ASSIGN, NO_OP)
|
156
|
+
# @param data Opaque data being passed with the message
|
157
|
+
# @param connection The Connection object where the message originates
|
158
|
+
# @return
|
159
|
+
def handle_work_message(type, data, connection)
|
160
|
+
case type
|
161
|
+
when :no_job
|
162
|
+
logger.info "Got NO_JOB from #{connection}"
|
163
|
+
run_no_job_callback
|
164
|
+
when :job_assign
|
165
|
+
@status = :working
|
166
|
+
run_job_assign_callback
|
167
|
+
return worker_enabled if handle_job_assign(data, connection)
|
168
|
+
when :noop
|
169
|
+
# We'll have to read again
|
170
|
+
logger.debug "Received NOOP while polling. Ignoring NOOP"
|
171
|
+
else
|
172
|
+
logger.error "Got unexpected #{type.to_s} from #{connection}"
|
228
173
|
end
|
229
174
|
end
|
230
|
-
end
|
231
|
-
private :update_job_servers
|
232
|
-
|
233
|
-
##
|
234
|
-
# Connect to a job server.
|
235
|
-
#
|
236
|
-
# @param hostport "hostname:port"
|
237
|
-
def connect(hostport)
|
238
|
-
begin
|
239
|
-
# FIXME: handle timeouts
|
240
|
-
sock = TCPSocket.new(*hostport.split(':'))
|
241
|
-
rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH
|
242
|
-
raise NetworkError
|
243
|
-
rescue Exception => e
|
244
|
-
Util.logger.debug "GearmanRuby: Unhandled exception while connecting to #{hostport} : #{e} (raising NetworkError exception)"
|
245
|
-
raise NetworkError
|
246
|
-
end
|
247
|
-
# FIXME: catch exceptions; do something smart
|
248
|
-
Util.send_request(sock, Util.pack_request(:set_client_id, @client_id))
|
249
|
-
@abilities.each {|f,a| announce_ability(sock, f, a.timeout) }
|
250
|
-
sock
|
251
|
-
end
|
252
|
-
private :connect
|
253
|
-
|
254
|
-
##
|
255
|
-
# Announce an ability over a particular socket.
|
256
|
-
#
|
257
|
-
# @param sock Socket connect to a job server
|
258
|
-
# @param func function name (including prefix)
|
259
|
-
# @param timeout the server will give up on us if we don't finish
|
260
|
-
# a task in this many seconds
|
261
|
-
def announce_ability(sock, func, timeout=nil)
|
262
|
-
begin
|
263
|
-
cmd = timeout ? :can_do_timeout : :can_do
|
264
|
-
arg = timeout ? "#{func}\0#{timeout.to_s}" : func
|
265
|
-
Util.send_request(sock, Util.pack_request(cmd, arg))
|
266
|
-
rescue Exception => ex
|
267
|
-
bad_servers << @sockets.keys.detect{|hp| @sockets[hp] == sock}
|
268
|
-
end
|
269
|
-
end
|
270
|
-
private :announce_ability
|
271
|
-
|
272
|
-
##
|
273
|
-
# Add a new ability, announcing it to job servers.
|
274
|
-
#
|
275
|
-
# The passed-in block of code will be executed for jobs of this function
|
276
|
-
# type. It'll receive two arguments, the data supplied by the client and
|
277
|
-
# a Job object. If it returns nil or false, the server will be informed
|
278
|
-
# that the job has failed; otherwise the return value of the block will
|
279
|
-
# be passed back to the client in String form.
|
280
|
-
#
|
281
|
-
# @param func function name (without prefix)
|
282
|
-
# @param timeout the server will give up on us if we don't finish
|
283
|
-
# a task in this many seconds
|
284
|
-
def add_ability(func, timeout=nil, &f)
|
285
|
-
@abilities[func] = Ability.new(f, timeout)
|
286
|
-
@sockets.values.each {|s| announce_ability(s, func, timeout) }
|
287
|
-
end
|
288
175
|
|
176
|
+
##
|
177
|
+
# Do a single job and return.
|
178
|
+
def work
|
179
|
+
grab_job_req = Packet.pack_request(:grab_job)
|
180
|
+
type, data = nil
|
289
181
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
# @param func function name (without prefix)
|
302
|
-
#
|
303
|
-
def after_ability(func, &f)
|
304
|
-
@after_abilities[func] = Ability.new(f)
|
305
|
-
end
|
182
|
+
loop do
|
183
|
+
@status = :preparing
|
184
|
+
|
185
|
+
@connection_pool.with_all_connections do |connection|
|
186
|
+
begin
|
187
|
+
logger.debug "Sending GRAB_JOB to #{connection}"
|
188
|
+
run_grab_job_callback
|
189
|
+
type, data = connection.send_request(grab_job_req, @network_timeout_sec)
|
190
|
+
handle_work_message(type, data, connection)
|
191
|
+
end while type == :job_assign
|
192
|
+
end
|
306
193
|
|
307
|
-
##
|
308
|
-
# Let job servers know that we're no longer able to do something.
|
309
|
-
#
|
310
|
-
# @param func function name
|
311
|
-
def remove_ability(func)
|
312
|
-
@abilities.delete(func)
|
313
|
-
req = Util.pack_request(:cant_do, func)
|
314
|
-
@sockets.values.each {|s| Util.send_request(s, req) }
|
315
|
-
end
|
316
|
-
|
317
|
-
##
|
318
|
-
# Handle a job_assign packet.
|
319
|
-
#
|
320
|
-
# @param data data in the packet
|
321
|
-
# @param sock Socket on which the packet arrived
|
322
|
-
# @param hostport "host:port"
|
323
|
-
def handle_job_assign(data, sock, hostport)
|
324
|
-
handle, func, data = data.split("\0", 3)
|
325
|
-
if not func
|
326
|
-
Util.logger.error "GearmanRuby: Ignoring job_assign with no function from #{hostport}"
|
327
|
-
return false
|
328
|
-
end
|
329
|
-
|
330
|
-
Util.logger.error "GearmanRuby: Got job_assign with handle #{handle} and #{data.size} byte(s) " +
|
331
|
-
"from #{hostport}"
|
332
194
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
Util.send_request(sock, Util.pack_request(:work_fail, handle))
|
338
|
-
return false
|
339
|
-
end
|
195
|
+
logger.info "Sending PRE_SLEEP and going to sleep for #{@reconnect_sec} second(s)"
|
196
|
+
@connection_pool.with_all_connections do |connection|
|
197
|
+
connection.send_update(Packet.pack_request(:pre_sleep))
|
198
|
+
end
|
340
199
|
|
341
|
-
|
342
|
-
|
343
|
-
ret = ability.run(data, Job.new(sock, handle))
|
344
|
-
rescue Exception => e
|
345
|
-
exception = e
|
346
|
-
Util.logger.debug "GearmanRuby: Exception: #{e}\n#{e.backtrace.join("\n")}\n"
|
347
|
-
end
|
200
|
+
return false unless worker_enabled
|
201
|
+
@status = :waiting
|
348
202
|
|
349
|
-
|
350
|
-
Util.logger.debug "GearmanRuby: Sending work_complete for #{handle} with #{ret.to_s.size} byte(s) " +
|
351
|
-
"to #{hostport}"
|
352
|
-
run_work_complete_callback
|
353
|
-
[ Util.pack_request(:work_complete, "#{handle}\0#{ret.to_s}") ]
|
354
|
-
elsif exception.nil?
|
355
|
-
Util.logger.debug "GearmanRuby: Sending work_fail for #{handle} to #{hostport}"
|
356
|
-
run_work_fail_callback
|
357
|
-
[ Util.pack_request(:work_fail, handle) ]
|
358
|
-
elsif exception
|
359
|
-
Util.logger.debug "GearmanRuby: Sending work_exception for #{handle} to #{hostport}"
|
360
|
-
run_work_exception_callback
|
361
|
-
[ Util.pack_request(:work_exception, "#{handle}\0#{exception.message}") ]
|
362
|
-
end
|
203
|
+
time_asleep = Time.now
|
363
204
|
|
364
|
-
|
365
|
-
|
366
|
-
# There are cases where we might want to run something after the worker
|
367
|
-
# successfully completes the ability in question and sends its results
|
368
|
-
if ret && exception.nil?
|
369
|
-
after_ability = @after_abilities[func]
|
370
|
-
if after_ability
|
371
|
-
Util.logger.debug "Running after ability for #{func}..."
|
372
|
-
begin
|
373
|
-
after_ability.run(ret, data)
|
374
|
-
rescue Exception => e
|
375
|
-
Util.logger.debug "GearmanRuby: Exception: #{e}\n#{e.backtrace.join("\n")}\n"
|
376
|
-
nil
|
205
|
+
while (@status == :waiting)
|
206
|
+
sleep(time_asleep)
|
377
207
|
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
true
|
382
|
-
end
|
383
208
|
|
384
|
-
##
|
385
|
-
# Do a single job and return.
|
386
|
-
def work
|
387
|
-
req = Util.pack_request(:grab_job)
|
388
|
-
type = nil
|
389
|
-
data = nil
|
390
|
-
loop do
|
391
|
-
@status = :preparing
|
392
|
-
bad_servers = []
|
393
|
-
# We iterate through the servers in sorted order to make testing
|
394
|
-
# easier.
|
395
|
-
servers = nil
|
396
|
-
@servers_mutex.synchronize { servers = @sockets.keys.sort }
|
397
|
-
servers.each do |hostport|
|
398
|
-
Util.logger.debug "GearmanRuby: Sending grab_job to #{hostport}"
|
399
|
-
run_grab_job_callback
|
400
|
-
sock = @sockets[hostport]
|
401
|
-
Util.send_request(sock, req)
|
402
|
-
|
403
|
-
# Now that we've sent grab_job, we need to keep reading packets
|
404
|
-
# until we see a no_job or job_assign response (there may be a noop
|
405
|
-
# waiting for us in response to a previous pre_sleep).
|
406
|
-
loop do
|
407
|
-
begin
|
408
|
-
type, data = Util.read_response(sock, @network_timeout_sec)
|
409
|
-
case type
|
410
|
-
when :no_job
|
411
|
-
Util.logger.debug "GearmanRuby: Got no_job from #{hostport}"
|
412
|
-
run_no_job_callback
|
413
|
-
break
|
414
|
-
when :job_assign
|
415
|
-
@status = :working
|
416
|
-
run_job_assign_callback
|
417
|
-
return worker_enabled if handle_job_assign(data, sock, hostport)
|
418
|
-
break
|
419
|
-
else
|
420
|
-
Util.logger.debug "GearmanRuby: Got #{type.to_s} from #{hostport}"
|
421
|
-
end
|
422
|
-
rescue Exception
|
423
|
-
Util.logger.info "GearmanRuby: Server #{hostport} timed out or lost connection (#{$!.inspect}); marking bad"
|
424
|
-
bad_servers << hostport
|
425
|
-
break
|
426
|
-
end
|
427
|
-
end
|
428
209
|
end
|
210
|
+
end
|
429
211
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
212
|
+
##
|
213
|
+
# Sleep and poll until timeout occurs or a NO_OP packet is received
|
214
|
+
# @param time_fell_asleep The time that we fell asleep (Time object)
|
215
|
+
def sleep(time_fell_asleep)
|
216
|
+
max_timeout = 30 - (Time.now - time_fell_asleep).to_i
|
217
|
+
|
218
|
+
if max_timeout > 0
|
219
|
+
# Use IO::select to wait for available connection data
|
220
|
+
@connection_pool.poll_connections(max_timeout)
|
436
221
|
end
|
437
222
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
223
|
+
# If 30 seconds have passed, then wakeup
|
224
|
+
time_asleep = (Time.now - time_fell_asleep).to_f
|
225
|
+
@status = :wakeup if time_asleep >= 30
|
226
|
+
|
227
|
+
# We didn't sleep for >= 30s, so we need to check for a NOOP
|
228
|
+
if (@status == :waiting)
|
229
|
+
@connection_pool.with_all_connections do |connection|
|
230
|
+
begin
|
231
|
+
type, data = connection.read_response(@network_timeout_sec)
|
444
232
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
sleepTime = Time.now
|
449
|
-
while(@status == :waiting)
|
450
|
-
# FIXME: We could optimize things the next time through the 'each' by
|
451
|
-
# sending the first grab_job to one of the servers that had a socket
|
452
|
-
# with data in it. Not bothering with it for now.
|
453
|
-
IO::select(@sockets.values, nil, nil, @reconnect_sec)
|
454
|
-
|
455
|
-
# If 30 seconds have passed, then wakeup
|
456
|
-
@status = :wakeup if Time.now - sleepTime > 30
|
457
|
-
|
458
|
-
if(@status == :waiting)
|
459
|
-
@sockets.values.each do |sock|
|
460
|
-
type, data = Util.read_response(sock, @network_timeout_sec)
|
461
|
-
|
462
|
-
# there shouldn't be anything else here, if there is, we should be able to ignore it...
|
463
|
-
if(type == :noop)
|
464
|
-
Util.logger.debug "Received NoOp while sleeping... waking up!"
|
233
|
+
# Wake up if we receive a NOOP packet
|
234
|
+
if (type == :noop)
|
235
|
+
logger.debug "Received NOOP while sleeping... waking up!"
|
465
236
|
@status = :wakeup
|
237
|
+
else
|
238
|
+
logger.warn "Received something other than a NOOP packet while sleeping: #{type.to_s}"
|
466
239
|
end
|
240
|
+
rescue SocketTimeoutError
|
241
|
+
# This is okay here.
|
467
242
|
end
|
468
243
|
end
|
469
244
|
end
|
470
|
-
|
471
245
|
end
|
472
246
|
end
|
473
|
-
end
|
474
247
|
|
475
248
|
end
|