kongnomal 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/kongnomal +37 -0
- data/example/job_event_handler.rb +21 -0
- data/example/worker.rb +18 -0
- data/example/workers.rb +17 -0
- data/lib/kongnomal.rb +7 -0
- data/lib/kongnomal/beanstalk_server.rb +74 -0
- data/lib/kongnomal/job.rb +72 -0
- data/lib/kongnomal/job_queue.rb +39 -0
- metadata +111 -0
data/bin/kongnomal
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "rubygems"
|
3
|
+
require "optparse"
|
4
|
+
require "ostruct"
|
5
|
+
require "socket"
|
6
|
+
require "kongnomal"
|
7
|
+
|
8
|
+
options = OpenStruct.new
|
9
|
+
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "USAGE: kongnomal [options] job_event_handler_file"
|
12
|
+
|
13
|
+
opts.on("-c", "--connect-to [beanstalk_address]", "address of beanstalk process to connect to, in form of ip_address:port_number") do |config|
|
14
|
+
options.ip_address, options.port_number = config.split(":")
|
15
|
+
puts("beanstalk_address #{config} should be in form of ip_address:port_number") || exit unless options.ip_address and options.port_number
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
19
|
+
puts opts
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
end.parse!
|
23
|
+
options.job_event_handler = ARGV[0]
|
24
|
+
|
25
|
+
require File.expand_path(options.job_event_handler) if options.job_event_handler
|
26
|
+
unless options.ip_address and options.port_number
|
27
|
+
port_number, ip = begin
|
28
|
+
(dummy_server = TCPServer.new("127.0.0.1", 0)).addr[1..2]
|
29
|
+
ensure
|
30
|
+
dummy_server.close
|
31
|
+
end
|
32
|
+
puts "starting beanstalk server with new beanstalkd process at #{ip}:#{port_number}"
|
33
|
+
Kongnomal::BeanstalkServer.new.start(ip, port_number)
|
34
|
+
else
|
35
|
+
puts "starting beanstalk server with existing beanstalkd process at #{options.ip_address}:#{options.port_number}"
|
36
|
+
Kongnomal::BeanstalkServer.new.connect_to(options.ip_address, options.port_number)
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Kongnomal::Job.bind do |job|
|
2
|
+
job.when_touched do |event|
|
3
|
+
#do something when worker has given feedback
|
4
|
+
end
|
5
|
+
|
6
|
+
job.when_put do |event|
|
7
|
+
#do something when job is put into queue
|
8
|
+
end
|
9
|
+
|
10
|
+
job.when_reserved do |event|
|
11
|
+
#do something when job is reserved by a worker
|
12
|
+
end
|
13
|
+
|
14
|
+
job.when_deleted do |event|
|
15
|
+
#do something when job is deleted from tube
|
16
|
+
end
|
17
|
+
|
18
|
+
job.when_released do |event|
|
19
|
+
#do something when job is released
|
20
|
+
end
|
21
|
+
end
|
data/example/worker.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "rubygems"
|
3
|
+
require "kongnomal"
|
4
|
+
|
5
|
+
beanstalk_address = ARGV[0]
|
6
|
+
tube = ARGV[1]
|
7
|
+
|
8
|
+
jobs = Kongnomal::JobQueue.new(beanstalk_address, tube)
|
9
|
+
until jobs.peek_ready.nil? do
|
10
|
+
begin
|
11
|
+
job = jobs.reserve(1)
|
12
|
+
job.report_back({:state => "start"})
|
13
|
+
job.report_back({:state => "in_progress"})
|
14
|
+
job.report_back({:state => "success"})
|
15
|
+
job.delete
|
16
|
+
rescue Exception => e
|
17
|
+
end
|
18
|
+
end
|
data/example/workers.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "rubygems"
|
3
|
+
require "kongnomal"
|
4
|
+
|
5
|
+
ip_address = ARGV[0]
|
6
|
+
port_number = ARGV[1]
|
7
|
+
|
8
|
+
jobs = Kongnomal::JobQueue.new("#{ip_address}:#{port_number}", "jobs")
|
9
|
+
|
10
|
+
300.times do |i|
|
11
|
+
@pid = Process.spawn("./worker.rb '#{ip_address}:#{port_number}' 'jobs'")
|
12
|
+
Process.detach(@pid)
|
13
|
+
end
|
14
|
+
|
15
|
+
100000.times do |i|
|
16
|
+
jobs.put("job#{i}")
|
17
|
+
end
|
data/lib/kongnomal.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "beanstalk-client"
|
3
|
+
require "json"
|
4
|
+
require "yaml"
|
5
|
+
require File.join(File.dirname(__FILE__), "kongnomal", "beanstalk_server")
|
6
|
+
require File.join(File.dirname(__FILE__), "kongnomal", "job_queue")
|
7
|
+
require File.join(File.dirname(__FILE__), "kongnomal", "job")
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Kongnomal
|
2
|
+
class BeanstalkServer
|
3
|
+
def initialize(&blk)
|
4
|
+
@on_startup = block_given? ? blk : Proc.new { puts "beanstalk server is up and running..." }
|
5
|
+
at_exit {stop_beanstalkd}
|
6
|
+
self
|
7
|
+
end
|
8
|
+
|
9
|
+
def start(ip, port_number)
|
10
|
+
start_beanstalkd(ip, port_number)
|
11
|
+
connect_to(ip, port_number)
|
12
|
+
end
|
13
|
+
|
14
|
+
def connect_to(ip, port_number)
|
15
|
+
job_event_queue = job_event_queue("#{ip}:#{port_number}", Kongnomal::JobQueue::JOB_EVENT_TUBE)
|
16
|
+
@on_startup.call
|
17
|
+
|
18
|
+
loop do
|
19
|
+
job = begin
|
20
|
+
job_event_queue.reserve
|
21
|
+
rescue Exception => e
|
22
|
+
puts "Failed to retrieve job event from #{ip}:#{port_number}[#{Kongnomal::JobQueue::JOB_EVENT_TUBE}]"
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
|
26
|
+
job_evented = JSON.parse(job.body)
|
27
|
+
begin
|
28
|
+
Kongnomal::Job.send(job_evented["state"]).call(job_evented)
|
29
|
+
rescue Exception => e
|
30
|
+
job.release(nil, 60)
|
31
|
+
puts "Failed to process job event: #{job_evented.inspect}, retrying 60 sec later..."
|
32
|
+
puts e.message
|
33
|
+
puts e.backtrace
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
job.delete
|
39
|
+
rescue Exception => e
|
40
|
+
puts "Failed to delete job event: #{job_evented.inspect} from #{ip}:#{port_number}[#{Kongnomal::JobQueue::JOB_EVENT_TUBE}], ignoring for now to be reprocessed after natural beanstalk job timeout"
|
41
|
+
puts e.message
|
42
|
+
puts e.backtrace
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def start_beanstalkd(ip, port_number)
|
49
|
+
stop_beanstalkd
|
50
|
+
@pid = Process.spawn("beanstalkd -l #{ip} -p #{port_number}", :unsetenv_others => true, :close_others => true)
|
51
|
+
Process.detach(@pid)
|
52
|
+
puts "beanstalkd process(#{@pid}) has started at #{ip}:#{port_number}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop_beanstalkd
|
56
|
+
puts "beanstalkd process(#{@pid}) has been stopped" if @pid and system("kill #{@pid}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def job_event_queue(beanstalk_server_address, tube)
|
60
|
+
job_event_queue = 3.times do |i|
|
61
|
+
begin
|
62
|
+
puts "connecting to #{tube} tube at #{beanstalk_server_address}"
|
63
|
+
break Beanstalk::Connection.new(beanstalk_server_address, tube)
|
64
|
+
rescue Exception => e
|
65
|
+
raise e if i + 1 == 3
|
66
|
+
puts "reconnecting in 1 second..."
|
67
|
+
sleep(1)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
puts "listening for #{tube} tube at #{beanstalk_server_address}"
|
71
|
+
job_event_queue
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Kongnomal
|
2
|
+
class Job
|
3
|
+
PUT = "put".freeze
|
4
|
+
RESERVED = "reserved".freeze
|
5
|
+
DELETED = "deleted".freeze
|
6
|
+
TOUCHED = "touched".freeze
|
7
|
+
RELEASED = "released".freeze
|
8
|
+
|
9
|
+
def self.when_put(&blk)
|
10
|
+
@put_handler = blk
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.when_reserved(&blk)
|
14
|
+
@reserved_handler = blk
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.when_touched(&blk)
|
18
|
+
@touched_handler = blk
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.when_deleted(&blk)
|
22
|
+
@deleted_handler = blk
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.when_released(&blk)
|
26
|
+
@released_handler = blk
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
[PUT, RESERVED, TOUCHED, DELETED, RELEASED].each do |event|
|
31
|
+
define_method(event.to_sym) do
|
32
|
+
Proc.new do |job|
|
33
|
+
event_handler = instance_variable_get("@#{event}_handler")
|
34
|
+
event_handler.call(job) if event_handler
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def bind(&blk)
|
40
|
+
yield self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
extend Forwardable
|
45
|
+
def_delegators :@beanstalk_job, :id, :body, :ybody, :put_back, :bury, :stats, :timeouts, :time_left, :age, :state, :delay, :pri, :ttr, :server, :decay, :to_s, :inspect
|
46
|
+
|
47
|
+
def initialize(event_tube, beanstalk_job, tube)
|
48
|
+
@event_tube = event_tube
|
49
|
+
@beanstalk_job = beanstalk_job
|
50
|
+
@tube = tube
|
51
|
+
end
|
52
|
+
|
53
|
+
def touch(data)
|
54
|
+
as_msg = data.respond_to?(:to_hash) ? data.to_hash : data.to_s
|
55
|
+
touched = @beanstalk_job.touch
|
56
|
+
@event_tube.put({"state" => Kongnomal::Job::TOUCHED, "id" => id, "body" => body, "tube" => @tube, "msg" => as_msg}.to_json) if touched == :ok
|
57
|
+
touched
|
58
|
+
end
|
59
|
+
|
60
|
+
def release(*args, &blk)
|
61
|
+
released = @beanstalk_job.release(*args, &blk)
|
62
|
+
@event_tube.put({"state" => Kongnomal::Job::RELEASED, "id" => id, "body" => body, "tube" => @tube}.to_json) unless released.nil?
|
63
|
+
released
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete(*args, &blk)
|
67
|
+
deleted = @beanstalk_job.delete(*args, &blk)
|
68
|
+
@event_tube.put({"state" => Kongnomal::Job::DELETED, "id" => id, "body" => body, "tube" => @tube}.to_json) unless deleted.nil?
|
69
|
+
deleted
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Kongnomal
|
2
|
+
class JobQueue
|
3
|
+
JOB_EVENT_TUBE = "job.events"
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@job_tube, :peek_job, :peek_ready, :peek_delayed, :peek_buried, :bury, :kick, :stats, :job_stats, :stats_tube, :list_tubes, :list_tube_used, :list_tubes_watched
|
7
|
+
|
8
|
+
def initialize(beanstalk_server_address, tube = "default")
|
9
|
+
@tube = tube
|
10
|
+
@job_tube = Beanstalk::Connection.new(beanstalk_server_address, tube)
|
11
|
+
@event_tube = Beanstalk::Connection.new(beanstalk_server_address, JOB_EVENT_TUBE)
|
12
|
+
end
|
13
|
+
|
14
|
+
def put(body, pri=65536, delay=0, ttr=120)
|
15
|
+
job_id = @job_tube.put(body, pri, delay, ttr)
|
16
|
+
@event_tube.put({"state" => Kongnomal::Job::PUT, "tube" => @tube, "id" => job_id, "body" => body}.to_json)
|
17
|
+
job_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def yput(obj, pri=65536, delay=0, ttr=120)
|
21
|
+
job_id = @job_tube.yput(obj, pri, delay, ttr)
|
22
|
+
@event_tube.put({"state" => Kongnomal::Job::PUT, "tube" => @tube, "id" => job_id, "body" => YAML.dump(obj)}.to_json)
|
23
|
+
job_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def reserve(timeout=nil)
|
27
|
+
beanstalk_job = @job_tube.reserve(timeout)
|
28
|
+
@event_tube.put({"state" => Kongnomal::Job::RESERVED, "tube" => @tube, "id" => beanstalk_job.id, "body" => beanstalk_job.body}.to_json)
|
29
|
+
Kongnomal::Job.new(@event_tube, beanstalk_job, @tube)
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(id)
|
33
|
+
beanstalk_job = @job_tube.peek_job(id)
|
34
|
+
deleted = @job_tube.delete(id)
|
35
|
+
@event_tube.put({"state" => Kongnomal::Job::DELETED, "tube" => @tube, "id" => beanstalk_job.id, "body" => beanstalk_job.body}.to_json) if deleted == :ok
|
36
|
+
deleted
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kongnomal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jae Lee
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-02-05 00:00:00 +00:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: beanstalk-client
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 1
|
31
|
+
- 0
|
32
|
+
version: 1.1.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: agent
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id003
|
61
|
+
description: Register your custom job event handler that gets notified upon job event triggered from job.report_back or job.delete_then_report_back
|
62
|
+
email: jlee@yetitrails.com
|
63
|
+
executables:
|
64
|
+
- kongnomal
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- bin/kongnomal
|
71
|
+
- lib/kongnomal.rb
|
72
|
+
- lib/kongnomal/beanstalk_server.rb
|
73
|
+
- lib/kongnomal/job.rb
|
74
|
+
- lib/kongnomal/job_queue.rb
|
75
|
+
- example/job_event_handler.rb
|
76
|
+
- example/worker.rb
|
77
|
+
- example/workers.rb
|
78
|
+
has_rdoc: true
|
79
|
+
homepage: https://github.com/forward/kongnomal
|
80
|
+
licenses: []
|
81
|
+
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.3.7
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: beanstalk server wrapper that allows event handler registration on job event queue
|
110
|
+
test_files: []
|
111
|
+
|