gearman-ruby 3.0.8 → 4.0.2
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.
- checksums.yaml +8 -8
- data/.travis.yml +5 -0
- data/CHANGELOG.md +6 -4
- data/README.md +111 -0
- data/examples/client.rb +1 -2
- data/examples/client_reverse_nohost.rb +30 -0
- data/examples/{client_reverse.rb → client_reverse_wait.rb} +9 -11
- data/examples/worker.rb +8 -5
- data/examples/worker_reverse_string.rb +12 -17
- data/gearman-ruby.gemspec +3 -5
- data/lib/gearman.rb +17 -77
- data/lib/gearman/client.rb +129 -147
- data/lib/gearman/connection.rb +158 -0
- data/lib/gearman/connection_pool.rb +131 -0
- data/lib/gearman/exceptions.rb +24 -0
- data/lib/gearman/logging.rb +19 -0
- data/lib/gearman/packet.rb +61 -0
- data/lib/gearman/task.rb +1 -1
- data/lib/gearman/task_set.rb +67 -0
- data/lib/gearman/version.rb +1 -1
- data/lib/gearman/worker.rb +185 -412
- data/lib/gearman/worker/ability.rb +55 -0
- data/lib/gearman/worker/callbacks.rb +39 -0
- data/lib/gearman/worker/job.rb +44 -0
- data/spec/client_spec.rb +32 -20
- data/spec/connection_pool_spec.rb +55 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/task_spec.rb +10 -0
- data/spec/taskset_spec.rb +2 -2
- metadata +18 -37
- data/HOWTO +0 -146
- data/README +0 -9
- data/TODO +0 -8
- data/VERSION.yml +0 -4
- data/examples/calculus_client.rb +0 -39
- data/examples/calculus_worker.rb +0 -45
- data/examples/client.php +0 -23
- data/examples/client_background.rb +0 -14
- data/examples/client_data.rb +0 -16
- data/examples/client_epoch.rb +0 -23
- data/examples/client_exception.rb +0 -19
- data/examples/client_prefix.rb +0 -17
- data/examples/gearman_environment.sh +0 -25
- data/examples/scale_image.rb +0 -31
- data/examples/scale_image_worker.rb +0 -34
- data/examples/server.rb +0 -15
- data/examples/worker_data.rb +0 -16
- data/examples/worker_exception.rb +0 -14
- data/examples/worker_prefix.rb +0 -25
- data/examples/worker_reverse_to_file.rb +0 -18
- data/examples/worker_signals.rb +0 -36
- data/lib/gearman/taskset.rb +0 -293
- data/lib/gearman/util.rb +0 -211
- data/spec/util_spec.rb +0 -67
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Gearman
|
4
|
+
class Connection
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
def initialize(hostname, port)
|
8
|
+
@hostname = hostname
|
9
|
+
@port = port
|
10
|
+
@real_socket = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :hostname, :port, :state, :socket
|
14
|
+
|
15
|
+
##
|
16
|
+
# Check server health status by sending an ECHO request
|
17
|
+
# Return true / false
|
18
|
+
##
|
19
|
+
def is_healthy?
|
20
|
+
if @real_socket == nil
|
21
|
+
logger.debug "Performing health check for #{self}"
|
22
|
+
begin
|
23
|
+
request = Packet.pack_request("echo_req", "ping")
|
24
|
+
response = send_request(request, 3)
|
25
|
+
logger.debug "Health check response for #{self} is #{response.inspect}"
|
26
|
+
raise ProtocolError unless response[0] == :echo_res and response[1] == "ping"
|
27
|
+
return true
|
28
|
+
rescue NetworkError
|
29
|
+
logger.debug "NetworkError -- unhealthy"
|
30
|
+
return false
|
31
|
+
rescue ProtocolError
|
32
|
+
logger.debug "ProtocolError -- unhealthy"
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# @param num_retries Number of times to retry
|
40
|
+
# @return This connection's Socket
|
41
|
+
##
|
42
|
+
def socket(num_retries=3)
|
43
|
+
# If we already have an open socket to this host, return it.
|
44
|
+
return @real_socket if @real_socket
|
45
|
+
num_retries.times do |i|
|
46
|
+
begin
|
47
|
+
logger.debug("Attempt ##{i} to connect to #{hostname}:#{port}")
|
48
|
+
@real_socket = TCPSocket.new(hostname, port)
|
49
|
+
rescue Exception => e
|
50
|
+
logger.error("Unable to connect: #{e}")
|
51
|
+
# Swallow error so we can retry -> num_retries times
|
52
|
+
else
|
53
|
+
return @real_socket
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
raise_exception("Unable to connect to job server #{hostname}:#{port}")
|
58
|
+
end
|
59
|
+
|
60
|
+
def close_socket
|
61
|
+
@real_socket.close if @real_socket
|
62
|
+
@real_socket = nil
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def raise_exception(message)
|
67
|
+
close_socket
|
68
|
+
raise NetworkError, message
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Read from a socket, giving up if it doesn't finish quickly enough.
|
73
|
+
# NetworkError is thrown if we don't read all the bytes in time.
|
74
|
+
#
|
75
|
+
# @param sock Socket from which we read
|
76
|
+
# @param len number of bytes to read
|
77
|
+
# @param timeout maximum number of seconds we'll take; nil for no timeout
|
78
|
+
# @return full data that was read
|
79
|
+
def timed_recv(sock, len, timeout=nil)
|
80
|
+
data = ''
|
81
|
+
start_time = Time.now.to_f
|
82
|
+
end_time = Time.now.to_f + timeout if timeout
|
83
|
+
while data.size < len and (not timeout or Time.now.to_f < end_time) do
|
84
|
+
IO::select([sock], nil, nil, timeout ? end_time - Time.now.to_f : nil) \
|
85
|
+
or break
|
86
|
+
begin
|
87
|
+
data += sock.readpartial(len - data.size)
|
88
|
+
rescue
|
89
|
+
close_socket
|
90
|
+
raise NetworkError, "Unable to read data from socket."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
if data.size < len
|
94
|
+
now = Time.now.to_f
|
95
|
+
if now > end_time
|
96
|
+
time_lapse = now - start_time
|
97
|
+
raise SocketTimeoutError, "Took too long to read data: #{time_lapse} sec. to read on a #{timeout} sec. timeout"
|
98
|
+
else
|
99
|
+
raise_exception("Read #{data.size} byte(s) instead of #{len}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
data
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Read a response packet from a socket.
|
107
|
+
#
|
108
|
+
# @param sock Socket connected to a job server
|
109
|
+
# @param timeout timeout in seconds, nil for no timeout
|
110
|
+
# @return array consisting of integer packet type and data
|
111
|
+
def read_response(timeout=nil)
|
112
|
+
end_time = Time.now.to_f + timeout if timeout
|
113
|
+
head = timed_recv(socket, 12, timeout)
|
114
|
+
magic, type, len = head.unpack('a4NN')
|
115
|
+
raise ProtocolError, "Invalid magic '#{magic}'" unless magic == "\0RES"
|
116
|
+
buf = len > 0 ?
|
117
|
+
timed_recv(socket, len, timeout ? end_time - Time.now.to_f : nil) : ''
|
118
|
+
type = Packet::COMMANDS[type]
|
119
|
+
raise ProtocolError, "Invalid packet type #{type}" unless type
|
120
|
+
[type, buf]
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Send a request packet over a socket that needs a response.
|
125
|
+
#
|
126
|
+
# @param sock Socket connected to a job server
|
127
|
+
# @param req request packet to send
|
128
|
+
# @result response from server
|
129
|
+
def send_request(req, timeout = nil)
|
130
|
+
send_update(req, timeout)
|
131
|
+
return read_response(timeout)
|
132
|
+
end
|
133
|
+
|
134
|
+
def send_update(req, timeout = nil)
|
135
|
+
len = with_safe_socket_op{ socket.write(req) }
|
136
|
+
if len != req.size
|
137
|
+
raise_exception("Wrote #{len} instead of #{req.size}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def with_safe_socket_op
|
142
|
+
begin
|
143
|
+
yield
|
144
|
+
rescue Exception => ex
|
145
|
+
raise_exception(ex.message)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_host_port
|
150
|
+
"#{hostname}:#{port}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_s
|
154
|
+
"#{hostname}:#{port} (connected: #{@real_socket != nil})"
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Gearman
|
4
|
+
class ConnectionPool
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
DEFAULT_PORT = 4730
|
8
|
+
|
9
|
+
def initialize(servers = [])
|
10
|
+
@bad_servers = []
|
11
|
+
@coalesce_connections = {}
|
12
|
+
@connection_handler = nil
|
13
|
+
@job_servers = []
|
14
|
+
@reconnect_seconds = 10
|
15
|
+
@server_counter = 0 # Round-robin distribution of requests
|
16
|
+
@servers_mutex = Mutex.new
|
17
|
+
|
18
|
+
add_servers(servers)
|
19
|
+
start_reconnect_thread
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_connection(connection)
|
23
|
+
@servers_mutex.synchronize do
|
24
|
+
if connection.is_healthy?
|
25
|
+
activate_connection(connection)
|
26
|
+
else
|
27
|
+
deactivate_connection(connection)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_host_port(host_port)
|
33
|
+
host, port = host_port.split(":")
|
34
|
+
connection = Connection.new(host, port.to_i)
|
35
|
+
add_connection(connection)
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_servers(servers)
|
39
|
+
if servers.class == String or servers.class == Symbol
|
40
|
+
servers = [ servers.to_s ]
|
41
|
+
end
|
42
|
+
|
43
|
+
servers = servers.map {|s| s =~ /:/ ? s : "#{s}:#{DEFAULT_PORT}" }
|
44
|
+
|
45
|
+
servers.each do |host_port|
|
46
|
+
add_host_port(host_port)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_connection(coalesce_key = nil)
|
51
|
+
@servers_mutex.synchronize do
|
52
|
+
|
53
|
+
logger.debug "Available job servers: #{@job_servers.inspect}"
|
54
|
+
raise NoJobServersError if @job_servers.empty?
|
55
|
+
@server_counter += 1
|
56
|
+
@job_servers[@server_counter % @job_servers.size]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_connection(&block)
|
61
|
+
@connection_handler = block
|
62
|
+
end
|
63
|
+
|
64
|
+
def poll_connections(timeout = nil)
|
65
|
+
available_sockets = []
|
66
|
+
@servers_mutex.synchronize do
|
67
|
+
available_sockets.concat @job_servers.collect { |conn| conn.socket }
|
68
|
+
end
|
69
|
+
if available_sockets.size > 0
|
70
|
+
logger.debug "Polling on #{available_sockets.size} available server(s) with a #{timeout} second timeout"
|
71
|
+
IO::select(available_sockets, nil, nil, timeout)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_all_connections(&block)
|
76
|
+
@servers_mutex.synchronize do
|
77
|
+
@job_servers.each do |connection|
|
78
|
+
begin
|
79
|
+
block.call(connection)
|
80
|
+
rescue NetworkError => ex
|
81
|
+
logger.debug "Error with #{connection}, marking as bad"
|
82
|
+
deactivate_connection(connection)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def deactivate_connection(connection)
|
92
|
+
@job_servers.reject! { |c| c == connection }
|
93
|
+
@bad_servers << connection
|
94
|
+
end
|
95
|
+
|
96
|
+
def activate_connection(connection)
|
97
|
+
@bad_servers.reject! { |c| c == connection }
|
98
|
+
@job_servers << connection
|
99
|
+
@connection_handler.call(connection) if @connection_handler
|
100
|
+
end
|
101
|
+
|
102
|
+
def start_reconnect_thread
|
103
|
+
Thread.new do
|
104
|
+
loop do
|
105
|
+
@servers_mutex.synchronize do
|
106
|
+
# If there are any failed servers, try to reconnect to them.
|
107
|
+
update_job_servers unless @bad_servers.empty?
|
108
|
+
end
|
109
|
+
sleep @reconnect_seconds
|
110
|
+
end
|
111
|
+
end.run
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_job_servers
|
115
|
+
logger.debug "Found #{@bad_servers.size} zombie connections, checking pulse."
|
116
|
+
@bad_servers.each do |connection|
|
117
|
+
begin
|
118
|
+
message = "Testing server #{connection}..."
|
119
|
+
if connection.is_healthy?
|
120
|
+
logger.debug "#{message} Connection is healthy, putting back into service"
|
121
|
+
activate_connection(connection)
|
122
|
+
else
|
123
|
+
logger.debug "#{message} Still down."
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Gearman
|
2
|
+
|
3
|
+
class InvalidArgsError < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
class ProtocolError < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
class NetworkError < Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
class NoJobServersError < Exception
|
13
|
+
end
|
14
|
+
|
15
|
+
class JobQueueError < Exception
|
16
|
+
end
|
17
|
+
|
18
|
+
class SocketTimeoutError < Exception
|
19
|
+
end
|
20
|
+
|
21
|
+
class ServerDownException < Exception
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Gearman
|
2
|
+
class Packet
|
3
|
+
# Map from Integer representations of commands used in the network
|
4
|
+
# protocol to more-convenient symbols.
|
5
|
+
COMMANDS = {
|
6
|
+
1 => :can_do, # W->J: FUNC
|
7
|
+
2 => :cant_do, # W->J: FUNC
|
8
|
+
3 => :reset_abilities, # W->J: --
|
9
|
+
4 => :pre_sleep, # W->J: --
|
10
|
+
#5 => (unused), # - -
|
11
|
+
6 => :noop, # J->W: --
|
12
|
+
7 => :submit_job, # C->J: FUNC[0]UNIQ[0]ARGS
|
13
|
+
8 => :job_created, # J->C: HANDLE
|
14
|
+
9 => :grab_job, # W->J: --
|
15
|
+
10 => :no_job, # J->W: --
|
16
|
+
11 => :job_assign, # J->W: HANDLE[0]FUNC[0]ARG
|
17
|
+
12 => :work_status, # W->J/C: HANDLE[0]NUMERATOR[0]DENOMINATOR
|
18
|
+
13 => :work_complete, # W->J/C: HANDLE[0]RES
|
19
|
+
14 => :work_fail, # W->J/C: HANDLE
|
20
|
+
15 => :get_status, # C->J: HANDLE
|
21
|
+
16 => :echo_req, # ?->J: TEXT
|
22
|
+
17 => :echo_res, # J->?: TEXT
|
23
|
+
18 => :submit_job_bg, # C->J: FUNC[0]UNIQ[0]ARGS
|
24
|
+
19 => :error, # J->?: ERRCODE[0]ERR_TEXT
|
25
|
+
20 => :status_res, # C->J: HANDLE[0]KNOWN[0]RUNNING[0]NUM[0]DENOM
|
26
|
+
21 => :submit_job_high, # C->J: FUNC[0]UNIQ[0]ARGS
|
27
|
+
22 => :set_client_id, # W->J: [RANDOM_STRING_NO_WHITESPACE]
|
28
|
+
23 => :can_do_timeout, # W->J: FUNC[0]TIMEOUT
|
29
|
+
24 => :all_yours, # REQ Worker
|
30
|
+
25 => :work_exception, # W->J: HANDLE[0]ARG
|
31
|
+
26 => :option_req, # C->J: TEXT
|
32
|
+
27 => :option_res, # J->C: TEXT
|
33
|
+
28 => :work_data, # REQ Worker
|
34
|
+
29 => :work_warning, # W->J/C: HANDLE[0]MSG
|
35
|
+
30 => :grab_job_uniq, # REQ Worker
|
36
|
+
31 => :job_assign_uniq, # RES Worker
|
37
|
+
32 => :submit_job_high_bg, # C->J: FUNC[0]UNIQ[0]ARGS
|
38
|
+
33 => :submit_job_low, # C->J: FUNC[0]UNIQ[0]ARGS
|
39
|
+
34 => :submit_job_low_bg, # C->J: FUNC[0]UNIQ[0]ARGS
|
40
|
+
35 => :submit_job_sched, # REQ Client
|
41
|
+
36 => :submit_job_epoch # C->J: FUNC[0]UNIQ[0]EPOCH[0]ARGS
|
42
|
+
}
|
43
|
+
|
44
|
+
# Map e.g. 'can_do' => 1
|
45
|
+
NUMS = COMMANDS.invert
|
46
|
+
|
47
|
+
##
|
48
|
+
# Construct a request packet.
|
49
|
+
#
|
50
|
+
# @param type_name command type's name (see COMMANDS)
|
51
|
+
# @param arg optional data to pack into the command
|
52
|
+
# @return packet (as a string)
|
53
|
+
def Packet.pack_request(type_name, arg='')
|
54
|
+
type_num = NUMS[type_name.to_sym]
|
55
|
+
raise InvalidArgsError, "Invalid type name '#{type_name}'" unless type_num
|
56
|
+
arg = '' if not arg
|
57
|
+
"\0REQ" + [type_num, arg.size].pack('NN') + arg
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/gearman/task.rb
CHANGED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Gearman
|
4
|
+
class TaskSet
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
@tasks_in_progress = []
|
10
|
+
@finished_tasks = []
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Add a new task to this TaskSet.
|
15
|
+
#
|
16
|
+
# @param args A Task object
|
17
|
+
# @return true if the task was created successfully, false otherwise
|
18
|
+
def add_task(task)
|
19
|
+
@tasks_in_progress << task
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Wait for all tasks in the set to finish.
|
24
|
+
#
|
25
|
+
# @param timeout maximum amount of time to wait, in seconds - if this is nil, waits forever
|
26
|
+
def wait(timeout = 1)
|
27
|
+
end_time = if timeout
|
28
|
+
Time.now.to_f + timeout
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
while not @tasks_in_progress.empty?
|
34
|
+
remaining = if end_time
|
35
|
+
(t = end_time - Time.now.to_f) > 0 ? t : 0
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
begin
|
40
|
+
task = @tasks_in_progress.pop
|
41
|
+
if
|
42
|
+
@client.submit_job(task, true, remaining)
|
43
|
+
@finished_tasks << task
|
44
|
+
end
|
45
|
+
rescue SocketTimeoutError
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
@finished_tasks.each do |t|
|
52
|
+
if ( (t.background.nil? || t.background == false) && !t.successful)
|
53
|
+
logger.warn "GearmanRuby: TaskSet failed"
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Wait for all tasks in set to finish, with no timeout
|
61
|
+
def wait_forever
|
62
|
+
wait(nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|