gearman-ruby 2.0.0

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 (48) hide show
  1. data/.gitignore +1 -0
  2. data/HOWTO +146 -0
  3. data/LICENSE +20 -0
  4. data/README +9 -0
  5. data/Rakefile +41 -0
  6. data/TODO +8 -0
  7. data/VERSION.yml +4 -0
  8. data/examples/calculus_client.rb +41 -0
  9. data/examples/calculus_worker.rb +42 -0
  10. data/examples/client.rb +19 -0
  11. data/examples/client_background.rb +14 -0
  12. data/examples/client_data.rb +16 -0
  13. data/examples/client_echo.rb +16 -0
  14. data/examples/client_exception.rb +17 -0
  15. data/examples/client_prefix.rb +15 -0
  16. data/examples/evented_client.rb +23 -0
  17. data/examples/evented_worker.rb +26 -0
  18. data/examples/gearman_environment.sh +25 -0
  19. data/examples/scale_image.rb +30 -0
  20. data/examples/scale_image_worker.rb +34 -0
  21. data/examples/server.rb +15 -0
  22. data/examples/worker.rb +23 -0
  23. data/examples/worker_data.rb +16 -0
  24. data/examples/worker_echo.rb +20 -0
  25. data/examples/worker_echo_pprof.rb +5 -0
  26. data/examples/worker_exception.rb +14 -0
  27. data/examples/worker_prefix.rb +25 -0
  28. data/gearman-ruby.gemspec +111 -0
  29. data/lib/gearman.rb +29 -0
  30. data/lib/gearman/client.rb +80 -0
  31. data/lib/gearman/evented/client.rb +99 -0
  32. data/lib/gearman/evented/reactor.rb +86 -0
  33. data/lib/gearman/evented/worker.rb +118 -0
  34. data/lib/gearman/job.rb +38 -0
  35. data/lib/gearman/protocol.rb +110 -0
  36. data/lib/gearman/server.rb +94 -0
  37. data/lib/gearman/task.rb +99 -0
  38. data/lib/gearman/taskset.rb +11 -0
  39. data/lib/gearman/util.rb +52 -0
  40. data/lib/gearman/worker.rb +39 -0
  41. data/test/basic_integration_test.rb +121 -0
  42. data/test/crash_test.rb +69 -0
  43. data/test/job_test.rb +30 -0
  44. data/test/protocol_test.rb +132 -0
  45. data/test/test_helper.rb +31 -0
  46. data/test/util_test.rb +12 -0
  47. data/test/worker_test.rb +45 -0
  48. metadata +133 -0
