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,14 @@
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
+
10
+ task = Gearman::Task.new('sleep', 20, :background => true, :poll_status_interval => 1)
11
+ task.on_complete {|d| puts d } #never called
12
+ task.on_status {|d| puts "Status: #{d}"}
13
+
14
+ client.run(task)
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ #require 'gearman'
3
+ require '../lib/gearman'
4
+ Gearman::Util.debug = true
5
+
6
+ servers = ['localhost:4730']
7
+
8
+ client = Gearman::Client.new(servers)
9
+ taskset = Gearman::Taskset.new
10
+
11
+ task = Gearman::Task.new('chunked_transfer')
12
+ task.on_data {|d| puts d }
13
+ task.on_complete {|d| puts d }
14
+
15
+ taskset << task
16
+ client.run taskset
@@ -0,0 +1,17 @@
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
+
9
+ task = Gearman::Task.new('fail_with_exception', "void")
10
+ task.retries = 2
11
+ task.on_complete {|d| puts d }
12
+ task.on_exception {|ex| puts "This should never be called" }
13
+ task.on_warning {|warning| puts "WARNING: #{warning}" }
14
+ task.on_retry { puts "PRE-RETRY HOOK: retry no. #{task.retries_done}" }
15
+ task.on_fail { puts "TASK FAILED, GIVING UP" }
16
+
17
+ client.run task
@@ -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
+ ability_name_with_prefix = Gearman::Util.ability_name_with_prefix("test","sleep")
9
+
10
+ client = Gearman::Client.new(servers)
11
+
12
+ task = Gearman::Task.new(ability_name_with_prefix, 20)
13
+ task.on_complete {|d| puts d }
14
+
15
+ client.run task
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require "ruby-debug"
3
+ require '../lib/gearman'
4
+
5
+ Gearman::Util.debug = true
6
+
7
+ servers = ['localhost:4730', 'localhost:4731']
8
+ client = Gearman::Client.new(servers)
9
+
10
+ taskset = Gearman::Taskset.new
11
+
12
+ task = Gearman::Task.new('sleep', 2)
13
+ task.on_status {|numerator, denominator| puts "TASK 1: Completed #{numerator} of #{denominator}"}
14
+ task.on_complete {|d| puts "TASK 1: #{d}" }
15
+ taskset << task
16
+
17
+ task = Gearman::Task.new('sleep', 15, :poll_status_interval => 2, :uuid => nil)
18
+ task.on_status {|numerator, denominator| puts "TASK 2: Completed #{numerator} of #{denominator}"}
19
+ task.on_data {|data| puts "TASK 2 DATA: #{data}" }
20
+ task.on_complete {|d| puts "TASK 2: #{d}" }
21
+ taskset << task
22
+
23
+ client.run(taskset)
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require '../lib/gearman'
3
+
4
+ Gearman::Util.debug = true
5
+
6
+ servers = ['localhost:4730', 'localhost:4731']
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('sleep') do |data, job|
12
+ seconds = data
13
+ job.report_warning("this is a warning you can safely ignore")
14
+
15
+ (1..seconds.to_i).each do |i|
16
+ sleep 1
17
+ print i
18
+ # Report our progress to the job server every second.
19
+ job.report_status(i, seconds)
20
+ job.send_partial(".")
21
+ end
22
+
23
+ # Report success.
24
+ "SUCCESS"
25
+ end
26
+ w.work
@@ -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,30 @@
1
+ #!/usr/bin/ruby
2
+
3
+ $: << '../lib'
4
+ require 'gearman'
5
+ require 'optparse'
6
+
7
+ Gearman::Util.debug = true
8
+ servers = 'localhost:7003'
9
+ format = 'PNG'
10
+ width, height = 100, 100
11
+
12
+ opts = OptionParser.new
13
+ opts.banner = "Usage: #{$0} [options] <input> <output>"
14
+ opts.on('-f FORMAT', '--format', 'Scaled image format') { format }
15
+ opts.on('-h HEIGHT', '--height', 'Scaled image height') { height }
16
+ opts.on('-s SERVERS', '--servers',
17
+ 'Servers, comma-separated host:port') { servers }
18
+ opts.on('-w WIDTH', '--width', 'Scaled image width') { width }
19
+ opts.parse!
20
+
21
+ if ARGV.size != 2
22
+ $stderr.puts opts.banner
23
+ exit 1
24
+ end
25
+
26
+ client = Gearman::Client.new(servers.split(','), 'example')
27
+ arg = [width, height, format, File.read(ARGV[0])].join("\0")
28
+ task = Gearman::Task.new('scale_image', arg)
29
+ task.on_complete {|d| File.new(ARGV[1],'w').write(d) }
30
+ client.run task
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/ruby
2
+
3
+ $: << '../lib'
4
+ require 'gearman'
5
+ require 'optparse'
6
+ require 'RMagick'
7
+
8
+ Gearman::Util.debug = true
9
+ servers = 'localhost:7003'
10
+
11
+ opts = OptionParser.new
12
+ opts.banner = "Usage: #{$0} [options]"
13
+ opts.on('-s SERVERS', '--servers',
14
+ 'Job servers, comma-separated host:port') { servers }
15
+ opts.parse!
16
+
17
+ worker = Gearman::Worker.new(servers.split(','), 'example')
18
+
19
+ worker.add_ability('scale_image') do |data,job|
20
+ width, height, format, data = data.split("\0", 4)
21
+ width = width.to_f
22
+ height = height.to_f
23
+ image = Magick::Image.from_blob(data)[0]
24
+ orig_ratio = image.columns.to_f / image.rows
25
+ new_ratio = width / height
26
+ w = new_ratio < orig_ratio ? width : orig_ratio / new_ratio * width
27
+ h = new_ratio > orig_ratio ? height : new_ratio / orig_ratio * height
28
+ puts "Got #{image.inspect}; resizing to #{w}x#{h} #{format}"
29
+ image.resize!(w, h)
30
+ image.format = format
31
+ image.to_blob
32
+ end
33
+
34
+ worker.work
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ # require 'gearman'
3
+ # require 'gearman/server'
4
+ require '../lib/gearman'
5
+ require '../lib/gearman/server'
6
+ require 'pp'
7
+
8
+ Gearman::Util.debug = true
9
+ w = Gearman::Server.new('localhost:4730')
10
+
11
+ loop {
12
+ pp "Status: ", w.status
13
+ pp "Workers: ", w.workers
14
+ sleep 5
15
+ }
@@ -0,0 +1,23 @@
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
+ # Add a handler for a "sleep" function that takes a single argument, the
11
+ # number of seconds to sleep before reporting success.
12
+ w.add_ability('sleep') do |data,job|
13
+ seconds = data
14
+ (1..seconds.to_i).each do |i|
15
+ sleep 1
16
+ print i
17
+ # Report our progress to the job server every second.
18
+ job.report_status(i, seconds)
19
+ end
20
+ # Report success.
21
+ true
22
+ end
23
+ w.work
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require '../lib/gearman'
3
+
4
+ Gearman::Util.debug = true
5
+
6
+ servers = ['localhost:4730']
7
+ worker = Gearman::Worker.new(servers)
8
+
9
+ worker.add_ability('chunked_transfer') do |data, job|
10
+ 5.times do |i|
11
+ sleep 1
12
+ job.send_partial("CHUNK #{i}")
13
+ end
14
+ "EOD"
15
+ end
16
+ worker.work
@@ -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("Exception in worker (args: #{data.inspect})")
13
+ end
14
+ 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
+ w.work
data/lib/gearman.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ module Gearman
5
+
6
+ require File.dirname(__FILE__) + '/gearman/evented/reactor'
7
+ require File.dirname(__FILE__) + '/gearman/evented/client'
8
+ require File.dirname(__FILE__) + '/gearman/evented/worker'
9
+ require File.dirname(__FILE__) + '/gearman/client'
10
+ require File.dirname(__FILE__) + '/gearman/task'
11
+ require File.dirname(__FILE__) + '/gearman/taskset'
12
+ require File.dirname(__FILE__) + '/gearman/util'
13
+ require File.dirname(__FILE__) + '/gearman/worker'
14
+ require File.dirname(__FILE__) + '/gearman/job'
15
+
16
+ require File.dirname(__FILE__) + '/gearman/protocol'
17
+
18
+
19
+ class InvalidArgsError < Exception
20
+ end
21
+
22
+ class NetworkError < Exception
23
+ end
24
+
25
+ def log(msg, force = false)
26
+ Util.log(msg, force)
27
+ end
28
+
29
+ end
@@ -0,0 +1,80 @@
1
+ module Gearman
2
+ class Client
3
+ attr_accessor :uniq, :jobs
4
+
5
+ def initialize(job_servers, opts = {})
6
+ @reactors = []
7
+ @jobs = {}
8
+
9
+ @job_servers = Array[*job_servers]
10
+
11
+ @uniq = opts.delete(:uniq)
12
+ @opts = opts
13
+ end
14
+
15
+ # Run a Task or Taskset
16
+ def run(taskset, timeout = nil, async = false)
17
+ timeout ||= 0
18
+ use_em_stop = EM.reactor_running?
19
+ EM.run do
20
+ @taskset ||= Taskset.new
21
+ @taskset += Taskset.create(taskset)
22
+
23
+ job_servers = @job_servers.dup
24
+ @reactors.each do |reactor|
25
+ unless reactor.connected?
26
+ reactor.reconnect true
27
+ reactor.keep_connected = async
28
+ reactor.callback { create_job(@taskset.shift, reactor) }
29
+ job_servers.delete reactor.to_s
30
+ else
31
+ create_job(@taskset.shift, reactor)
32
+ end
33
+ end
34
+ job_servers.each do |hostport|
35
+ host, port = hostport.split(":")
36
+ reactor = Gearman::Evented::ClientReactor.connect(host, port, @opts)
37
+ reactor.keep_connected = async
38
+ reactor.callback { create_job(@taskset.shift, reactor) }
39
+ @reactors << reactor
40
+ end
41
+
42
+ if timeout > 0
43
+ if use_em_stop
44
+ EM.add_timer(timeout) { EM.stop }
45
+ else
46
+ sleep timeout
47
+ end
48
+ elsif !async
49
+ Thread.new do
50
+ loop do
51
+ sleep 0.1
52
+ live = 0
53
+ @reactors.each {|reactor| live += 1 if reactor.connected? }
54
+ break if live == 0
55
+ end
56
+ end.join
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def create_job(task, reactor = nil)
64
+ return unless task
65
+ reactor ||= @reactors[rand(@reactors.size)]
66
+ unless reactor.connected?
67
+ log "create_job: server #{reactor} not connected"
68
+ EM.next_tick { create_job(task) }
69
+ return
70
+ end
71
+
72
+ reactor.submit_job(task) {|handle| create_job(@taskset.shift) }
73
+ end
74
+
75
+ def log(msg)
76
+ Gearman::Util.log(msg)
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,99 @@
1
+ module Gearman
2
+ module Evented
3
+
4
+ module ClientReactor
5
+ include Gearman::Evented::Reactor
6
+
7
+ def keep_connected
8
+ @keep_connected ||= (@opts[:keep_connected] || false)
9
+ end
10
+
11
+ def keep_connected=(keep)
12
+ @keep_connected = keep
13
+ end
14
+
15
+ def connection_completed
16
+ @cbs_job_created ||= []
17
+ @pending_jobs = []
18
+ @assigned_jobs = {}
19
+ @background_jobs = {}
20
+ super
21
+ end
22
+
23
+ def receive_data(data)
24
+ packets = Gearman::Protocol.decode_response(data)
25
+ log "received #{packets.size} packet(s) at once"
26
+ log "packets: #{packets.inspect}"
27
+ packets.each do |type, handle, *data|
28
+ dispatch_packet(type, handle, *data)
29
+ end
30
+ end
31
+
32
+ def dispatch_packet_callback(&callback)
33
+ @dispatch_packet_callback = callback
34
+ end
35
+
36
+ def dispatch_packet(type, handle, *data)
37
+ log "Got #{type.to_s}, #{handle}, #{data.inspect} from #{server}"
38
+ if type == :job_created
39
+ job_created(handle)
40
+ if cb = @cbs_job_created.shift
41
+ cb.call(handle)
42
+ end
43
+ else
44
+ dispatch(type, handle, data)
45
+ end
46
+ end
47
+
48
+ def submit_job(task, &cb_job_created)
49
+ cmd = "submit_job"
50
+ cmd << "_#{task.priority}" if [ :high, :low ].include?(task.priority)
51
+ cmd << "_bg" if task.background
52
+
53
+ log "#{cmd} #{task.name}, #{task.payload} to #{server}"
54
+ send cmd.to_sym, [ task.name, task.hash, task.payload ].join("\0")
55
+ @pending_jobs << task
56
+ @cbs_job_created << cb_job_created if cb_job_created
57
+ end
58
+
59
+ def job_created(handle)
60
+ job = @pending_jobs.shift
61
+ raise ProtocolError, "No job waiting for handle! (#{handle})" unless job
62
+ EM.add_periodic_timer(job.poll_status_interval) { get_status(handle) } if job.poll_status_interval
63
+ if job.background
64
+ @background_jobs[handle] = job
65
+ else
66
+ @assigned_jobs[handle] = job
67
+ end
68
+ end
69
+
70
+ def get_status(handle)
71
+ send :get_status, handle
72
+ end
73
+
74
+ def dispatch(type, handle, args)
75
+ return unless type
76
+ task = @assigned_jobs[handle]
77
+ task = @background_jobs[handle] unless task
78
+ raise ProtocolError, "No task by that name: #{handle}" unless task
79
+
80
+ if :work_fail == type && task.should_retry?
81
+ task.dispatch(:on_retry, task.retries_done)
82
+ @assigned_jobs.delete(handle)
83
+ submit_job(task)
84
+ return
85
+ end
86
+
87
+ if type == :status_res
88
+ task.dispatch(:on_status, args)
89
+ else
90
+ task.dispatch(type.to_s.sub("work", "on"), *args)
91
+ end
92
+
93
+ @assigned_jobs.delete(handle) if [:work_complete, :work_fail].include?(type)
94
+ disconnect if @assigned_jobs.empty? && !keep_connected
95
+ end
96
+ end
97
+
98
+ end
99
+ end