xing-gearman-ruby 1.0.0 → 1.1.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/HOWTO ADDED
@@ -0,0 +1,146 @@
1
+ = GEARMAN
2
+
3
+ "Gearman provides a generic application framework to farm out work to other
4
+ machines or processes that are better suited to do the work. It allows you to
5
+ do work in parallel, to load balance processing, and to call functions between
6
+ languages. It can be used in a variety of applications, from high-availability
7
+ web sites to the transport of database replication events. In other words, it
8
+ is the nervous system for how distributed processing communicates."
9
+
10
+ - http://www.gearman.org/
11
+
12
+
13
+ == Setting up a basic environment
14
+
15
+ A very basic German environment will look like this:
16
+
17
+ ----------
18
+ | Client |
19
+ ----------
20
+ |
21
+ --------------
22
+ | Job Server |
23
+ --------------
24
+ |
25
+ ----------------------------------------------
26
+ | | | |
27
+ ---------- ---------- ---------- ----------
28
+ | Worker | | Worker | | Worker | | Worker |
29
+ ---------- ---------- ---------- ----------
30
+
31
+ And the behavior will be the following:
32
+
33
+ * JobServer: Acts as a message passing point.
34
+ * Client: Sends tasks to the JobServer. Will be connected to only one JobServer
35
+ in case more than one exits for failover purposes.
36
+ * Worker: Anounce his 'abilities' to the JobServer and waits for tasks.
37
+
38
+ For the JobServer we recommend to use the offical Perl version, there's also a
39
+ more performant C implementation of the server with support for persistent
40
+ queues, bells and whistles but is not stable enough for production use at the
41
+ time of this document was wrote.
42
+
43
+ The Client and the Worker can be implemented in any language. This way you can
44
+ send tasks from a Ruby client server, to a Perl or C worker in order to get
45
+ better performance.
46
+
47
+ == Installing the required software
48
+
49
+ For the JobServer we recommend to use the offical Perl version, to install it:
50
+
51
+ * Mac OS X: sudo port install p5-gearman-server
52
+ * Debian/Ubuntu: sudo apt-get install gearman-server
53
+
54
+ To get the Ruby libraries by Xing:
55
+
56
+ git clone git://github.com/xing/gearman-ruby.git
57
+
58
+ == Gearman demo
59
+
60
+ Now you're ready for you first experience with Gearman. In the cloned repository
61
+ you'll find an 'examples' directory.
62
+
63
+ Run the 'gearman_environment.sh' to build an environment like the one showed in
64
+ the diagram above.
65
+
66
+ * Client: Will ask you for an arithmetic operation, like: 2+3
67
+ The code of the client is in: 'examples/calculus_client.rb'
68
+
69
+ * JobServer: The Perl server.
70
+
71
+ * Workers: You'll have 4 worker, one for each of the basic arithmetic
72
+ operations.
73
+ The code of the worker is in: 'examples/calculus_worker.rb'
74
+
75
+ There are other demos in the examples folder you can give a look at. Each demo usually
76
+ consist in the client and server scripts.
77
+
78
+ === Creating clients and tasks
79
+
80
+ In order to get a job scheduled by a Gearman server using the gearman ruby library, there
81
+ are three main objects you must interact with: Gearman::Client, Gearman::Task and Gearman::TaskSet.
82
+ Let's review all of them briefly:
83
+
84
+ - Gearman::Client -> the portion of the library storing the data about the connection to
85
+ the Gearman server.
86
+ - Gearman::Task -> a job execution request that will be dispatched by the Gearman server to
87
+ worker and whose result data will be returned to the client.
88
+ - Gearman::TaskSet -> a collection of tasks to be executed. The Taskset object will track the
89
+ execution of the tasks with the info returned from the Gearman server
90
+ and notify the client with the results or errors when all the tasks
91
+ have completed their execution.
92
+
93
+ To send a new task to the Gearman server, the client must build a new Gearman::Task object, add it to
94
+ a Gearman::TaskSet that must hold a reference to a Gearman::Client and send the wait message to
95
+ the TaskSet.
96
+ The following code taken from examples/client.rb shows the process:
97
+
98
+ ----------------------------------------------------
99
+ servers = ['localhost:4730', 'localhost:4731']
100
+
101
+ client = Gearman::Client.new(servers)
102
+ taskset = Gearman::TaskSet.new(client)
103
+
104
+ task = Gearman::Task.new('sleep', 20)
105
+ task.on_complete {|d| puts d }
106
+
107
+ taskset.add_task(task)
108
+ taskset.wait(100)
109
+ ----------------------------------------------------
110
+
111
+ The name of the function to be executed is the first parameter to the constructor of the Task object.
112
+ Take into account that the string you pass as a parameter will be used 'as it' by the Gearman server
113
+ to locate a suitable worker for that function.
114
+ The second parameter is the argument to be sent to the worker that will execute, if the arguments for
115
+ the task are complex, a serialization format like YAML or XML must be agreeded with the workers.
116
+ The last and optional parameter is a hash of options. The following options are currently available:
117
+
118
+ - :priority -> (:high | :low) the priority of the job, a high priority job is executed before a low on
119
+ - :background -> (true | false) a background task will return no further information to the client.
120
+
121
+ The execution of a task in a Gearman remote worker can fail, the worker can throw an exception, etc.
122
+ All these events can be handled by the client of the ruby library registering callback blocks.
123
+ The following events are currently available:
124
+
125
+ - on_complete -> the task was executed succesfully
126
+ - on_fail -> the task fail for some unknown reason
127
+ - on_retry -> after failure, the task is gonna be retried, the number of retries is passed
128
+ - on_exception -> the remote worker send an exception notification, the exception text is passed
129
+ - on_stauts -> a status update is sent by the remote worker
130
+
131
+ In order to receive exception notifications in the client, this option must be sent in the server, the
132
+ method option_request can be used this task. The following example, extracted from the examples/client_exception.rb
133
+ demo script shows the process:
134
+
135
+ ----------------------------------------------------
136
+ client = Gearman::Client.new(servers)
137
+ #try this out
138
+ client.option_request("exceptions")
139
+ ----------------------------------------------------
140
+
141
+ This feature will only works if the server and workers have implemented support for the OPT_REQ and WORK_EXCEPTION
142
+ messages of the Gearman protocol.
143
+
144
+
145
+ Enjoy.
146
+
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ - Failover strategies
2
+ - Client:
3
+ * If connected for the first time, try to connect to at least one server from the server array
4
+ * If already connected to a server, and it goes down, the client should go down as well
5
+ - Worker:
6
+ * If connected for the first time, try to connect to as many servers as it can.
7
+ Loop trough the bad servers, trying to reconnect to them as well.
8
+ * If a already connected to a server, and it goes down, wait and try to reconnect again.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :major: 1
3
- :minor: 0
4
2
  :patch: 0
