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.
Files changed (45) hide show
  1. data/.gitignore +1 -0
  2. data/HOWTO +146 -0
  3. data/LICENSE +20 -0
  4. data/README +4 -0
  5. data/Rakefile +41 -0
  6. data/TODO +8 -0
  7. data/VERSION.yml +4 -0
  8. data/evented-gearman-ruby.gemspec +110 -0
  9. data/examples/calculus_client.rb +41 -0
  10. data/examples/calculus_worker.rb +42 -0
  11. data/examples/client.rb +19 -0
  12. data/examples/client_background.rb +14 -0
  13. data/examples/client_data.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_exception.rb +14 -0
  25. data/examples/worker_prefix.rb +25 -0
  26. data/lib/gearman.rb +29 -0
  27. data/lib/gearman/client.rb +80 -0
  28. data/lib/gearman/evented/client.rb +99 -0
  29. data/lib/gearman/evented/reactor.rb +86 -0
  30. data/lib/gearman/evented/worker.rb +118 -0
  31. data/lib/gearman/job.rb +38 -0
  32. data/lib/gearman/protocol.rb +110 -0
  33. data/lib/gearman/server.rb +94 -0
  34. data/lib/gearman/task.rb +99 -0
  35. data/lib/gearman/taskset.rb +11 -0
  36. data/lib/gearman/util.rb +52 -0
  37. data/lib/gearman/worker.rb +39 -0
  38. data/test/basic_integration_test.rb +121 -0
  39. data/test/crash_test.rb +69 -0
  40. data/test/job_test.rb +30 -0
  41. data/test/protocol_test.rb +132 -0
  42. data/test/test_helper.rb +31 -0
  43. data/test/util_test.rb +12 -0
  44. data/test/worker_test.rb +45 -0
  45. metadata +149 -0
