gearman-ruby 2.0.0

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