kongnomal 0.1.3
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/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
|
+
|