@@ -0,0 +1,99 @@
1
+ module Gearman
2
+ class Task
3
+
4
+ attr_reader :name, :payload, :retries_done
5
+ attr_accessor :retries, :priority, :background, :poll_status_interval
6
+
7
+ def initialize(name, payload = nil, opts = {})
8
+ @name = name.to_s
9
+ @payload = payload || ''
10
+ @priority = opts.delete(:priority).to_sym rescue nil
11
+ @background = opts.delete(:background) ? true : false
12
+
13
+ @retries_done = 0
14
+ @retries = opts.delete(:retries) || 0
15
+
16
+ @poll_status_interval = opts.delete(:poll_status_interval)
17
+ @uniq = opts.has_key?(:uuid) ? opts.delete(:uuid) : `uuidgen`.strip
18
+ end
19
+
20
+ ##
21
+ # Set a block of code to be executed when this task completes
22
+ # successfully. The returned data will be passed to the block.
23
+ def on_complete(&f)
24
+ @on_complete = f
25
+ end
26
+
27
+ ##
28
+ # Set a block of code to be executed when this task fails.
29
+ def on_fail(&f)
30
+ @on_fail = f
31
+ end
32
+
33
+ ##
34
+ # Set a block of code to be executed when this task is retried after
35
+ # failing. The number of retries that have been attempted (including the
36
+ # current one) will be passed to the block.
37
+ def on_retry(&f)
38
+ @on_retry = f
39
+ end
40
+
41
+ ##
42
+ # Set a block of code to be executed when a remote exception is sent by a worker.
43
+ # The block will receive the message of the exception passed from the worker.
44
+ # The user can return true for retrying or false to mark it as finished
45
+ #
46
+ # NOTE: this is actually deprecated, cf. https://bugs.launchpad.net/gearmand/+bug/405732
47
+ #
48
+ def on_exception(&f)
49
+ @on_exception = f
50
+ end
51
+
52
+ ##
53
+ # Set a block of code to be executed when we receive a status update for
54
+ # this task. The block will receive two arguments, a numerator and
55
+ # denominator describing the task's status.
56
+ def on_status(&f)
57
+ @on_status = f
58
+ end
59
+
60
+ ##
61
+ # Set a block of code to be executed when we receive a warning from a worker.
62
+ # It is recommended for workers to send work_warning, followed by work_fail if
63
+ # an exception occurs on their side. Don't expect this behavior from workers NOT
64
+ # using this very library ATM, though. (cf. https://bugs.launchpad.net/gearmand/+bug/405732)
65
+ def on_warning(&f)
66
+ @on_warning = f
67
+ end
68
+
69
+ ##
70
+ # Set a block of code to be executed when we receive a (partial) data packet for this task.
71
+ # The data received will be passed as an argument to the block.
72
+ def on_data(&f)
73
+ @on_data = f
74
+ end
75
+
76
+ ##
77
+ # Record a failure and check whether we should be retried.
78
+ #
79
+ # @return true if we should be resubmitted; false otherwise
80
+ def should_retry?
81
+ return false if @retries_done >= @retries
82
+ @retries_done += 1
83
+ true
84
+ end
85
+
86
+ def background?
87
+ background
88
+ end
89
+
90
+ def dispatch(event, *args)
91
+ callback = instance_variable_get("@#{event}".to_sym)
92
+ callback.call(*args) if callback
93
+ end
94
+
95
+ def hash
96
+ @uniq
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,11 @@
1
+ module Gearman
2
+ class Taskset < ::Array
3
+
4
+ def self.create(task_or_taskset)
5
+ [*task_or_taskset]
6
+ end
7
+
8
+ alias :add :<<
9
+ alias :add_task :add
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ require 'time'
5
+
6
+ module Gearman
7
+
8
+ class ServerDownException < Exception; end
9
+
10
+ # = Util
11
+ #
12
+ # == Description
13
+ # Static helper methods and data used by other classes.
14
+ class Util
15
+
16
+ @@debug = false
17
+
18
+ ##
19
+ # Enable or disable debugging output (off by default).
20
+ #
21
+ # @param v print debugging output
22
+ def Util.debug=(v)
23
+ @@debug = v
24
+ end
25
+
26
+ ##
27
+ # Log a message if debugging is enabled.
28
+ #
29
+ # @param str message to log
30
+ def Util.log(str, force=false)
31
+ puts "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} #{str}" if force or @@debug
32
+ end
33
+
34
+ ##
35
+ # Log a message no matter what.
36
+ #
37
+ # @param str message to log
38
+ def Util.err(str)
39
+ log(str, true)
40
+ end
41
+
42
+ def Util.ability_name_with_prefix(prefix,name)
43
+ "#{prefix}\t#{name}"
44
+ end
45
+
46
+ class << self
47
+ alias :ability_name_for_perl :ability_name_with_prefix
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,39 @@
1
+ module Gearman
2
+ class Worker
3
+
4
+ attr_reader :abilities
5
+
6
+ def initialize(job_servers, opts = {})
7
+ @reactors = []
8
+ @abilities = {}
9
+
10
+ @job_servers = Array[*job_servers]
11
+
12
+ @opts = opts
13
+ end
14
+
15
+ def add_ability(name, timeout = nil, &f)
16
+ remove_ability(name) if @abilities.has_key?(name)
17
+ @abilities[name] = { :callback => f, :timeout => timeout }
18
+ end
19
+
20
+ def remove_ability(name)
21
+ @abilities.delete(name)
22
+ end
23
+
24
+ def has_ability?(name)
25
+ @abilities.has_key?(name)
26
+ end
27
+
28
+ def work
29
+ EM.run do
30
+ @job_servers.each do |hostport|
31
+ host, port = hostport.split(":")
32
+ opts = { :abilities => @abilities }.merge(@opts)
33
+ Gearman::Evented::WorkerReactor.connect(host, port, opts)
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,121 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class BasicIntegrationTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ start_gearmand
7
+ @client = Gearman::Client.new("localhost:4730")
8
+ @worker = Gearman::Worker.new("localhost:4730")
9
+ Thread.new { EM.run } unless EM.reactor_running?
10
+ end
11
+
12
+ def teardown
13
+ stop_gearmand
14
+ end
15
+
16
+ def test_ping_job
17
+ response = nil
18
+
19
+ @worker.add_ability("pingpong") {|data, job| "pong" }
20
+ @worker.work
21
+
22
+ task = Gearman::Task.new("pingpong", "ping")
23
+
24
+ task.on_complete {|res| response = res }
25
+ @client.run task
26
+
27
+ assert_equal "pong", response
28
+ end
29
+
30
+ def test_exception_in_worker
31
+ warning_given = nil
32
+ failed = false
33
+
34
+ task = Gearman::Task.new("crash", "doesntmatter")
35
+
36
+ @worker.add_ability("crash") {|data, job| raise Exception.new("BOOM!") }
37
+ @worker.work
38
+
39
+ task.on_warning {|warning| warning_given = warning }
40
+ task.on_fail { failed = true}
41
+ @client.run task
42
+
43
+ assert_not_nil warning_given
44
+ assert_equal true, failed
45
+ assert_equal 0, task.retries_done
46
+ end
47
+
48
+ def test_should_be_able_to_retry_on_worker_exception
49
+ retry_callback_called = false
50
+ fail_count = 0
51
+
52
+ # Gearman::Util.debug = true
53
+ task = Gearman::Task.new("crash", "doesntmatter", :retries => 1)
54
+
55
+ @worker.add_ability("crash") {|data, job| raise Exception.new("BOOM!") }
56
+ @worker.work
57
+
58
+ task.on_retry {|i| retry_callback_called = true }
59
+ task.on_fail { fail_count += 1 }
60
+ @client.run task
61
+
62
+ assert_equal true, retry_callback_called
63
+ assert_equal 1, task.retries_done
64
+ assert_equal 1, fail_count
65
+ end
66
+
67
+ def test_chunked_response
68
+ chunks_received = 0
69
+
70
+ @worker.add_ability("chunked") do |data, job|
71
+ 5.times {|i| job.send_partial("chunk #{i}") }
72
+ end
73
+ @worker.work
74
+
75
+ task = Gearman::Task.new("chunked")
76
+ task.on_data do |data|
77
+ assert_match /^chunk \d/, data
78
+ chunks_received += 1
79
+ end
80
+ @client.run task
81
+
82
+ assert_equal 5, chunks_received
83
+ end
84
+
85
+ def test_background
86
+ status_received = false
87
+
88
+ @worker.add_ability("fireandforget") {|data, job| "this goes to /dev/null" }
89
+ @worker.work
90
+
91
+ task = Gearman::Task.new('fireandforget', 'background', :background => true, :poll_status_interval => 0.1)
92
+ task.on_complete {|d| flunk "on_complete should never be called for a background job!" }
93
+ task.on_status {|d| status_received = true }
94
+ @client.run task
95
+
96
+ assert_equal true, status_received
97
+ end
98
+
99
+ def test_non_blocking_run
100
+ @worker.add_ability("foo") {|data, job| "foo: #{data}" }
101
+ @worker.work
102
+
103
+ task1_complete = false
104
+ task2_complete = false
105
+
106
+ task1 = Gearman::Task.new("foo", 1)
107
+ task1.on_complete {|d| task1_complete = true}
108
+ @client.run task1, nil, true
109
+
110
+ assert_equal false, task1_complete
111
+
112
+ task2 = Gearman::Task.new("foo", 2)
113
+ task2.on_complete {|d| task2_complete = true}
114
+ @client.run task2, nil, true
115
+
116
+ Thread.new { sleep 0.1 }.join
117
+
118
+ assert_equal true, task1_complete
119
+ assert_equal true, task2_complete
120
+ end
121
+ end
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ # Gearman::Util.debug = true
4
+ class CrashTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ teardown_gearmands
8
+ Thread.new { EM.run } unless EM.reactor_running?
9
+ end
10
+
11
+ def teardown
12
+ teardown_gearmands
13
+ end
14
+
15
+ def test_worker_should_reconnect_if_gearmand_goes_away
16
+ start_gearmand
17
+ worker = Gearman::Worker.new("localhost:4730", :reconnect_sec => 1)
18
+ worker.add_ability("foo") {|data, job| "noop!" }
19
+
20
+ response = nil
21
+
22
+ worker.work
23
+ stop_gearmand
24
+ start_gearmand
25
+
26
+ task = Gearman::Task.new("foo", "ping")
27
+ task.on_complete {|res| response = res }
28
+ Gearman::Client.new("localhost:4730").run task
29
+
30
+ assert_equal "noop!", response
31
+ stop_gearmand
32
+ end
33
+
34
+ def test_client_and_worker_should_use_failover_gearmand_if_primary_is_not_available
35
+ start_gearmand 4731
36
+
37
+ worker = Gearman::Worker.new(["localhost:4730", "localhost:4731"], :reconnect_sec => 1)
38
+ worker.add_ability("foo") {|data, job| "noop!" }
39
+
40
+ response = nil
41
+ worker.work
42
+ task = Gearman::Task.new("foo", "ping")
43
+ task.on_complete {|res| response = res }
44
+ Gearman::Client.new(["localhost:4730", "localhost:4731"]).run task
45
+
46
+ assert_equal "noop!", response
47
+ stop_gearmand 4731
48
+ end
49
+
50
+ def test_client_and_worker_should_switch_to_failover_gearmand_if_primary_goes_down
51
+ start_gearmand 4730
52
+ start_gearmand 4731
53
+
54
+ worker = Gearman::Worker.new(["localhost:4730", "localhost:4731"], :reconnect_sec => 1)
55
+ worker.add_ability("foo") {|data, job| "noop!" }
56
+
57
+ response = nil
58
+ worker.work
59
+
60
+ stop_gearmand 4730
61
+
62
+ task = Gearman::Task.new("foo", "ping")
63
+ task.on_complete {|res| response = res }
64
+ Gearman::Client.new(["localhost:4730", "localhost:4731"]).run task
65
+
66
+ assert_equal "noop!", response
67
+ stop_gearmand 4731
68
+ end
69
+ end
data/test/job_test.rb ADDED
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class JobTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @handle = "H:foo:1"
7
+ @mock_client = mock()
8
+ @job = Gearman::Job.new(@mock_client, @handle)
9
+ end
10
+
11
+ def test_supports_report_status
12
+ @mock_client.expects(:send).with(:work_status, [@handle, 1, 5].join("\0"))
13
+ @job.report_status(1, 5)
14
+ end
15
+
16
+ def test_supports_send_partial
17
+ @mock_client.expects(:send).with(:work_data, [@handle, "bar"].join("\0"))
18
+ @job.send_partial("bar")
19
+ end
20
+
21
+ def test_supports_send_data
22
+ @mock_client.expects(:send).with(:work_data, [@handle, "bar"].join("\0"))
23
+ @job.send_data("bar")
24
+ end
25
+
26
+ def test_supports_report_warning
27
+ @mock_client.expects(:send).with(:work_warning, [@handle, "danger"].join("\0"))
28
+ @job.report_warning("danger")
29
+ end
30
+ end
@@ -0,0 +1,132 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ProtocolTest < Test::Unit::TestCase
4
+
5
+ def test_encode_request
6
+ payload = ["foo", "123", "bar"].join("\0")
7
+ expected = "\0REQ" + [Gearman::Protocol::COMMANDS_NUMERIC[:submit_job], payload.size].pack("NN") + payload
8
+ assert_equal expected, Gearman::Protocol.encode_request(:submit_job, payload)
9
+ end
10
+
11
+ def test_decode_response
12
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_data], 3].pack("NN") + handle + "\0foo"
13
+ packets = Gearman::Protocol.decode_response(response)
14
+ assert_equal 1, packets.size
15
+ assert_equal [:work_data, "H:wrk.acme:1", "foo"], packets.first
16
+ end
17
+
18
+ def test_decodes_multiple_response_packets
19
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_data], 3].pack("NN") + handle + "\0foo"
20
+ response << "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_data], 3].pack("NN") + handle + "\0bar"
21
+ response << "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_data], 3].pack("NN") + handle + "\0baz"
22
+
23
+ assert_equal 3, Gearman::Protocol.decode_response(response).size
24
+ end
25
+
26
+ def test_response_packet
27
+ packet = [:work_data, handle, "foo"]
28
+ assert_equal packet, Gearman::Protocol.response_packet(*packet)
29
+ end
30
+
31
+ def test_decodes_work_complete
32
+ data = "esta complet"
33
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_complete], data.size].pack("NN") + [handle, data].join("\0")
34
+ assert_equal [:work_complete, handle, data], Gearman::Protocol.decode_response(response).first
35
+ end
36
+
37
+ def test_decodes_work_exception
38
+ data = "{native perl exception object}"
39
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_exception], data.size].pack("NN") + [handle, data].join("\0")
40
+ assert_equal [:work_exception, handle, data], Gearman::Protocol.decode_response(response).first
41
+ end
42
+
43
+ def test_decodes_work_warning
44
+ data = "I warn you, dude"
45
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_warning], data.size].pack("NN") + [handle, data].join("\0")
46
+ assert_equal [:work_warning, handle, data], Gearman::Protocol.decode_response(response).first
47
+ end
48
+
49
+ def test_decodes_work_data
50
+ data = "foo"
51
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_data], data.size].pack("NN") + [handle, data].join("\0")
52
+ assert_equal [:work_data, handle, data], Gearman::Protocol.decode_response(response).first
53
+ end
54
+
55
+ def test_decodes_error
56
+ data = "error"
57
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:error], data.size].pack("NN") + [handle, data].join("\0")
58
+ assert_equal [:error, handle, data], Gearman::Protocol.decode_response(response).first
59
+ end
60
+
61
+ def test_decodes_job_assign
62
+ function = "foo_function"
63
+ arguments = "arguments"
64
+
65
+ payload = [handle, function, arguments].join("\0")
66
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:job_assign], payload.size].pack("NN") + payload
67
+ assert_equal [:job_assign, handle, function, arguments], Gearman::Protocol.decode_response(response).first
68
+ end
69
+
70
+ def test_decodes_job_assign_uniq
71
+ function = "foo_function"
72
+ arguments = "arguments"
73
+ unique_id = "123-657"
74
+
75
+ payload = [handle, function, unique_id, arguments].join("\0")
76
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:job_assign_uniq], payload.size].pack("NN") + payload
77
+ assert_equal [:job_assign_uniq, handle, function, arguments, unique_id], Gearman::Protocol.decode_response(response).first
78
+ end
79
+
80
+ def test_decodes_work_fail
81
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_fail], 0].pack("NN") + handle
82
+ assert_equal [:work_fail, handle], Gearman::Protocol.decode_response(response).first
83
+ end
84
+
85
+ def test_decodes_job_created
86
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:job_created], 0].pack("NN") + handle
87
+ assert_equal [:job_created, handle], Gearman::Protocol.decode_response(response).first
88
+ end
89
+
90
+ def test_decodes_no_job
91
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:no_job], 0].pack("NN") + handle
92
+ assert_equal [:no_job, handle], Gearman::Protocol.decode_response(response).first
93
+ end
94
+
95
+ def test_decodes_noop
96
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:noop], 0].pack("NN") + handle
97
+ assert_equal [:noop, handle], Gearman::Protocol.decode_response(response).first
98
+ end
99
+
100
+ def test_decodes_work_status
101
+ numerator = "1"
102
+ denominator = "5"
103
+
104
+ payload = [handle, numerator, denominator].join("\0")
105
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:work_status], payload.size].pack("NN") + payload
106
+
107
+ assert_equal [:work_status, handle, numerator, denominator], Gearman::Protocol.decode_response(response).first
108
+ end
109
+
110
+ def test_decodes_status_res
111
+ known = "1"
112
+ running = "0"
113
+ numerator = "1"
114
+ denominator = "4"
115
+
116
+ payload = [handle, known, running, numerator, denominator].join("\0")
117
+ response = "\0RES" + [Gearman::Protocol::COMMANDS_NUMERIC[:status_res], payload.size].pack("NN") + payload
118
+
119
+ assert_equal [:status_res, handle, known, running, numerator, denominator], Gearman::Protocol.decode_response(response).first
120
+ end
121
+
122
+ def test_raises_on_invalid_command
123
+ response = "\0RES" + [6969, 0].pack("NN")
124
+ assert_raises(Gearman::ProtocolError) { Gearman::Protocol.decode_response(response) }
125
+ assert_raises(Gearman::ProtocolError) { Gearman::Protocol.response_packet(*[6969, handle, ''])}
126
+ end
127
+
128
+ private
129
+ def handle
130
+ "H:wrk.acme:1"
131
+ end
132
+ end