3
+ :major: 1
4
+ :minor: 1
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require '../lib/gearman'
4
+ #Gearman::Util.debug = true
5
+
6
+ # Connect to the local server (at the default port 7003)
7
+ client = Gearman::Client.new('localhost')
8
+ taskset = Gearman::TaskSet.new(client)
9
+
10
+ # Get something to echo
11
+ puts '[client] Write a basic arithmetic operation:'
12
+ input = gets
13
+
14
+ operations = input.chomp.scan(/\d+[\+\-\*\/]\d+/).compact
15
+ puts "[client] The following operations were found: #{operations.inspect}"
16
+
17
+ # Setup a task for operation
18
+ operations.each do |op|
19
+ # Determining the operation
20
+ case op
21
+ when /\+/
22
+ type, data = 'addition', op.split('+')
23
+ when /\-/
24
+ type, data = 'subtraction', op.split('-')
25
+ when /\*/
26
+ type, data = 'multiplication', op.split('*')
27
+ when /\//
28
+ type, data = 'division', op.split('/')
29
+ end
30
+
31
+ task = Gearman::Task.new(type, Marshal.dump(data.map {|v| v.to_i}))
32
+ task.on_complete {|r| puts "[client] #{type} result is: #{r}" }
33
+
34
+ # Sending the task to the server
35
+ puts "[client] Sending values: #{data.inspect}, to the '#{type}' worker"
36
+ taskset.add_task(task)
37
+ taskset.wait(100)
38
+ end
39
+
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require '../lib/gearman'
4
+
5
+ #Gearman::Util.debug = true
6
+
7
+ worker = Gearman::Worker.new('localhost')
8
+ worker.reconnect_sec = 2
9
+
10
+ # Additon ability
11
+ worker.add_ability('addition') do |data,job|
12
+ values = Marshal.load(data)
13
+ puts "[addition_worker] Calculating #{values.inspect}..."
14
+ # sleep 5
15
+ values.first + values.last
16
+ end
17
+
18
+ # Subtraction ability
19
+ worker.add_ability('subtraction') do |data,job|
20
+ values = Marshal.load(data)
21
+ puts "[subtraction_worker] Calculating #{values.inspect}..."
22
+ # sleep 5
23
+ values.first - values.last
24
+ end
25
+
26
+ # Multiplication worker
27
+ worker.add_ability('multiplication') do |data,job|
28
+ values = Marshal.load(data)
29
+ puts "[multiplication_worker] Calculating #{values.inspect}..."
30
+ # sleep 5
31
+ values.first * values.last
32
+ end
33
+
34
+ # Division worker
35
+ worker.add_ability('division') do |data,job|
36
+ values = Marshal.load(data)
37
+ puts "[division_worker] Calculating #{data.inspect}..."
38
+ # sleep 5
39
+ values.first / values.last
40
+ end
41
+
42
+ # Running the workers
43
+ loop do
44
+ worker.work
45
+ end
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ #require 'gearman'
3
+ require '../lib/gearman'
4
+ Gearman::Util.debug = true
5
+
6
+ servers = ['localhost:4730', 'localhost:4731']
7
+
8
+ client = Gearman::Client.new(servers)
9
+ taskset = Gearman::TaskSet.new(client)
10
+
11
+ task = Gearman::Task.new('sleep', 20, { :background => true })
12
+ task.on_complete {|d| puts d }
13
+
14
+ taskset.add_task(task)
15
+ taskset.wait(100)
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require '../lib/gearman'
3
+ Gearman::Util.debug = true
4
+
5
+ servers = ['localhost:4730']
6
+
7
+ client = Gearman::Client.new(servers)
8
+ #try this out
9
+ client.option_request("exceptions")
10
+
11
+ taskset = Gearman::TaskSet.new(client)
12
+
13
+ task = Gearman::Task.new('fail_with_exception', 20)
14
+ task.on_complete {|d| puts d }
15
+ task.on_exception {|message| puts message; false}
16
+
17
+ taskset.add_task(task)
18
+ taskset.wait(100)
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ #require 'gearman'
3
+ require '../lib/gearman'
4
+ Gearman::Util.debug = true
5
+
6
+ servers = ['localhost:4730', 'localhost:4731']
7
+
8
+ ability_name_with_prefix = Gearman::Util.ability_name_with_prefix("test","sleep")
9
+
10
+ client = Gearman::Client.new(servers)
11
+ taskset = Gearman::TaskSet.new(client)
12
+
13
+ task = Gearman::Task.new(ability_name_with_prefix, 20)
14
+ task.on_complete {|d| puts d }
15
+
16
+ taskset.add_task(task)
17
+ taskset.wait(100)
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+
3
+ # Start Gearmand
4
+ echo ' + Starting Gearmand'
5
+ gearmand --daemon --pidfile=/tmp/gearmand.pid
6
+
7
+ # Start the client and the worker(s)
8
+ echo ' + Starting calculus_worker.rb'
9
+ ruby calculus_worker.rb &
10
+
11
+ sleep 3
12
+
13
+ echo ' + Starting calculus_client.rb'
14
+ ruby calculus_client.rb
15
+
16
+ echo ' +++ Example finished +++ '
17
+
18
+ # Stop Gearmand
19
+ echo ' - Stopping Gearmand'
20
+ kill -9 `cat /tmp/gearmand.pid`
21
+
22
+ # Stop the workers
23
+ echo ' - Stopping calculus_worker.rb'
24
+ kill -9 `ps ax|grep calculus_worker|grep ruby|awk -F' ' '{print $1}'`
25
+
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require '../lib/gearman'
3
+
4
+ Gearman::Util.debug = true
5
+
6
+ servers = ['localhost:4730']
7
+ w = Gearman::Worker.new(servers)
8
+
9
+ # Add a handler for a "sleep" function that takes a single argument, the
10
+ # number of seconds to sleep before reporting success.
11
+ w.add_ability('fail_with_exception') do |data,job|
12
+ raise Exception.new("fooexception")
13
+ end
14
+ loop { w.work }
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ #require 'gearman'
3
+ require '../lib/gearman'
4
+
5
+ Gearman::Util.debug = true
6
+
7
+ servers = ['localhost:4730', 'localhost:4731']
8
+ w = Gearman::Worker.new(servers)
9
+
10
+ ability_name_with_prefix = Gearman::Util.ability_name_with_prefix("test","sleep")
11
+
12
+ # Add a handler for a "sleep" function that takes a single argument, the
13
+ # number of seconds to sleep before reporting success.
14
+ w.add_ability(ability_name_with_prefix) do |data,job|
15
+ seconds = data
16
+ (1..seconds.to_i).each do |i|
17
+ sleep 1
18
+ print i
19
+ # Report our progress to the job server every second.
20
+ job.report_status(i, seconds)
21
+ end
22
+ # Report success.
23
+ true
24
+ end
25
+ loop { w.work }
data/gearman-ruby.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{gearman-ruby}
5
- s.version = "1.0.0"
5
+ s.version = "1.1.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Daniel Erat", "Ladislav Martincik"]
9
- s.date = %q{2009-06-25}
9
+ s.date = %q{2009-07-16}
10
10
  s.description = %q{Library for the Gearman distributed job system}