@@ -0,0 +1,99 @@
1
+ module Gearman
2
+ module Evented
3
+
4
+ module ClientReactor
5
+ include Gearman::Evented::Reactor
6
+
7
+ def keep_connected
8
+ @keep_connected ||= (@opts[:keep_connected] || false)
9
+ end
10
+
11
+ def keep_connected=(keep)
12
+ @keep_connected = keep
13
+ end
14
+
15
+ def connection_completed
16
+ @cbs_job_created ||= []
17
+ @pending_jobs = []
18
+ @assigned_jobs = {}
19
+ @background_jobs = {}
20
+ super
21
+ end
22
+
23
+ def receive_data(data)
24
+ packets = Gearman::Protocol.decode_response(data)
25
+ log "received #{packets.size} packet(s) at once"
26
+ log "packets: #{packets.inspect}"
27
+ packets.each do |type, handle, *data|
28
+ dispatch_packet(type, handle, *data)
29
+ end
30
+ end
31
+
32
+ def dispatch_packet_callback(&callback)
33
+ @dispatch_packet_callback = callback
34
+ end
35
+
36
+ def dispatch_packet(type, handle, *data)
37
+ log "Got #{type.to_s}, #{handle}, #{data.inspect} from #{server}"
38
+ if type == :job_created
39
+ job_created(handle)
40
+ if cb = @cbs_job_created.shift
41
+ cb.call(handle)
42
+ end
43
+ else
44
+ dispatch(type, handle, data)
45
+ end
46
+ end
47
+
48
+ def submit_job(task, &cb_job_created)
49
+ cmd = "submit_job"
50
+ cmd << "_#{task.priority}" if [ :high, :low ].include?(task.priority)
51
+ cmd << "_bg" if task.background
52
+
53
+ log "#{cmd} #{task.name}, #{task.payload} to #{server}"
54
+ send cmd.to_sym, [ task.name, task.hash, task.payload ].join("\0")
55
+ @pending_jobs << task
56
+ @cbs_job_created << cb_job_created if cb_job_created
57
+ end
58
+
59
+ def job_created(handle)
60
+ job = @pending_jobs.shift
61
+ raise ProtocolError, "No job waiting for handle! (#{handle})" unless job
62
+ EM.add_periodic_timer(job.poll_status_interval) { get_status(handle) } if job.poll_status_interval
63
+ if job.background
64
+ @background_jobs[handle] = job
65
+ else
66
+ @assigned_jobs[handle] = job
67
+ end
68
+ end
69
+
70
+ def get_status(handle)
71
+ send :get_status, handle
72
+ end
73
+
74
+ def dispatch(type, handle, args)
75
+ return unless type
76
+ task = @assigned_jobs[handle]
77
+ task = @background_jobs[handle] unless task
78
+ raise ProtocolError, "No task by that name: #{handle}" unless task
79
+
80
+ if :work_fail == type && task.should_retry?
81
+ task.dispatch(:on_retry, task.retries_done)
82
+ @assigned_jobs.delete(handle)
83
+ submit_job(task)
84
+ return
85
+ end
86
+
87
+ if type == :status_res
88
+ task.dispatch(:on_status, args)
89
+ else
90
+ task.dispatch(type.to_s.sub("work", "on"), *args)
91
+ end
92
+
93
+ @assigned_jobs.delete(handle) if [:work_complete, :work_fail].include?(type)
94
+ disconnect if @assigned_jobs.empty? && !keep_connected
95
+ end
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,86 @@
1
+ module Gearman
2
+ module Evented
3
+
4
+ module Reactor
5
+ include EM::Deferrable
6
+
7
+ def self.included(mod)
8
+ mod.instance_eval do
9
+ def connect(host, port, opts = {})
10
+ Gearman::Evented::Reactor.connect(host, port, self, opts)
11
+ end
12
+ end
13
+ end
14
+
15
+ def self.connect(host, port, reactor, opts = {})
16
+ EM.connect(host, (port || 4730), reactor) do |c|
17
+ c.instance_eval do
18
+ @host = host
19
+ @port = port || 4730
20
+ @opts = opts
21
+ end
22
+ end
23
+ end
24
+
25
+ def connected?
26
+ @connected
27
+ end
28
+
29
+ def server
30
+ @hostport ||= [ @host, @port ].join(":")
31
+ end
32
+
33
+ def connection_completed
34
+ log "connected to #{@host}:#{@port}"
35
+ @connected = true
36
+ @reconnecting = false
37
+ @reconnect = true
38
+ succeed
39
+ end
40
+
41
+ def unbind
42
+ log "disconnected from #{@host}:#{@port}"
43
+ @connected = false
44
+ EM.next_tick { reconnect } unless !@reconnect
45
+ @reconnect = true
46
+ end
47
+
48
+ def disconnect
49
+ log "force disconnect from #{@host}:#{@port}"
50
+ @reconnect = false
51
+ close_connection_after_writing
52
+ end
53
+
54
+ def reconnect(force = false)
55
+ if @reconnecting
56
+ EM.add_timer(@opts[:reconnect_sec] || 30) { reconnect }
57
+ return
58
+ elsif !@reconnect && !force
59
+ log "forced disconnect, aborting reconnect attempt"
60
+ @reconnect = true
61
+ return
62
+ else
63
+ @reconnecting = true
64
+ @deferred_status = nil
65
+ end
66
+
67
+ log "reconnecting to #{@host}:#{@port}"
68
+ EM.reconnect(@host, @port.to_i, self)
69
+ end
70
+
71
+ def send(command, data = nil)
72
+ log "send #{command} #{data.inspect} (#{server})"
73
+ send_data(Gearman::Protocol.encode_request(command, data))
74
+ end
75
+
76
+ def log(msg, force = false)
77
+ Gearman::Util.log(msg, force)
78
+ end
79
+
80
+ def to_s
81
+ "#{@host}:#{@port}"
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,118 @@
1
+ module Gearman
2
+ module Evented
3
+
4
+ module WorkerReactor
5
+ include Gearman::Evented::Reactor
6
+
7
+ def connection_completed
8
+ send :set_client_id, client_id
9
+ super
10
+
11
+ @abilities ||= @opts.delete(:abilities) || []
12
+ @abilities.each do |ability, args|
13
+ announce_ability(ability, args[:timeout])
14
+ end
15
+
16
+ grab_job
17
+ end
18
+
19
+ def announce_ability(name, timeout)
20
+ cmd = timeout ? :can_do_timeout : :can_do
21
+ arg = timeout ? "#{name}\0#{timeout.to_s}" : name
22
+ log "announce_ability #{name} #{timeout}"
23
+ send cmd, arg
24
+ end
25
+
26
+ def announce_disability(name)
27
+ send :cant_do, name
28
+ end
29
+
30
+ def grab_job
31
+ log "Grab Job"
32
+ send :grab_job_uniq
33
+ end
34
+
35
+ def work_fail(handle)
36
+ send :work_fail, handle
37
+ end
38
+
39
+ def work_complete(handle, data)
40
+ send :work_complete, "#{handle}\0#{data}"
41
+ end
42
+
43
+ def work_warning(handle, message)
44
+ send :work_warning, "#{handle}\0#{message}"
45
+ end
46
+
47
+ def receive_data(data)
48
+ Gearman::Protocol.decode_response(data).each do |type, handle, *data|
49
+ dispatch_packet(type, handle, *data)
50
+ end
51
+ end
52
+
53
+ def dispatch_packet(type, handle, *data)
54
+ success = true
55
+ timer = 0
56
+ case type
57
+ when :no_job
58
+ send :pre_sleep
59
+ timer = @opts[:reconnect_sec] || 30
60
+ when :job_assign, :job_assign_uniq
61
+ log "job assign #{handle}, #{data.inspect}"
62
+ handle_job_assign(handle, data[0], data[1])
63
+ when :noop
64
+ log "NOOP"
65
+ when :error
66
+ log "[ERROR]: error from server #{server}: #{data}"
67
+ else
68
+ log "Got unknown #{type}, #{data} from #{server}"
69
+ end
70
+
71
+ EM.add_timer(timer) { grab_job }
72
+ succeed [handle, data]
73
+ end
74
+
75
+ def handle_job_assign(handle, func, args = '')
76
+ return unless handle
77
+ unless func
78
+ log "ERROR: Ignoring job_assign with no function"
79
+ return
80
+ end
81
+
82
+ log "Got job_assign '#{func}' with handle #{handle} and #{args.size rescue 0} byte(s)"
83
+
84
+ unless @abilities.has_key?(func)
85
+ log "Ignoring job_assign for unsupported func #{func} with handle #{handle}"
86
+ work_fail handle
87
+ return
88
+ end
89
+
90
+ exception = nil
91
+ begin
92
+ ret = @abilities[func][:callback].call(args, Gearman::Job.new(self, handle))
93
+ rescue Exception => e
94
+ exception = e
95
+ end
96
+
97
+ if ret && exception.nil?
98
+ ret = ret.to_s
99
+ log "Sending work_complete for #{handle} with #{ret.size} byte(s)"
100
+ work_complete handle, ret
101
+ elsif exception.nil?
102
+ log "Sending work_fail for #{handle} to #{server}"
103
+ work_fail handle
104
+ elsif exception
105
+ log "exception #{exception.message}, sending work_warning, work_fail for #{handle}"
106
+ work_warning handle, exception.message
107
+ work_fail handle
108
+ end
109
+ end
110
+
111
+ def client_id
112
+ @client_id ||= `uuidgen`.strip
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,38 @@
1
+ module Gearman
2
+ # = Job
3
+ #
4
+ # == Description
5
+ # Interface to allow a worker to report information to a job server.
6
+ class Job
7
+ ##
8
+ # Create a new Job.
9
+ #
10
+ # @param sock Socket connected to job server
11
+ # @param handle job server-supplied job handle
12
+ def initialize(client, handle)
13
+ @client = client
14
+ @handle = handle
15
+ end
16
+ ##
17
+ # Report our status to the job server.
18
+ def report_status(numerator, denominator)
19
+ @client.send :work_status, "#{@handle}\0#{numerator}\0#{denominator}"
20
+ self
21
+ end
22
+
23
+ ##
24
+ # Send data before job completes
25
+ def send_partial(data)
26
+ @client.send :work_data, "#{@handle}\0#{data}"
27
+ self
28
+ end
29
+ alias :send_data :send_partial
30
+
31
+ ##
32
+ # Send a warning explicitly
33
+ def report_warning(warning)
34
+ @client.send :work_warning, "#{@handle}\0#{warning}"
35
+ self
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,110 @@
1
+ module Gearman
2
+
3
+ class ProtocolError < Exception; end
4
+
5
+
6
+ class Protocol
7
+ # Map from Integer representations of commands used in the network
8
+ # protocol to more-convenient symbols.
9
+ COMMANDS = {
10
+ 1 => :can_do, # W->J: FUNC
11
+ 2 => :cant_do, # W->J: FUNC
12
+ 3 => :reset_abilities, # W->J: --
13
+ 4 => :pre_sleep, # W->J: --
14
+ #5 => (unused), # - -
15
+ 6 => :noop, # J->W: --
16
+ 7 => :submit_job, # C->J: FUNC[0]UNIQ[0]ARGS
17
+ 8 => :job_created, # J->C: HANDLE
18
+ 9 => :grab_job, # W->J: --
19
+ 10 => :no_job, # J->W: --
20
+ 11 => :job_assign, # J->W: HANDLE[0]FUNC[0]ARG
21
+ 12 => :work_status, # W->J/C: HANDLE[0]NUMERATOR[0]DENOMINATOR
22
+ 13 => :work_complete, # W->J/C: HANDLE[0]RES
23
+ 14 => :work_fail, # W->J/C: HANDLE
24
+ 15 => :get_status, # C->J: HANDLE
25
+ 16 => :echo_req, # ?->J: TEXT
26
+ 17 => :echo_res, # J->?: TEXT
27
+ 18 => :submit_job_bg, # C->J: FUNC[0]UNIQ[0]ARGS
28
+ 19 => :error, # J->?: ERRCODE[0]ERR_TEXT
29
+ 20 => :status_res, # C->J: HANDLE[0]KNOWN[0]RUNNING[0]NUM[0]DENOM
30
+ 21 => :submit_job_high, # C->J: FUNC[0]UNIQ[0]ARGS
31
+ 22 => :set_client_id, # W->J: [RANDOM_STRING_NO_WHITESPACE]
32
+ 23 => :can_do_timeout, # W->J: FUNC[0]TIMEOUT
33
+ 24 => :all_yours, # REQ Worker
34
+ 25 => :work_exception, # W->J: HANDLE[0]ARG
35
+ 26 => :option_req, # C->J: TEXT
36
+ 27 => :option_res, # J->C: TEXT
37
+ 28 => :work_data, # REQ Worker
38
+ 29 => :work_warning, # W->J/C: HANDLE[0]MSG
39
+ 30 => :grab_job_uniq, # REQ Worker
40
+ 31 => :job_assign_uniq, # RES Worker
41
+ 32 => :submit_job_high_bg, # C->J: FUNC[0]UNIQ[0]ARGS
42
+ 33 => :submit_job_low, # C->J: FUNC[0]UNIQ[0]ARGS
43
+ 34 => :submit_job_low_bg, # C->J: FUNC[0]UNIQ[0]ARGS
44
+ 35 => :submit_job_sched, # REQ Client
45
+ 36 => :submit_job_epoch # REQ Client
46
+ }
47
+
48
+ # Map e.g. 'can_do' => 1
49
+ COMMANDS_NUMERIC = COMMANDS.invert
50
+
51
+ # Default job server port.
52
+ DEFAULT_PORT = 4730
53
+
54
+ class << self
55
+
56
+ def encode_request(type_name, arg = nil)
57
+ type_num = COMMANDS_NUMERIC[type_name.to_sym]
58
+ raise InvalidArgsError, "Invalid type name '#{type_name}'" unless type_num
59
+ arg = '' if not arg
60
+ "\0REQ" + [type_num, arg.size].pack('NN') + arg
61
+ end
62
+
63
+ def decode_response(data, packets = [])
64
+ # p data
65
+ magic, type, len = data[0..12].unpack('a4NN')
66
+ raise ProtocolError, "Invalid magic '#{magic}'" unless magic == "\0RES"
67
+ type = COMMANDS[type]
68
+ raise ProtocolError, "Invalid packet type #{type}" unless type
69
+
70
+ buf, *more = data[12, data.size].split("\0RES")
71
+ handle, data = buf ? buf.split("\0", 2) : nil
72
+ packets << response_packet(type, handle, data)
73
+ more.each do |packet|
74
+ decode_response("\0RES#{packet}", packets)
75
+ end
76
+ packets
77
+ end
78
+
79
+ def response_packet(type, handle, data)
80
+ case type
81
+ when :work_complete,
82
+ :work_exception,
83
+ :work_warning,
84
+ :work_data,
85
+ :error
86
+ [type, handle, data]
87
+ when :job_assign
88
+ func, data = data.split("\0", 3)
89
+ [type, handle, func.to_s, data]
90
+ when :job_assign_uniq
91
+ func, uuid, data = data.split("\0", 3)
92
+ [type, handle, func.to_s, data, uuid]
93
+ when :work_fail,
94
+ :job_created,
95
+ :no_job,
96
+ :noop
97
+ [type, handle]
98
+ when :work_status
99
+ num, den = data.split("\0", 3)
100
+ [type, handle, num, den]
101
+ when :status_res
102
+ known, running, num, den = data.split("\0", 4)
103
+ [type, handle, known, running, num, den]
104
+ else
105
+ raise ProtocolError, "Invalid packet #{type}"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end