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.
Files changed (50) hide show
  1. data/Rakefile +5 -6
  2. data/VERSION.yml +2 -2
  3. data/examples/calculus_client.rb +8 -10
  4. data/examples/calculus_worker.rb +4 -1
  5. data/examples/client.php +23 -0
  6. data/examples/client.rb +6 -10
  7. data/examples/client_background.rb +7 -7
  8. data/examples/client_data.rb +4 -4
  9. data/examples/client_epoch.rb +23 -0
  10. data/examples/client_exception.rb +4 -2
  11. data/examples/client_prefix.rb +4 -2
  12. data/examples/client_reverse.rb +27 -0
  13. data/examples/scale_image.rb +4 -3
  14. data/examples/scale_image_worker.rb +1 -1
  15. data/examples/worker.rb +2 -4
  16. data/examples/worker_data.rb +2 -2
  17. data/examples/worker_exception.rb +1 -1
  18. data/examples/worker_prefix.rb +1 -1
  19. data/examples/worker_reverse_string.rb +27 -0
  20. data/examples/worker_reverse_to_file.rb +18 -0
  21. data/examples/{evented_worker.rb → worker_signals.rb} +18 -8
  22. data/lib/gearman.rb +68 -21
  23. data/lib/gearman/client.rb +137 -65
  24. data/lib/gearman/server.rb +4 -4
  25. data/lib/gearman/task.rb +140 -20
  26. data/lib/gearman/taskset.rb +280 -5
  27. data/lib/gearman/testlib.rb +95 -0
  28. data/lib/gearman/util.rb +184 -28
  29. data/lib/gearman/worker.rb +356 -20
  30. data/test/client_test.rb +145 -0
  31. data/test/mock_client_test.rb +629 -0
  32. data/test/mock_worker_test.rb +321 -0
  33. data/test/util_test.rb +8 -3
  34. data/test/worker_test.rb +50 -34
  35. metadata +41 -41
  36. data/examples/client_echo.rb +0 -16
  37. data/examples/evented_client.rb +0 -23
  38. data/examples/worker_echo.rb +0 -20
  39. data/examples/worker_echo_pprof.rb +0 -5
  40. data/gearman-ruby.gemspec +0 -111
  41. data/lib/gearman/evented/client.rb +0 -99
  42. data/lib/gearman/evented/reactor.rb +0 -86
  43. data/lib/gearman/evented/worker.rb +0 -118
  44. data/lib/gearman/job.rb +0 -38
  45. data/lib/gearman/protocol.rb +0 -110
  46. data/test/basic_integration_test.rb +0 -121
  47. data/test/crash_test.rb +0 -69
  48. data/test/job_test.rb +0 -30
  49. data/test/protocol_test.rb +0 -132
  50. data/test/test_helper.rb +0 -31
@@ -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
- def initialize(job_servers, opts = {})
6
- @reactors = []
7
- @jobs = {}
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
- @job_servers = Array[*job_servers]
58
+ raise Exception.new('No servers available') if @job_servers.empty?
10
59
 
11
- @uniq = opts.delete(:uniq)
12
- @opts = opts
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
- # Run a Task or Taskset
16
- def run(taskset, timeout = nil, async = false)
17
- timeout ||= 0
18
- use_em_stop = EM.reactor_running?
19
- EM.run do
20
- @taskset ||= Taskset.new
21
- @taskset += Taskset.create(taskset)
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
- private
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
- def create_job(task, reactor = nil)
64
- return unless task
65
- reactor ||= @reactors[rand(@reactors.size)]
66
- unless reactor.connected?
67
- log "create_job: server #{reactor} not connected"
68
- EM.next_tick { create_job(task) }
69
- return
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
- reactor.submit_job(task) {|handle| create_job(@taskset.shift) }
73
- end
138
+ result = nil
139
+ failed = false
140
+ task.on_complete {|v| result = v }
141
+ task.on_fail { failed = true }
74
142
 
75
- def log(msg)
76
- Gearman::Util.log(msg)
77
- end
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
@@ -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
- attr_reader :name, :payload, :retries_done
5
- attr_accessor :retries, :priority, :background, :poll_status_interval
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
- def initialize(name, payload = nil, opts = {})
8
- @name = name.to_s
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
- @retries_done = 0
14
- @retries = opts.delete(:retries) || 0
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
- @poll_status_interval = opts.delete(:poll_status_interval)
17
- @uniq = opts.has_key?(:uuid) ? opts.delete(:uuid) : `uuidgen`.strip
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 should_retry?
81
- return false if @retries_done >= @retries
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
- def background?
87
- background
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
- def dispatch(event, *args)
91
- callback = instance_variable_get("@#{event}".to_sym)
92
- callback.call(*args) if callback
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
- def hash
96
- @uniq
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
@@ -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
- def self.create(task_or_taskset)
5
- [*task_or_taskset]
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
- alias :add :<<
9
- alias :add_task :add
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