11
11
  s.email = %q{ladislav.martincik@xing.com}
12
12
  s.extra_rdoc_files = [
@@ -15,9 +15,11 @@ Gem::Specification.new do |s|
15
15
  ]
16
16
  s.files = [
17
17
  ".gitignore",
18
+ "HOWTO",
18
19
  "LICENSE",
19
20
  "README",
20
21
  "Rakefile",
22
+ "TODO",
21
23
  "VERSION.yml",
22
24
  "examples/Net/Gearman/Client.php",
23
25
  "examples/Net/Gearman/Connection.php",
@@ -30,13 +32,21 @@ Gem::Specification.new do |s|
30
32
  "examples/Net/Gearman/Set.php",
31
33
  "examples/Net/Gearman/Task.php",
32
34
  "examples/Net/Gearman/Worker.php",
35
+ "examples/calculus_client.rb",
36
+ "examples/calculus_worker.rb",
33
37
  "examples/client.php",
34
38
  "examples/client.rb",
39
+ "examples/client_background.rb",
40
+ "examples/client_exception.rb",
41
+ "examples/client_prefix.rb",
42
+ "examples/gearman_environment.sh",
35
43
  "examples/scale_image.rb",
36
44
  "examples/scale_image_worker.rb",
37
45
  "examples/server.rb",
38
46
  "examples/worker.php",
39
47
  "examples/worker.rb",
48
+ "examples/worker_exception.rb",
49
+ "examples/worker_prefix.rb",
40
50
  "gearman-ruby.gemspec",
41
51
  "lib/gearman.rb",
42
52
  "lib/gearman/client.rb",
@@ -49,29 +59,37 @@ Gem::Specification.new do |s|
49
59
  "test/client_test.rb",
50
60
  "test/mock_client_test.rb",
51
61
  "test/mock_worker_test.rb",
62
+ "test/util_test.rb",
52
63
  "test/worker_test.rb"
53
64
  ]
