evented-gearman-ruby 1.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 +4 -0
- data/Rakefile +41 -0
- data/TODO +8 -0
- data/VERSION.yml +4 -0
- data/evented-gearman-ruby.gemspec +110 -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_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_exception.rb +14 -0
- data/examples/worker_prefix.rb +25 -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 +149 -0
@@ -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
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'gearman'
|
5
|
+
|
6
|
+
module Gearman
|
7
|
+
|
8
|
+
# = Server
|
9
|
+
#
|
10
|
+
# == Description
|
11
|
+
# A client for managing Gearman job servers.
|
12
|
+
class Server
|
13
|
+
##
|
14
|
+
# Create a new client.
|
15
|
+
#
|
16
|
+
# @param job_servers "host:port"; either a single server or an array
|
17
|
+
# @param prefix function name prefix (namespace)
|
18
|
+
def initialize(hostport)
|
19
|
+
@hostport = hostport # "host:port"
|
20
|
+
end
|
21
|
+
attr_reader :hostport
|
22
|
+
|
23
|
+
##
|
24
|
+
# Get a socket for a job server.
|
25
|
+
#
|
26
|
+
# @param hostport job server "host:port"
|
27
|
+
# @return a Socket
|
28
|
+
def socket(num_retries=3)
|
29
|
+
return @socket if @socket
|
30
|
+
num_retries.times do
|
31
|
+
begin
|
32
|
+
sock = TCPSocket.new(*hostport.split(':'))
|
33
|
+
rescue Exception
|
34
|
+
else
|
35
|
+
return @socket = sock
|
36
|
+
end
|
37
|
+
end
|
38
|
+
raise RuntimeError, "Unable to connect to job server #{hostport}"
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Sends a command to the server.
|
43
|
+
#
|
44
|
+
# @return a response string
|
45
|
+
def send_command(name)
|
46
|
+
response = ''
|
47
|
+
socket.puts(name)
|
48
|
+
while true do
|
49
|
+
if buf = socket.recv_nonblock(65536) rescue nil
|
50
|
+
response << buf
|
51
|
+
return response if response =~ /\n.\n$/
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Returns results of a 'status' command.
|
58
|
+
#
|
59
|
+
# @return a hash of abilities with queued, active and workers keys.
|
60
|
+
def status
|
61
|
+
status = {}
|
62
|
+
if response = send_command('status')
|
63
|
+
response.split("\n").each do |line|
|
64
|
+
if line.match /^([A-Za-z_]+)\t([A-Za-z_]+)\t(\d+)\t(\d+)\t(\d+)$/
|
65
|
+
(status[$1] ||= {})[$2] = { :queue => $3, :active => $4, :workers => $5 }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
status
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Returns results of a 'workers' command.
|
74
|
+
#
|
75
|
+
# @return an array of worker hashes, containing host, status and functions keys.
|
76
|
+
def workers
|
77
|
+
workers = []
|
78
|
+
if response = send_command('workers')
|
79
|
+
response.split("\n").each do |line|
|
80
|
+
if line.match /^(\d+)\s([a-z0-9\:\.]+)\s([^\s]*)\s:\s([a-z_\s\t]+)$/
|
81
|
+
func_parts = $4.split(' ')
|
82
|
+
functions = []
|
83
|
+
while !func_parts.empty?
|
84
|
+
functions << func_parts.shift << '.' << func_parts.shift
|
85
|
+
end
|
86
|
+
workers << { :host => $2, :status => $3, :functions => functions }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
workers
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|