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.
- data/.gitignore +1 -0
- data/HOWTO +146 -0
- data/LICENSE +20 -0
- data/README +9 -0
- data/Rakefile +41 -0
- data/TODO +8 -0
- data/VERSION.yml +4 -0
- data/examples/calculus_client.rb +41 -0
- data/examples/calculus_worker.rb +42 -0
- data/examples/client.rb +19 -0
- data/examples/client_background.rb +14 -0
- data/examples/client_data.rb +16 -0
- data/examples/client_echo.rb +16 -0
- data/examples/client_exception.rb +17 -0
- data/examples/client_prefix.rb +15 -0
- data/examples/evented_client.rb +23 -0
- data/examples/evented_worker.rb +26 -0
- data/examples/gearman_environment.sh +25 -0
- data/examples/scale_image.rb +30 -0
- data/examples/scale_image_worker.rb +34 -0
- data/examples/server.rb +15 -0
- data/examples/worker.rb +23 -0
- data/examples/worker_data.rb +16 -0
- data/examples/worker_echo.rb +20 -0
- data/examples/worker_echo_pprof.rb +5 -0
- data/examples/worker_exception.rb +14 -0
- data/examples/worker_prefix.rb +25 -0
- data/gearman-ruby.gemspec +111 -0
- data/lib/gearman.rb +29 -0
- data/lib/gearman/client.rb +80 -0
- data/lib/gearman/evented/client.rb +99 -0
- data/lib/gearman/evented/reactor.rb +86 -0
- data/lib/gearman/evented/worker.rb +118 -0
- data/lib/gearman/job.rb +38 -0
- data/lib/gearman/protocol.rb +110 -0
- data/lib/gearman/server.rb +94 -0
- data/lib/gearman/task.rb +99 -0
- data/lib/gearman/taskset.rb +11 -0
- data/lib/gearman/util.rb +52 -0
- data/lib/gearman/worker.rb +39 -0
- data/test/basic_integration_test.rb +121 -0
- data/test/crash_test.rb +69 -0
- data/test/job_test.rb +30 -0
- data/test/protocol_test.rb +132 -0
- data/test/test_helper.rb +31 -0
- data/test/util_test.rb +12 -0
- data/test/worker_test.rb +45 -0
- 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
|
data/lib/gearman/job.rb
ADDED
@@ -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
|