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.
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