evented-gearman-ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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