gearman-ruby 2.0.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|