54
- s.has_rdoc = true
55
65
  s.homepage = %q{http://github.com/xing/gearman-ruby}
56
66
  s.rdoc_options = ["--charset=UTF-8"]
57
67
  s.require_paths = ["lib"]
58
- s.rubygems_version = %q{1.3.1}
68
+ s.rubygems_version = %q{1.3.4}
59
69
  s.summary = %q{Library for the Gearman distributed job system}
60
70
  s.test_files = [
61
71
  "test/client_test.rb",
62
72
  "test/mock_client_test.rb",
63
73
  "test/mock_worker_test.rb",
74
+ "test/util_test.rb",
64
75
  "test/worker_test.rb",
76
+ "examples/calculus_client.rb",
77
+ "examples/calculus_worker.rb",
65
78
  "examples/client.rb",
79
+ "examples/client_background.rb",
80
+ "examples/client_exception.rb",
81
+ "examples/client_prefix.rb",
66
82
  "examples/scale_image.rb",
67
83
  "examples/scale_image_worker.rb",
68
84
  "examples/server.rb",
69
- "examples/worker.rb"
85
+ "examples/worker.rb",
86
+ "examples/worker_exception.rb",
87
+ "examples/worker_prefix.rb"
70
88
  ]
71
89
 
72
90
  if s.respond_to? :specification_version then
73
91
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
74
- s.specification_version = 2
92
+ s.specification_version = 3
75
93
 
76
94
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
77
95
  else
@@ -23,8 +23,21 @@ class Client
23
23
  @server_counter = -1
24
24
  @bad_servers = []
25
25
  end
26
- attr_reader :job_servers, :bad_servers
27
- attr_accessor :test_hostport, :task_create_timeout_sec
26
+ attr_reader :job_servers, :bad_servers
27
+ attr_accessor :test_hostport, :task_create_timeout_sec
28
+
29
+ ##
30
+ # Set the options
31
+ #
32
+ # @options options to pass to the servers "exeptions"
33
+ def option_request(opts)
34
+ Util.log "Send options request with #{opts}"
35
+ request = Util.pack_request("option_req", opts)
36
+ sock= self.get_socket(self.get_job_server)
37
+ Util.send_request(sock, request)
38
+ response = Util.read_response(sock, 20)
39
+ raise ProtocolError, response[1] if response[0]==:error
40
+ end
28
41
 
29
42
  ##
30
43
  # Set the job servers to be used by this client.
data/lib/gearman/task.rb CHANGED
@@ -7,6 +7,7 @@ module Gearman
7
7
  # == Description
8
8
  # A task submitted to a Gearman job server.
9
9
  class Task
10
+
10
11
  ##
11
12
  # Create a new Task object.
12
13
  #
@@ -16,8 +17,8 @@ class Task
16
17
  def initialize(func, arg='', opts={})
17
18
  @func = func.to_s
18
19
  @arg = arg or '' # TODO: use something more ref-like?
19
- %w{uniq on_complete on_fail on_retry on_status retry_count
20
- high_priority}.map {|s| s.to_sym }.each do |k|
20
+ %w{uniq on_complete on_fail on_retry on_exception on_status retry_count
21
+ priority background}.map {|s| s.to_sym }.each do |k|
21
22
  instance_variable_set "@#{k}", opts[k]
22
23
  opts.delete k
23
24
  end
@@ -29,7 +30,7 @@ class Task
29
30
  @retries_done = 0
30
31
  @hash = nil
31
32
  end
32
- attr_accessor :uniq, :retry_count, :high_priority
33
+ attr_accessor :uniq, :retry_count, :priority, :background
33
34
  attr_reader :successful, :func, :arg
34
35
 
35
36
  ##
@@ -62,6 +63,14 @@ class Task
62
63
  @on_retry = f
63
64
  end
64
65
 
66
+ ##
67
+ # Set a block of code to be executed when a remote exception is sent by a worker.
68
+ # The block will receive the message of the exception passed from the worker.
69
+ # The user can return true for retrying or false to mark it as finished
70
+ def on_exception(&f)
71
+ @on_exception = f
72
+ end
73
+
65
74
  ##
66
75
  # Set a block of code to be executed when we receive a status update for
67
76
  # this task. The block will receive two arguments, a numerator and
@@ -94,6 +103,20 @@ class Task
94
103
  true
95
104
  end
96
105
 
106
+ ##
107
+ # Record an exception and check whether we should be retried.
108
+ #
109
+ # @return true if we should be resubmitted; false otherwise
110
+ def handle_exception(exception)
111
+ if @on_exception
112
+ should_retry = @on_exception.call(exception)
113
+ @retries_done += 1 if should_retry
114
+ should_retry
115
+ else
116
+ false
117
+ end
118
+ end
119
+
97
120
  ##
98
121
  # Handle a status update for the task.
99
122
  def handle_status(numerator, denominator)
@@ -116,11 +139,21 @@ class Task
116
139
  ##
117
140
  # Construct a packet to submit this task to a job server.
118
141
  #
119
- # @param background ??
120
142
  # @return String representation of packet
121
- def get_submit_packet(background=false)
122
- mode = 'submit_job' +
123
- (background ? '_bg' : @high_priority ? '_high' : '')
143
+ def get_submit_packet()
144
+ mode = 'submit_job'
145
+ if(@priority)
146
+ if(@priority == :high)
147
+ mode += "_high"
148
+ elsif(@priority == :low)
149
+ mode += "_low"
150
+ end
151
+ end
152
+
153
+ if(@background)
154
+ mode += "_bg"
155
+ end
156
+
124
157
  Util::pack_request(mode, [func, get_uniq_hash, arg].join("\0"))
125
158
  end
126
159
  end
@@ -92,13 +92,16 @@ class TaskSet
92
92
  def handle_job_created(hostport, data)
93
93
  Util.log "Got job_created with handle #{data} from #{hostport}"
94
94
  if not @task_waiting_for_handle
95
- raise ProtocolError, "Got unexpected job_created notification " +
96
- "with handle #{data} from #{hostport}"
95
+ raise ProtocolError, "Got unexpected job_created notification " + "with handle #{data} from #{hostport}"
97
96
  end
98
97
  js_handle = Util.handle_to_str(hostport, data)
99
98
  task = @task_waiting_for_handle
100
99
  @task_waiting_for_handle = nil
101
- (@tasks_in_progress[js_handle] ||= []) << task
100
+ if(task.background)
101
+ @finished_tasks << task
102
+ else
103
+ (@tasks_in_progress[js_handle] ||= []) << task
104
+ end
102
105
  nil
103
106
  end
104
107
  private :handle_job_created
@@ -124,9 +127,32 @@ class TaskSet
124
127
  end
125
128
  nil
126
129
  end
127
-
128
130
  private :handle_work_complete
129
131
 
132
+ ##
133
+ # Handle a 'work_exception' response from a job server.
134
+ #
135
+ # @param hostport "host:port" of job server
136
+ # @param data data returned in packet from server
137
+ def handle_work_exception(hostport, data)
138
+ handle, exception = data.split("\0", 3)
139
+ Util.log "Got work_exception with handle #{handle} from #{hostport}: '#{exception}'"
140
+ js_handle = Util.handle_to_str(hostport, handle)
141
+ tasks = @tasks_in_progress.delete(js_handle)
142
+ if not tasks
143
+ raise ProtocolError, "Got unexpected work_status with handle " +
144
+ "#{handle} from #{hostport} (no task by that name)"
145
+ end
146
+ tasks.each do |t|
147
+ if t.handle_exception(exception)
148
+ add_task_internal(t, false)
149
+ else
150
+ @finished_tasks << t
151
+ end
152
+ end
153
+ end
154
+ private :handle_work_exception
155
+
130
156
  ##
131
157
  # Handle a 'work_fail' response from a job server.
132
158
  #
@@ -189,6 +215,8 @@ class TaskSet
189
215
  handle_work_fail(hostport, data)
190
216
  when :work_status
191
217
  handle_work_status(hostport, data)
218
+ when :work_exception
219
+ handle_work_exception(hostport, data)
192
220
  else
193
221
  Util.log "Got #{type.to_s} from #{hostport}"
194
222
  end
@@ -229,7 +257,7 @@ class TaskSet
229
257
  @sockets.values.each {|s| @client.return_socket(s) }
230
258
  @sockets = {}
231
259
  @finished_tasks.each do |t|
232
- if not t.successful
260
+ if ( (t.background.nil? || t.background == false) && !t.successful)
233
261
  Util.log "Taskset failed"
234
262
  return false
235
263
  end
@@ -33,10 +33,10 @@ class FakeJobServer
33
33
  @tester.assert_true(sock.closed?)
34
34
  end
35
35
 
36
- def expect_request(sock, exp_type, exp_data='')
37
- head = sock.recv(12)
36
+ def expect_request(sock, exp_type, exp_data='', size=12)
37
+ head = sock.recv(size)
38
38
  magic, type, len = head.unpack('a4NN')
39
- @tester.assert_equal("\0REQ", magic)
39
+ @tester.assert("\0REQ" == magic || "\000REQ" == magic)
40
40
  @tester.assert_equal(Gearman::Util::NUMS[exp_type.to_sym], type)
41
41
  data = len > 0 ? sock.recv(len) : ''
42
42
  @tester.assert_equal(exp_data, data)
@@ -52,8 +52,7 @@ class FakeJobServer
52
52
  end
53
53
 
54
54
  def send_response(sock, type, data='', bogus_size=nil)
55
- type_num = Gearman::Util::NUMS[type.to_sym]
56
- raise RuntimeError, "Invalid type #{type}" if not type_num
55
+ type_num = Gearman::Util::NUMS[type.to_sym] || 0
57
56
  response = "\0RES" + [type_num, (bogus_size or data.size)].pack('NN') + data
58
57
  sock.write(response)
59
58
  end
data/lib/gearman/util.rb CHANGED
@@ -24,8 +24,11 @@ class Util
24
24
 
25
25
  6 => :noop, # J->W: --
26
26
  7 => :submit_job, # C->J: FUNC[0]UNIQ[0]ARGS
27
- 21 => :submit_job_high, # C->J: FUNC[0]UNIQ[0]ARGS
28
- 18 => :submit_job_bg, # C->J: FUNC[0]UNIQ[0]ARGS
27
+ 33 => :submit_job_low, # C->J: FUNC[0]UNIQ[0]ARGS
28
+ 34 => :submit_job_low_bg, # C->J: FUNC[0]UNIQ[0]ARGS
29
+ 21 => :submit_job_high, # C->J: FUNC[0]UNIQ[0]ARGS
30
+ 32 => :submit_job_high_bg, # C->J: FUNC[0]UNIQ[0]ARGS
31
+ 18 => :submit_job_bg, # C->J: FUNC[0]UNIQ[0]ARGS
29
32
 
30
33
  8 => :job_created, # J->C: HANDLE
31
34
  9 => :grab_job, # W->J: --
@@ -36,9 +39,14 @@ class Util
36
39
  13 => :work_complete, # W->J/C: HANDLE[0]RES
37
40
  14 => :work_fail, # W->J/C: HANDLE
38
41
 
42
+ 25 => :work_exception, # W->J: HANDLE[0]ARG
43
+ 26 => :option_req, # C->J: TEXT
44
+ 27 => :option_res, # J->C: TEXT
45
+
39
46
  15 => :get_status, # C->J: HANDLE
40
47
  20 => :status_res, # C->J: HANDLE[0]KNOWN[0]RUNNING[0]NUM[0]DENOM
41
48
 
49
+ 25 => :work_exception, # W->J/C: HANDLE[0]ARG
42
50
  16 => :echo_req, # ?->J: TEXT
43
51
  17 => :echo_res, # J->?: TEXT
44
52
 
@@ -198,15 +206,15 @@ class Util
198
206
  raise ServerDownException.new(ex.message)
199
207
  end
200
208
  end
201
-
209
+
202
210
  def Util.ability_name_with_prefix(prefix,name)
203
211
  "#{prefix}\t#{name}"
204
212
  end
205
-
213
+
206
214
  class << self
207
215
  alias :ability_name_for_perl :ability_name_with_prefix
208
216
  end
209
-
217
+
210
218
  end
211
219
 
212
220
  end
@@ -259,17 +259,25 @@ class Worker
259
259
  return false
260
260
  end
261
261
 
262
- ret = ability.run(data, Job.new(sock, handle))
262
+ exception = nil
263
+ begin
264
+ ret = ability.run(data, Job.new(sock, handle))
265
+ rescue Exception => e
266
+ exception = e
267
+ end
268
+
263
269
 
264
- cmd = nil
265
- if ret
270
+ cmd = if ret && exception.nil?
266
271
  ret = ret.to_s
267
272
  Util.log "Sending work_complete for #{handle} with #{ret.size} byte(s) " +
268
273
  "to #{hostport}"
269
- cmd = Util.pack_request(:work_complete, "#{handle}\0#{ret}")
270
- else
274
+ Util.pack_request(:work_complete, "#{handle}\0#{ret}")
275
+ elsif exception.nil?
271
276
  Util.log "Sending work_fail for #{handle} to #{hostport}"
272
- cmd = Util.pack_request(:work_fail, handle)
277
+ Util.pack_request(:work_fail, handle)
278
+ elsif exception
279
+ Util.log "Sending work_exception for #{handle} to #{hostport}"
280
+ Util.pack_request(:work_exception, "#{handle}\0#{exception.message}")
273
281
  end
274
282
 
275
283
  Util.send_request(sock, cmd)
data/test/client_test.rb CHANGED
@@ -108,4 +108,38 @@ class TestClient < Test::Unit::TestCase
108
108
  end
109
109
  assert(should_be_true)
110
110
  end
111
+
112
+ def test_option_request_exceptions
113
+ this_server = FakeJobServer.new(self)
114
+ Thread.new do
115
+ server_socket = this_server.expect_connection
116
+ this_server.expect_request(server_socket, "option_req", "exceptions")
117
+ this_server.send_response(server_socket, :job_created, 'a')
118
+ end
119
+ client = Gearman::Client.new
120
+ hostport = "localhost:#{this_server.port}"
121
+ client.job_servers = [hostport]
122
+ client.option_request("exceptions")
123
+ end
124
+
125
+ def test_option_request_bad
126
+ this_server = FakeJobServer.new(self)
127
+ Thread.new do
128
+ server_socket = this_server.expect_connection
129
+ this_server.expect_request(server_socket, "option_req", "cccceptionsccc")
130
+ this_server.send_response(server_socket, :exception, 'a')
131
+ end
132
+
133
+ client = Gearman::Client.new
134
+ hostport = "localhost:#{this_server.port}"
135
+ client.job_servers = [hostport]
136
+ begin
137
+ client.option_request("cccceptionsccc")
138
+ assert(false)
139
+ rescue Gearman::ProtocolError
140
+ assert(true)
141
+ end
142
+ end
143
+
144
+
111
145
  end
@@ -56,6 +56,127 @@ class TestClient < Test::Unit::TestCase
56
56
  assert_equal(15, res2)
57
57
  end
58
58
 
59
+ ##
60
+ # Tests that the high priority option can be set in a job request
61
+ def test_client_submit_priority_high
62
+ server = FakeJobServer.new(self)
63
+ client, task1, task2, taskset, sock, res1, res2 = nil
64
+
65
+ s = TestScript.new
66
+ c = TestScript.new
67
+
68
+ server_thread = Thread.new { s.loop_forever }.run
69
+ client_thread = Thread.new { c.loop_forever }.run
70
+
71
+ c.exec { client = Gearman::Client.new("localhost:#{server.port}") }
72
+
73
+ c.exec { task1 = Gearman::Task.new('add', '5 2', { :priority => :high }) }
74
+ c.exec { task1.on_complete {|d| res1 = d.to_i } }
75
+ c.exec { taskset = Gearman::TaskSet.new(client) }
76
+ c.exec { taskset.add_task(task1) }
77
+ s.exec { sock = server.expect_connection }
78
+ s.wait
79
+
80
+ s.exec { server.expect_request(sock, :submit_job_high, "add\000\0005 2") }
81
+ end
82
+
83
+ ##
84
+ # Tests that the low priority option can be set in a job request
85
+ def test_client_submit_priority_low
86
+ server = FakeJobServer.new(self)
87
+ client, task1, task2, taskset, sock, res1, res2 = nil
88
+
89
+ s = TestScript.new
90
+ c = TestScript.new
91
+
92
+ server_thread = Thread.new { s.loop_forever }.run
93
+ client_thread = Thread.new { c.loop_forever }.run
94
+
95
+ c.exec { client = Gearman::Client.new("localhost:#{server.port}") }
96
+
97
+ c.exec { task1 = Gearman::Task.new('add', '5 2', { :priority => :low }) }
98
+ c.exec { task1.on_complete {|d| res1 = d.to_i } }
99
+ c.exec { taskset = Gearman::TaskSet.new(client) }
100
+ c.exec { taskset.add_task(task1) }
101
+ s.exec { sock = server.expect_connection }
102
+ s.wait
103
+
104
+ s.exec { server.expect_request(sock, :submit_job_low, "add\000\0005 2") }
105
+ end
106
+
107
+
108
+ ##
109
+ # Check that the client sends a correct background job request
110
+ def test_client_submit_background
111
+ server = FakeJobServer.new(self)
112
+ client, task1, task2, taskset, sock, res1, res2 = nil
113
+
114
+ s = TestScript.new
115
+ c = TestScript.new
116
+
117
+ server_thread = Thread.new { s.loop_forever }.run
118
+ client_thread = Thread.new { c.loop_forever }.run
119
+
120
+ c.exec { client = Gearman::Client.new("localhost:#{server.port}") }
121
+
122
+ c.exec { task1 = Gearman::Task.new('add', '5 2', { :background => :true }) }
123
+ c.exec { task1.on_complete {|d| res1 = d.to_i } }
124
+ c.exec { taskset = Gearman::TaskSet.new(client) }
125
+ c.exec { taskset.add_task(task1) }
126
+ s.exec { sock = server.expect_connection }
127
+ s.wait
128
+
129
+ s.exec { server.expect_request(sock, :submit_job_bg, "add\000\0005 2") }
130
+ end
131
+
132
+ ##
133
+ # Check that the client sends a correct background job with high priority request
134
+ def test_client_submit_background
135
+ server = FakeJobServer.new(self)
136
+ client, task1, task2, taskset, sock, res1, res2 = nil
137
+
138
+ s = TestScript.new
139
+ c = TestScript.new
140
+
141
+ server_thread = Thread.new { s.loop_forever }.run
142
+ client_thread = Thread.new { c.loop_forever }.run
143
+
144
+ c.exec { client = Gearman::Client.new("localhost:#{server.port}") }
145
+
146
+ c.exec { task1 = Gearman::Task.new('add', '5 2', { :background => :true, :priority => :high }) }
147
+ c.exec { task1.on_complete {|d| res1 = d.to_i } }
148
+ c.exec { taskset = Gearman::TaskSet.new(client) }
149
+ c.exec { taskset.add_task(task1) }
150
+ s.exec { sock = server.expect_connection }
151
+ s.wait
152
+
153
+ s.exec { server.expect_request(sock, :submit_job_high_bg, "add\000\0005 2") }
154
+ end
155
+
156
+ ##
157
+ # Check that the client sends a correct background job with low priority request
158
+ def test_client_submit_background
159
+ server = FakeJobServer.new(self)
160
+ client, task1, task2, taskset, sock, res1, res2 = nil
161
+
162
+ s = TestScript.new
163
+ c = TestScript.new
164
+
165
+ server_thread = Thread.new { s.loop_forever }.run
166
+ client_thread = Thread.new { c.loop_forever }.run
167
+
168
+ c.exec { client = Gearman::Client.new("localhost:#{server.port}") }
169
+
170
+ c.exec { task1 = Gearman::Task.new('add', '5 2', { :background => :true, :priority => :low }) }
171
+ c.exec { task1.on_complete {|d| res1 = d.to_i } }
172
+ c.exec { taskset = Gearman::TaskSet.new(client) }
173
+ c.exec { taskset.add_task(task1) }
174
+ s.exec { sock = server.expect_connection }
175
+ s.wait
176
+
177
+ s.exec { server.expect_request(sock, :submit_job_low_bg, "add\000\0005 2") }
178
+ end
179
+
59
180
  ##
60
181
  # Test Client#do_task.
61
182
  def test_do_task
@@ -185,6 +306,39 @@ class TestClient < Test::Unit::TestCase
185
306
  assert_equal(false, setres)
186
307
  end
187
308
 
309
+ def test_exception
310
+ server = FakeJobServer.new(self)
311
+ client, task, taskset, sock = nil
312
+ res,exception, setres = nil
313
+
314
+ s = TestScript.new
315
+ c = TestScript.new
316
+
317
+ server_thread = Thread.new { s.loop_forever }.run
318
+ client_thread = Thread.new { c.loop_forever }.run
319
+
320
+ c.exec { client = Gearman::Client.new("localhost:#{server.port}") }
321
+
322
+ c.exec { task = Gearman::Task.new('func2', 'b') }
323
+ c.exec { task.on_complete {|d| res = d } }
324
+ c.exec { task.on_exception {|message| exception=message; false } }
325
+ c.exec { taskset = Gearman::TaskSet.new(client) }
326
+ c.exec { taskset.add_task(task) }
327
+ s.exec { sock = server.expect_connection }
328
+
329
+ s.exec { server.expect_request(sock, :submit_job, "func2\000\000b") }
330
+ s.exec { server.send_response(sock, :job_created, 'b') }
331
+
332
+ s.exec { server.send_response(sock, :work_exception, "b\0exceptionmsg") }
333
+
334
+ c.exec { setres = taskset.wait }
335
+ c.wait
336
+ s.wait
337
+
338
+ assert_equal(nil, res)
339
+ assert_equal('exceptionmsg',exception)
340
+ end
341
+
188
342
  ##
189
343
  # Test that user-supplied uniq values are handled correctly.
190
344
  def test_uniq
@@ -210,4 +210,48 @@ class TestWorker < Test::Unit::TestCase
210
210
  s.wait
211
211
  w.wait
212
212
  end
213
+
214
+ def test_exception
215
+ @server = FakeJobServer.new(self)
216
+ worker = nil
217
+ sock = nil
218
+
219
+ s = TestScript.new
220
+ w = TestScript.new
221
+
222
+ server_thread = Thread.new { s.loop_forever }.run
223
+ worker_thread = Thread.new { w.loop_forever }.run
224
+
225
+ # Create a worker and wait for it to connect to us.
226
+ w.exec {
227
+ worker = Gearman::Worker.new(
228
+ "localhost:#{@server.port}", { :client_id => 'test' })
229
+ }
230
+ s.exec { sock = @server.expect_connection }
231
+ s.wait
232
+
233
+ # After it connects, it should send its ID, and it should tell us its
234
+ # abilities when we report them.
235
+ s.exec { @server.expect_request(sock, :set_client_id, 'test') }
236
+ w.exec { worker.add_ability('echo') {|d,j| raise Exception.new("fooexception") } }
237
+ s.exec { @server.expect_request(sock, :can_do, 'echo') }
238
+
239
+ # It should try to grab a job when we tell it to work.
240
+ w.exec { worker.work }
241
+ s.exec { @server.expect_request(sock, :grab_job) }
242
+
243
+ # If we tell it there aren't any jobs, it should go to sleep.
244
+ s.exec { @server.send_response(sock, :no_job) }
245
+ s.exec { @server.expect_request(sock, :pre_sleep) }
246
+
247
+ # When we send it a noop, it should wake up and ask for a job again.
248
+ s.exec { @server.send_response(sock, :noop) }
249
+ s.exec { @server.expect_request(sock, :grab_job) }
250
+
251
+ # When we give it a job, it should raise an excpetion and notify the server
252
+ s.exec { @server.send_response(sock, :job_assign, "a\0echo\0foo") }
253
+ s.exec { @server.expect_request(sock, :work_exception, "a\0fooexception") }
254
+
255
+ s.wait
256
+ end
213
257
  end
data/test/util_test.rb ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift('../lib')
4
+ require 'gearman'
5
+ require 'gearman/testlib'
6
+ require 'test/unit'
7
+
8
+ class TestUtil < Test::Unit::TestCase
9
+
10
+ def test_ability_prefix_name_builder
11
+ assert_equal(Gearman::Util.ability_name_with_prefix("test","a"),"test\ta")
12
+ end
13
+
14
+ def test_ability_name_for_perl
15
+ assert_equal(Gearman::Util.ability_name_for_perl("test","a"),"test\ta")
16
+ end
17
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xing-gearman-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Erat
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-06-25 00:00:00 -07:00
13
+ date: 2009-07-16 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -25,9 +25,11 @@ extra_rdoc_files:
25
25
  - README
26
26
  files:
27
27
  - .gitignore
28
+ - HOWTO
28
29
  - LICENSE
29
30
  - README
30
31
  - Rakefile
32
+ - TODO
31
33
  - VERSION.yml
32
34
  - examples/Net/Gearman/Client.php
33
35
  - examples/Net/Gearman/Connection.php
@@ -40,13 +42,21 @@ files:
40
42
  - examples/Net/Gearman/Set.php
41
43
  - examples/Net/Gearman/Task.php
42
44
  - examples/Net/Gearman/Worker.php
45
+ - examples/calculus_client.rb
46
+ - examples/calculus_worker.rb
43
47
  - examples/client.php
44
48
  - examples/client.rb
49
+ - examples/client_background.rb
50
+ - examples/client_exception.rb
51
+ - examples/client_prefix.rb
52
+ - examples/gearman_environment.sh
45
53
  - examples/scale_image.rb
46
54
  - examples/scale_image_worker.rb
47
55
  - examples/server.rb
48
56
  - examples/worker.php
49
57
  - examples/worker.rb
58
+ - examples/worker_exception.rb
59
+ - examples/worker_prefix.rb
50
60
  - gearman-ruby.gemspec
51
61
  - lib/gearman.rb
52
62
  - lib/gearman/client.rb
@@ -59,8 +69,9 @@ files:
59
69
  - test/client_test.rb
60
70
  - test/mock_client_test.rb
61
71
  - test/mock_worker_test.rb
72
+ - test/util_test.rb
62
73
  - test/worker_test.rb
63
- has_rdoc: true
74
+ has_rdoc: false
64
75
  homepage: http://github.com/xing/gearman-ruby
65
76
  post_install_message:
66
77
  rdoc_options:
@@ -84,15 +95,23 @@ requirements: []
84
95
  rubyforge_project:
85
96
  rubygems_version: 1.2.0
86
97
  signing_key:
87
- specification_version: 2
98
+ specification_version: 3
88
99
  summary: Library for the Gearman distributed job system
89
100
  test_files:
90
101
  - test/client_test.rb
91
102
  - test/mock_client_test.rb
92
103
  - test/mock_worker_test.rb
104
+ - test/util_test.rb
93
105
  - test/worker_test.rb
106
+ - examples/calculus_client.rb
107
+ - examples/calculus_worker.rb
94
108
  - examples/client.rb
109
+ - examples/client_background.rb
110
+ - examples/client_exception.rb
111
+ - examples/client_prefix.rb
95
112
  - examples/scale_image.rb
96
113
  - examples/scale_image_worker.rb
97
114
  - examples/server.rb
98
115
  - examples/worker.rb
116
+ - examples/worker_exception.rb
117
+ - examples/worker_prefix.rb