gearman-ruby 2.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +5 -6
- data/VERSION.yml +2 -2
- data/examples/calculus_client.rb +8 -10
- data/examples/calculus_worker.rb +4 -1
- data/examples/client.php +23 -0
- data/examples/client.rb +6 -10
- data/examples/client_background.rb +7 -7
- data/examples/client_data.rb +4 -4
- data/examples/client_epoch.rb +23 -0
- data/examples/client_exception.rb +4 -2
- data/examples/client_prefix.rb +4 -2
- data/examples/client_reverse.rb +27 -0
- data/examples/scale_image.rb +4 -3
- data/examples/scale_image_worker.rb +1 -1
- data/examples/worker.rb +2 -4
- data/examples/worker_data.rb +2 -2
- data/examples/worker_exception.rb +1 -1
- data/examples/worker_prefix.rb +1 -1
- data/examples/worker_reverse_string.rb +27 -0
- data/examples/worker_reverse_to_file.rb +18 -0
- data/examples/{evented_worker.rb → worker_signals.rb} +18 -8
- data/lib/gearman.rb +68 -21
- data/lib/gearman/client.rb +137 -65
- data/lib/gearman/server.rb +4 -4
- data/lib/gearman/task.rb +140 -20
- data/lib/gearman/taskset.rb +280 -5
- data/lib/gearman/testlib.rb +95 -0
- data/lib/gearman/util.rb +184 -28
- data/lib/gearman/worker.rb +356 -20
- data/test/client_test.rb +145 -0
- data/test/mock_client_test.rb +629 -0
- data/test/mock_worker_test.rb +321 -0
- data/test/util_test.rb +8 -3
- data/test/worker_test.rb +50 -34
- metadata +41 -41
- data/examples/client_echo.rb +0 -16
- data/examples/evented_client.rb +0 -23
- data/examples/worker_echo.rb +0 -20
- data/examples/worker_echo_pprof.rb +0 -5
- data/gearman-ruby.gemspec +0 -111
- data/lib/gearman/evented/client.rb +0 -99
- data/lib/gearman/evented/reactor.rb +0 -86
- data/lib/gearman/evented/worker.rb +0 -118
- data/lib/gearman/job.rb +0 -38
- data/lib/gearman/protocol.rb +0 -110
- data/test/basic_integration_test.rb +0 -121
- data/test/crash_test.rb +0 -69
- data/test/job_test.rb +0 -30
- data/test/protocol_test.rb +0 -132
- data/test/test_helper.rb +0 -31
data/lib/gearman/client.rb
CHANGED
@@ -1,80 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
1
5
|
module Gearman
|
2
|
-
class Client
|
3
|
-
attr_accessor :uniq, :jobs
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
# = Client
|
8
|
+
#
|
9
|
+
# == Description
|
10
|
+
# A client for communicating with Gearman job servers.
|
11
|
+
class Client
|
12
|
+
##
|
13
|
+
# Create a new client.
|
14
|
+
#
|
15
|
+
# @param job_servers "host:port"; either a single server or an array
|
16
|
+
def initialize(job_servers=nil)
|
17
|
+
@job_servers = [] # "host:port"
|
18
|
+
self.job_servers = job_servers if job_servers
|
19
|
+
@sockets = {} # "host:port" -> [sock1, sock2, ...]
|
20
|
+
@socket_to_hostport = {} # sock -> "host:port"
|
21
|
+
@test_hostport = nil # make get_job_server return a given host for testing
|
22
|
+
@task_create_timeout_sec = 10
|
23
|
+
@server_counter = -1
|
24
|
+
@bad_servers = []
|
25
|
+
end
|
26
|
+
attr_reader :job_servers, :bad_servers
|
27
|
+
attr_accessor :test_hostport, :task_create_timeout_sec
|
28
|
+
|
29
|
+
##
|
30
|
+
# Set the options
|
31
|
+
#
|
32
|
+
# @options options to pass to the servers "exeptions"
|
33
|
+
def option_request(opts)
|
34
|
+
Util.logger.debug "GearmanRuby: Send options request with #{opts}"
|
35
|
+
request = Util.pack_request("option_req", opts)
|
36
|
+
sock= self.get_socket(self.get_job_server)
|
37
|
+
Util.send_request(sock, request)
|
38
|
+
response = Util.read_response(sock, 20)
|
39
|
+
raise ProtocolError, response[1] if response[0]==:error
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Set the job servers to be used by this client.
|
44
|
+
#
|
45
|
+
# @param servers "host:port"; either a single server or an array
|
46
|
+
def job_servers=(servers)
|
47
|
+
@job_servers = Util.normalize_job_servers(servers)
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Get connection info about an arbitrary (currently random, but maybe
|
53
|
+
# we'll do something smarter later) job server.
|
54
|
+
#
|
55
|
+
# @return "host:port"
|
56
|
+
def get_job_server
|
8
57
|
|
9
|
-
|
58
|
+
raise Exception.new('No servers available') if @job_servers.empty?
|
10
59
|
|
11
|
-
|
12
|
-
|
60
|
+
@server_counter += 1
|
61
|
+
# Return a specific server if one's been set.
|
62
|
+
@test_hostport or @job_servers[@server_counter % @job_servers.size]
|
63
|
+
end
|
64
|
+
|
65
|
+
def signal_bad_server(hostport)
|
66
|
+
@job_servers = @job_servers.reject { |s| s == hostport }
|
67
|
+
@bad_servers << hostport
|
68
|
+
end
|
69
|
+
##
|
70
|
+
# Get a socket for a job server.
|
71
|
+
#
|
72
|
+
# @param hostport job server "host:port"
|
73
|
+
# @return a Socket
|
74
|
+
def get_socket(hostport, num_retries=3)
|
75
|
+
# If we already have an open socket to this host, return it.
|
76
|
+
if @sockets[hostport]
|
77
|
+
sock = @sockets[hostport].shift
|
78
|
+
@sockets.delete(hostport) if @sockets[hostport].size == 0
|
79
|
+
return sock
|
13
80
|
end
|
14
81
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@
|
22
|
-
|
23
|
-
job_servers = @job_servers.dup
|
24
|
-
@reactors.each do |reactor|
|
25
|
-
unless reactor.connected?
|
26
|
-
reactor.reconnect true
|
27
|
-
reactor.keep_connected = async
|
28
|
-
reactor.callback { create_job(@taskset.shift, reactor) }
|
29
|
-
job_servers.delete reactor.to_s
|
30
|
-
else
|
31
|
-
create_job(@taskset.shift, reactor)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
job_servers.each do |hostport|
|
35
|
-
host, port = hostport.split(":")
|
36
|
-
reactor = Gearman::Evented::ClientReactor.connect(host, port, @opts)
|
37
|
-
reactor.keep_connected = async
|
38
|
-
reactor.callback { create_job(@taskset.shift, reactor) }
|
39
|
-
@reactors << reactor
|
40
|
-
end
|
41
|
-
|
42
|
-
if timeout > 0
|
43
|
-
if use_em_stop
|
44
|
-
EM.add_timer(timeout) { EM.stop }
|
45
|
-
else
|
46
|
-
sleep timeout
|
47
|
-
end
|
48
|
-
elsif !async
|
49
|
-
Thread.new do
|
50
|
-
loop do
|
51
|
-
sleep 0.1
|
52
|
-
live = 0
|
53
|
-
@reactors.each {|reactor| live += 1 if reactor.connected? }
|
54
|
-
break if live == 0
|
55
|
-
end
|
56
|
-
end.join
|
57
|
-
end
|
82
|
+
num_retries.times do |i|
|
83
|
+
begin
|
84
|
+
sock = TCPSocket.new(*hostport.split(':'))
|
85
|
+
rescue Exception
|
86
|
+
else
|
87
|
+
|
88
|
+
@socket_to_hostport[sock] = hostport
|
89
|
+
return sock
|
58
90
|
end
|
59
91
|
end
|
60
92
|
|
61
|
-
|
93
|
+
signal_bad_server(hostport)
|
94
|
+
raise RuntimeError, "Unable to connect to job server #{hostport}"
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Relinquish a socket created by Client#get_socket.
|
99
|
+
#
|
100
|
+
# If we don't know about the socket, we just close it.
|
101
|
+
#
|
102
|
+
# @param sock Socket
|
103
|
+
def return_socket(sock)
|
104
|
+
hostport = get_hostport_for_socket(sock)
|
105
|
+
if not hostport
|
106
|
+
inet, port, host, ip = s.addr
|
107
|
+
Util.logger.error "GearmanRuby: Got socket for #{ip}:#{port}, which we don't " +
|
108
|
+
"know about -- closing"
|
109
|
+
sock.close
|
110
|
+
return
|
111
|
+
end
|
112
|
+
(@sockets[hostport] ||= []) << sock
|
113
|
+
end
|
114
|
+
|
115
|
+
def close_socket(sock)
|
116
|
+
sock.close
|
117
|
+
@socket_to_hostport.delete(sock)
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Given a socket from Client#get_socket, return its host and port.
|
123
|
+
#
|
124
|
+
# @param sock Socket
|
125
|
+
# @return "host:port", or nil if unregistered (which shouldn't happen)
|
126
|
+
def get_hostport_for_socket(sock)
|
127
|
+
@socket_to_hostport[sock]
|
128
|
+
end
|
62
129
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
130
|
+
##
|
131
|
+
# Perform a single task.
|
132
|
+
#
|
133
|
+
# @param args either a Task or arguments for Task.new
|
134
|
+
# @return output of the task, or nil on failure
|
135
|
+
def do_task(*args)
|
136
|
+
task = Util::get_task_from_args(*args)
|
71
137
|
|
72
|
-
|
73
|
-
|
138
|
+
result = nil
|
139
|
+
failed = false
|
140
|
+
task.on_complete {|v| result = v }
|
141
|
+
task.on_fail { failed = true }
|
74
142
|
|
75
|
-
|
76
|
-
|
77
|
-
|
143
|
+
taskset = TaskSet.new(self)
|
144
|
+
taskset.add_task(task)
|
145
|
+
taskset.wait
|
78
146
|
|
147
|
+
failed ? nil : result
|
79
148
|
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
80
152
|
end
|
data/lib/gearman/server.rb
CHANGED
@@ -45,14 +45,14 @@ class Server
|
|
45
45
|
def send_command(name)
|
46
46
|
response = ''
|
47
47
|
socket.puts(name)
|
48
|
-
while true do
|
48
|
+
while true do
|
49
49
|
if buf = socket.recv_nonblock(65536) rescue nil
|
50
|
-
response << buf
|
50
|
+
response << buf
|
51
51
|
return response if response =~ /\n.\n$/
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
##
|
57
57
|
# Returns results of a 'status' command.
|
58
58
|
#
|
@@ -68,7 +68,7 @@ class Server
|
|
68
68
|
end
|
69
69
|
status
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
##
|
73
73
|
# Returns results of a 'workers' command.
|
74
74
|
#
|
data/lib/gearman/task.rb
CHANGED
@@ -1,20 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
1
5
|
module Gearman
|
6
|
+
|
7
|
+
# = Task
|
8
|
+
#
|
9
|
+
# == Description
|
10
|
+
# A task submitted to a Gearman job server.
|
2
11
|
class Task
|
3
12
|
|
4
|
-
|
5
|
-
|
13
|
+
##
|
14
|
+
# Create a new Task object.
|
15
|
+
#
|
16
|
+
# @param func function name
|
17
|
+
# @param arg argument to the function
|
18
|
+
# @param opts hash of additional options
|
19
|
+
def initialize(func, arg='', opts={})
|
20
|
+
@func = func.to_s
|
21
|
+
@arg = arg or '' # TODO: use something more ref-like?
|
22
|
+
@uniq = nil # Initialize to nil
|
23
|
+
%w{on_complete on_fail on_retry on_exception on_status on_warning on_data
|
24
|
+
uniq retry_count priority hash background}.map {|s| s.to_sym }.each do |k|
|
25
|
+
instance_variable_set "@#{k}", opts[k]
|
26
|
+
opts.delete k
|
27
|
+
end
|
28
|
+
if opts.size > 0
|
29
|
+
raise InvalidArgsError, 'Invalid task args: ' + opts.keys.sort.join(', ')
|
30
|
+
end
|
31
|
+
@retry_count ||= 0
|
32
|
+
@successful = false
|
33
|
+
@retries_done = 0
|
34
|
+
|
35
|
+
end
|
6
36
|
|
7
|
-
|
8
|
-
|
9
|
-
@payload = payload || ''
|
10
|
-
@priority = opts.delete(:priority).to_sym rescue nil
|
11
|
-
@background = opts.delete(:background) ? true : false
|
37
|
+
attr_accessor :uniq, :retry_count, :priority, :background, :epoch
|
38
|
+
attr_reader :successful, :func, :arg, :retries_done
|
12
39
|
|
13
|
-
|
14
|
-
|
40
|
+
##
|
41
|
+
# Schedule this job to run at a certain time (like `cron`)
|
42
|
+
# XXX: But there is no wildcard??
|
43
|
+
#
|
44
|
+
# @param time Ruby Time object that represents when to run the thing
|
45
|
+
def schedule(time)
|
46
|
+
@scheduled_at = time
|
47
|
+
end
|
15
48
|
|
16
|
-
|
17
|
-
|
49
|
+
##
|
50
|
+
# Internal method to reset this task's state so it can be run again.
|
51
|
+
# Called by TaskSet#add_task.
|
52
|
+
def reset_state
|
53
|
+
@retries_done = 0
|
54
|
+
@successful = false
|
55
|
+
self
|
18
56
|
end
|
19
57
|
|
20
58
|
##
|
@@ -73,27 +111,109 @@ module Gearman
|
|
73
111
|
@on_data = f
|
74
112
|
end
|
75
113
|
|
114
|
+
##
|
115
|
+
# Handle completion of the task.
|
116
|
+
#
|
117
|
+
# @param data data returned from the server (doesn't include handle)
|
118
|
+
def handle_completion(data)
|
119
|
+
@successful = true
|
120
|
+
@on_complete.call(data) if @on_complete
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
76
124
|
##
|
77
125
|
# Record a failure and check whether we should be retried.
|
78
126
|
#
|
79
127
|
# @return true if we should be resubmitted; false otherwise
|
80
|
-
def
|
81
|
-
|
128
|
+
def handle_failure
|
129
|
+
if @retries_done >= @retry_count
|
130
|
+
@on_fail.call if @on_fail
|
131
|
+
return false
|
132
|
+
end
|
82
133
|
@retries_done += 1
|
134
|
+
@on_retry.call(@retries_done) if @on_retry
|
83
135
|
true
|
84
136
|
end
|
85
137
|
|
86
|
-
|
87
|
-
|
138
|
+
##
|
139
|
+
# Record an exception.
|
140
|
+
#
|
141
|
+
def handle_exception(exception)
|
142
|
+
@on_exception.call(exception) if @on_exception
|
143
|
+
self
|
88
144
|
end
|
89
145
|
|
90
|
-
|
91
|
-
|
92
|
-
|
146
|
+
##
|
147
|
+
# Handle a status update for the task.
|
148
|
+
def handle_status(numerator, denominator)
|
149
|
+
@on_status.call(numerator, denominator) if @on_status
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Handle a warning.
|
155
|
+
#
|
156
|
+
def handle_warning(message)
|
157
|
+
@on_warning.call(message) if @on_warning
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Handle (partial) data
|
163
|
+
def handle_data(data)
|
164
|
+
@on_data.call(data) if @on_data
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Return a hash that we can use to execute identical tasks on the same
|
170
|
+
# job server.
|
171
|
+
#
|
172
|
+
# @return hashed value, based on @func + @arg (sorted letters and then SHA1'd) if @uniq is nil,
|
173
|
+
# or the SHA1 of @uniq if it's set to something
|
174
|
+
#
|
175
|
+
def get_uniq_hash
|
176
|
+
return @hash if @hash
|
177
|
+
|
178
|
+
if @uniq.nil?
|
179
|
+
string = (@func+@arg).split(//).sort.join
|
180
|
+
else
|
181
|
+
string = @uniq
|
182
|
+
end
|
183
|
+
|
184
|
+
@hash = Digest::SHA1.hexdigest(string)
|
93
185
|
end
|
94
186
|
|
95
|
-
|
96
|
-
|
187
|
+
##
|
188
|
+
# Construct a packet to submit this task to a job server.
|
189
|
+
#
|
190
|
+
# @return String representation of packet
|
191
|
+
def get_submit_packet()
|
192
|
+
modes = ['submit_job']
|
193
|
+
|
194
|
+
if @scheduled_at
|
195
|
+
modes << 'epoch'
|
196
|
+
args = [func, get_uniq_hash, @scheduled_at.to_i, arg]
|
197
|
+
else
|
198
|
+
if @priority
|
199
|
+
modes << 'high' if @priority == :high
|
200
|
+
modes << 'low' if @priority == :low
|
201
|
+
else
|
202
|
+
modes << 'bg' if @background
|
203
|
+
end
|
204
|
+
args = [func, get_uniq_hash, arg]
|
205
|
+
end
|
206
|
+
|
207
|
+
mode = modes.join('_')
|
208
|
+
Util::pack_request(mode, args.join("\0"))
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
class BackgroundTask < Task
|
213
|
+
def initialize(*args)
|
214
|
+
super
|
215
|
+
@background = true
|
97
216
|
end
|
98
217
|
end
|
218
|
+
|
99
219
|
end
|
data/lib/gearman/taskset.rb
CHANGED
@@ -1,11 +1,286 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'time'
|
5
|
+
|
1
6
|
module Gearman
|
2
|
-
class Taskset < ::Array
|
3
7
|
|
4
|
-
|
5
|
-
|
8
|
+
# = TaskSet
|
9
|
+
#
|
10
|
+
# == Description
|
11
|
+
# A set of tasks submitted to a Gearman job server.
|
12
|
+
class TaskSet
|
13
|
+
def initialize(client)
|
14
|
+
@client = client
|
15
|
+
@task_waiting_for_handle = nil
|
16
|
+
@tasks_in_progress = {} # "host:port//handle" -> [job1, job2, ...]
|
17
|
+
@finished_tasks = [] # tasks that have completed or failed
|
18
|
+
@sockets = {} # "host:port" -> Socket
|
19
|
+
@merge_hash_to_hostport = {} # Fixnum -> "host:port"
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Add a new task to this TaskSet.
|
24
|
+
#
|
25
|
+
# @param args either a Task or arguments for Task.new
|
26
|
+
# @return true if the task was created successfully, false otherwise
|
27
|
+
def add_task(*args)
|
28
|
+
task = Util::get_task_from_args(*args)
|
29
|
+
add_task_internal(task, true)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Internal function to add a task.
|
34
|
+
#
|
35
|
+
# @param task Task to add
|
36
|
+
# @param reset_state should we reset task state? true if we're adding a
|
37
|
+
# new task; false if we're rescheduling one that's
|
38
|
+
# failed
|
39
|
+
# @return true if the task was created successfully, false
|
40
|
+
# otherwise
|
41
|
+
def add_task_internal(task, reset_state=true)
|
42
|
+
task.reset_state if reset_state
|
43
|
+
req = task.get_submit_packet()
|
44
|
+
|
45
|
+
@task_waiting_for_handle = task
|
46
|
+
# FIXME: We need to loop here in case we get a bad job server, or the
|
47
|
+
# job creation fails (see how the server reports this to us), or ...
|
48
|
+
|
49
|
+
merge_hash = task.get_uniq_hash
|
50
|
+
|
51
|
+
looking_for_socket = true
|
52
|
+
|
53
|
+
should_try_rehash = true
|
54
|
+
while(looking_for_socket)
|
55
|
+
begin
|
56
|
+
hostport = if should_try_rehash
|
57
|
+
(@merge_hash_to_hostport[merge_hash] or @client.get_job_server)
|
58
|
+
else
|
59
|
+
@client.get_job_server
|
60
|
+
end
|
61
|
+
|
62
|
+
@merge_hash_to_hostport[merge_hash] = hostport if merge_hash
|
63
|
+
sock = (@sockets[hostport] or @client.get_socket(hostport))
|
64
|
+
looking_for_socket = false
|
65
|
+
rescue RuntimeError
|
66
|
+
should_try_rehash = false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
Util.logger.debug "GearmanRuby: Using socket #{sock.inspect} for #{hostport}"
|
70
|
+
Util.send_request(sock, req)
|
71
|
+
while @task_waiting_for_handle
|
72
|
+
begin
|
73
|
+
read_packet(sock, @client.task_create_timeout_sec)
|
74
|
+
rescue NetworkError
|
75
|
+
Util.logger.debug "GearmanRuby: Got timeout on read from #{hostport}"
|
76
|
+
@task_waiting_for_handle = nil
|
77
|
+
@client.close_socket(sock)
|
78
|
+
return false
|
79
|
+
end
|
6
80
|
end
|
7
81
|
|
8
|
-
|
9
|
-
|
82
|
+
@sockets[hostport] ||= sock
|
83
|
+
true
|
10
84
|
end
|
85
|
+
private :add_task_internal
|
86
|
+
|
87
|
+
##
|
88
|
+
# Handle a 'job_created' response from a job server.
|
89
|
+
#
|
90
|
+
# @param hostport "host:port" of job server
|
91
|
+
# @param data data returned in packet from server
|
92
|
+
def handle_job_created(hostport, data)
|
93
|
+
Util.logger.debug "GearmanRuby: Got job_created with handle #{data} from #{hostport}"
|
94
|
+
if not @task_waiting_for_handle
|
95
|
+
raise ProtocolError, "Got unexpected job_created notification " + "with handle #{data} from #{hostport}"
|
96
|
+
end
|
97
|
+
js_handle = Util.handle_to_str(hostport, data)
|
98
|
+
task = @task_waiting_for_handle
|
99
|
+
@task_waiting_for_handle = nil
|
100
|
+
if(task.background)
|
101
|
+
@finished_tasks << task
|
102
|
+
else
|
103
|
+
(@tasks_in_progress[js_handle] ||= []) << task
|
104
|
+
end
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
private :handle_job_created
|
108
|
+
|
109
|
+
##
|
110
|
+
# Handle a 'work_complete' response from a job server.
|
111
|
+
#
|
112
|
+
# @param hostport "host:port" of job server
|
113
|
+
# @param data data returned in packet from server
|
114
|
+
def handle_work_complete(hostport, data)
|
115
|
+
handle, data = data.split("\0", 2)
|
116
|
+
Util.logger.debug "GearmanRuby: Got work_complete with handle #{handle} and #{data ? data.size : '0'} byte(s) of data from #{hostport}"
|
117
|
+
tasks_in_progress(hostport, handle, true).each do |t|
|
118
|
+
t.handle_completion(data)
|
119
|
+
@finished_tasks << t
|
120
|
+
end
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
private :handle_work_complete
|
124
|
+
|
125
|
+
##
|
126
|
+
# Handle a 'work_exception' response from a job server.
|
127
|
+
#
|
128
|
+
# @param hostport "host:port" of job server
|
129
|
+
# @param data data returned in packet from server
|
130
|
+
def handle_work_exception(hostport, data)
|
131
|
+
handle, exception = data.split("\0", 2)
|
132
|
+
Util.logger.debug "GearmanRuby: Got work_exception with handle #{handle} from #{hostport}: '#{exception}'"
|
133
|
+
tasks_in_progress(hostport, handle).each {|t| t.handle_exception(exception) }
|
134
|
+
end
|
135
|
+
private :handle_work_exception
|
136
|
+
|
137
|
+
##
|
138
|
+
# Handle a 'work_fail' response from a job server.
|
139
|
+
#
|
140
|
+
# @param hostport "host:port" of job server
|
141
|
+
# @param data data returned in packet from server
|
142
|
+
def handle_work_fail(hostport, data)
|
143
|
+
Util.logger.debug "GearmanRuby: Got work_fail with handle #{data} from #{hostport}"
|
144
|
+
tasks_in_progress(hostport, data, true).each do |t|
|
145
|
+
if t.handle_failure
|
146
|
+
add_task_internal(t, false)
|
147
|
+
else
|
148
|
+
@finished_tasks << t
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
private :handle_work_fail
|
153
|
+
|
154
|
+
##
|
155
|
+
# Handle a 'work_status' response from a job server.
|
156
|
+
#
|
157
|
+
# @param hostport "host:port" of job server
|
158
|
+
# @param data data returned in packet from server
|
159
|
+
def handle_work_status(hostport, data)
|
160
|
+
handle, num, den = data.split("\0", 3)
|
161
|
+
Util.logger.debug "GearmanRuby: Got work_status with handle #{handle} from #{hostport}: #{num}/#{den}"
|
162
|
+
tasks_in_progress(hostport, handle).each {|t| t.handle_status(num, den) }
|
163
|
+
end
|
164
|
+
private :handle_work_status
|
165
|
+
|
166
|
+
##
|
167
|
+
# Handle a 'work_warning' response from a job server.
|
168
|
+
#
|
169
|
+
# @param hostport "host:port" of job server
|
170
|
+
# @param data data returned in packet from server
|
171
|
+
def handle_work_warning(hostport, data)
|
172
|
+
handle, message = data.split("\0", 2)
|
173
|
+
Util.logger.debug "GearmanRuby: Got work_warning with handle #{handle} from #{hostport}: '#{message}'"
|
174
|
+
tasks_in_progress(hostport, handle).each {|t| t.handle_warning(message) }
|
175
|
+
end
|
176
|
+
private :handle_work_warning
|
177
|
+
|
178
|
+
##
|
179
|
+
# Handle a 'work_data' response from a job server
|
180
|
+
#
|
181
|
+
# @param hostport "host:port" of a job server
|
182
|
+
# @param data data returned in packet from server
|
183
|
+
def handle_work_data(hostport, data)
|
184
|
+
handle, data = data.split("\0", 2)
|
185
|
+
Util.logger.debug "GearmanRuby: Got work_data with handle #{handle} and #{data ? data.size : '0'} byte(s) of data from #{hostport}"
|
186
|
+
|
187
|
+
js_handle = Util.handle_to_str(hostport, handle)
|
188
|
+
tasks = @tasks_in_progress[js_handle]
|
189
|
+
if not tasks
|
190
|
+
raise ProtocolError, "Got unexpected work_data with handle #{handle} from #{hostport} (no task by that name)"
|
191
|
+
end
|
192
|
+
tasks.each {|t| t.handle_data(data) }
|
193
|
+
end
|
194
|
+
private :handle_work_data
|
195
|
+
|
196
|
+
##
|
197
|
+
# Read and process a packet from a socket.
|
198
|
+
#
|
199
|
+
# @param sock socket connected to a job server
|
200
|
+
def read_packet(sock, timeout=nil)
|
201
|
+
hostport = @client.get_hostport_for_socket(sock)
|
202
|
+
if not hostport
|
203
|
+
raise RuntimeError, "Client doesn't know host/port for socket " +
|
204
|
+
sock.inspect
|
205
|
+
end
|
206
|
+
type, data = Util.read_response(sock, timeout)
|
207
|
+
known_types = [ :job_created,
|
208
|
+
:work_complete,
|
209
|
+
:work_fail,
|
210
|
+
:work_status,
|
211
|
+
:work_exception,
|
212
|
+
:work_warning,
|
213
|
+
:work_data ]
|
214
|
+
|
215
|
+
if known_types.include?(type)
|
216
|
+
send("handle_#{type}".to_sym, hostport, data)
|
217
|
+
else
|
218
|
+
Util.logger.debug "GearmanRuby: Got #{type.to_s} from #{hostport}"
|
219
|
+
end
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
private :read_packet
|
223
|
+
|
224
|
+
##
|
225
|
+
# Wait for all tasks in the set to finish.
|
226
|
+
#
|
227
|
+
# @param timeout maximum amount of time to wait, in seconds
|
228
|
+
def wait(timeout = 1)
|
229
|
+
end_time = if timeout
|
230
|
+
Time.now.to_f + timeout
|
231
|
+
else
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
while not @tasks_in_progress.empty?
|
236
|
+
remaining = if end_time
|
237
|
+
(t = end_time - Time.now.to_f) > 0 ? t : 0
|
238
|
+
else
|
239
|
+
nil
|
240
|
+
end
|
241
|
+
|
242
|
+
ready_socks = remaining == 0 ? nil : IO::select(@sockets.values, nil, nil, remaining)
|
243
|
+
if not ready_socks or not ready_socks[0]
|
244
|
+
Util.logger.debug "GearmanRuby: Timed out while waiting for tasks to finish"
|
245
|
+
# not sure what state the connections are in, so just be lame and
|
246
|
+
# close them for now
|
247
|
+
@sockets.values.each {|s| @client.close_socket(s) }
|
248
|
+
@sockets = {}
|
249
|
+
return false
|
250
|
+
end
|
251
|
+
ready_socks[0].each do |sock|
|
252
|
+
begin
|
253
|
+
read_packet(sock, (end_time ? end_time - Time.now.to_f : nil))
|
254
|
+
rescue ProtocolError
|
255
|
+
hostport = @client.get_hostport_for_socket(sock)
|
256
|
+
Util.logger.debug "GearmanRuby: Ignoring bad packet from #{hostport}"
|
257
|
+
rescue NetworkError
|
258
|
+
hostport = @client.get_hostport_for_socket(sock)
|
259
|
+
Util.logger.debug "GearmanRuby: Got timeout on read from #{hostport}"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
@sockets.values.each {|s| @client.return_socket(s) }
|
265
|
+
@sockets = {}
|
266
|
+
@finished_tasks.each do |t|
|
267
|
+
if ( (t.background.nil? || t.background == false) && !t.successful)
|
268
|
+
Util.logger.debug "GearmanRuby: Taskset failed"
|
269
|
+
return false
|
270
|
+
end
|
271
|
+
end
|
272
|
+
true
|
273
|
+
end
|
274
|
+
|
275
|
+
private
|
276
|
+
def tasks_in_progress(hostport, handle, remove_task = false)
|
277
|
+
js_handle = Util.handle_to_str(hostport, handle)
|
278
|
+
tasks = remove_task ? @tasks_in_progress.delete(js_handle) : @tasks_in_progress[js_handle]
|
279
|
+
if not tasks
|
280
|
+
raise ProtocolError, "Got unexpected work_data with handle #{handle} from #{hostport} (no task by that name)"
|
281
|
+
end
|
282
|
+
tasks
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
11
286
|
end
|