beanstalker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,27 @@
1
+ This is Async Observer -- a Rails plugin that provides deep integration with
2
+ Beanstalk.
3
+
4
+ For more information, see http://async-observer.rubyforge.org/.
5
+
6
+ For more information on Beanstalk, see its home page at
7
+ http://xph.us/software/beanstalkd/.
8
+
9
+
10
+ Worker Options:
11
+ -d : daemonize
12
+ --pid [path to pidfile] : drop a pid file to a path
13
+ -e [test,production,development] : set the rails environment
14
+
15
+ Example Usage:
16
+
17
+ start 3 workers
18
+ ./vendor/plugins/async_observer/bin/worker -d --pid log/worker1.pid -e production
19
+ ./vendor/plugins/async_observer/bin/worker -d --pid log/worker2.pid -e production
20
+ ./vendor/plugins/async_observer/bin/worker -d --pid log/worker3.pid -e production
21
+
22
+ kill one
23
+ kill -s INT `cat log/worker1.pid`
24
+
25
+ Remember kill a worker will cause it to go into a shutdown phase.
26
+ Run the above again to kill immediately, but remember all jobs in
27
+ the workers queue is lost at that point...
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "beanstalker"
8
+ gem.summary = %Q{Beanstalker provides deep integration with Beanstalk. Fork from http://github.com/kristjan/async_observer}
9
+ gem.description = %Q{Beanstalker provides deep integration with Beanstalk. Fork from http://github.com/kristjan/async_observer}
10
+ gem.email = "glebpom@gmail.com"
11
+ gem.homepage = "http://github.com/glebpom/beanstalker"
12
+ gem.authors = ["Gleb Pomykalov"]
13
+ # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ gem.add_dependency "daemonizer"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "beanstalker #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/worker ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ # async-observer - Rails plugin for asynchronous job execution
3
+
4
+ # Copyright (C) 2007 Philotic Inc.
5
+
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+
20
+ # Use the same pointer (and therefore same buffer) for stdout and stderr.
21
+ $VERBOSE = nil; STDERR = $stderr = STDOUT = $stdout; $VERBOSE = false
22
+
23
+ require 'time'
24
+
25
+ def load_rails_and_run
26
+ # Rails initialization.
27
+ # We do this here instead of using script/runner because script/runner
28
+ # breaks __FILE__, which we use below.
29
+ begin
30
+ puts "#!load-rails!begin!#{Time.now.utc.xmlschema(6)}"
31
+ require File.expand_path(File.dirname(__FILE__) + '/../../../../config/boot')
32
+ puts "RAILS_ROOT=#{RAILS_ROOT.inspect}"
33
+ require RAILS_ROOT + '/config/environment'
34
+ ensure
35
+ puts "#!load-rails!end!#{Time.now.utc.xmlschema(6)}"
36
+ end
37
+ require 'async_observer/worker'
38
+ AsyncObserver::Worker.new(binding).run()
39
+ end
40
+
41
+ # set environment
42
+ if ARGV.include?('-e') # check rails env
43
+ env=nil
44
+ ARGV.each_with_index{|arg,i| env = ARGV[i+1] if arg == '-e' }
45
+ RAILS_ENV=env unless env.nil?
46
+ end
47
+
48
+ if ARGV.include?('-d')
49
+ pidpath = 'log/worker.pid'
50
+ if ARGV.include?('--pid') # look up the pid path
51
+ ARGV.each_with_index{|arg,i| pidpath = ARGV[i+1] if arg == '--pid' }
52
+ STDERR.puts "Missing pid file path!" and exit(1) if pidpath.nil?
53
+ end
54
+ unless File.writable?(File.dirname(pidpath))
55
+ STDERR.puts "#{pidpath} not writable!"
56
+ exit(1)
57
+ end
58
+
59
+ if File.exist?(pidpath)
60
+ STDERR.puts "#{pidpath} exits! Make sure the worker isn't still running and try again after rm #{pidpath}"
61
+ exit(1)
62
+ end
63
+
64
+ require File.dirname(__FILE__) + '/../lib/async_observer/daemonize'
65
+ AsyncObserver::Daemonize.detach(pidpath) do
66
+ load_rails_and_run
67
+ end
68
+ else
69
+ load_rails_and_run
70
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'async_observer/extend'
@@ -0,0 +1,35 @@
1
+ # async-observer - Rails plugin for asynchronous job execution
2
+ # Copyright (C) 2009 Todd A. Fisher.
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ module AsyncObserver; end
17
+
18
+ class AsyncObserver::Daemonize
19
+ def self.detach(pidfile='log/worker.pid',&block)
20
+ # daemonize, create a pipe to send status to the parent process, after the child has successfully started or failed
21
+ fork do
22
+ Process.setsid
23
+ fork do
24
+ Process.setsid
25
+ File.open(pidfile, 'wb') {|f| f << Process.pid}
26
+ at_exit { File.unlink(pidfile) }
27
+ File.umask 0000
28
+ STDIN.reopen "/dev/null"
29
+ STDOUT.reopen "/dev/null", "a"
30
+ STDERR.reopen STDOUT
31
+ block.call
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,140 @@
1
+ # async-observer - Rails plugin for asynchronous job execution
2
+
3
+ # Copyright (C) 2007 Philotic Inc.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'async_observer/queue'
19
+
20
+ CLASSES_TO_EXTEND = [
21
+ ActiveRecord::Base,
22
+ Array,
23
+ Hash,
24
+ Module,
25
+ Numeric,
26
+ Range,
27
+ String,
28
+ Symbol,
29
+ ]
30
+
31
+ module AsyncObserver::Extensions
32
+ def async_send(selector, *args)
33
+ async_send_opts(selector, {}, *args)
34
+ end
35
+
36
+ def async_send_opts(selector, opts, *args)
37
+ AsyncObserver::Queue.put_call!(self, selector, opts, args)
38
+ end
39
+ end
40
+
41
+ CLASSES_TO_EXTEND.each do |c|
42
+ c.send :include, AsyncObserver::Extensions
43
+ end
44
+
45
+ class Range
46
+ DEFAULT_FANOUT_FUZZ = 0
47
+ DEFAULT_FANOUT_DEGREE = 1000
48
+
49
+ def split_to(n)
50
+ split_by((size + n - 1) / n) { |group| yield(group) }
51
+ end
52
+
53
+ def split_by(n)
54
+ raise ArgumentError.new('invalid slice size') if n < 1
55
+ n -= 1 if !exclude_end?
56
+ i = first
57
+ while member?(i)
58
+ j = [i + n, last].min
59
+ yield(Range.new(i, j, exclude_end?))
60
+ i = j + (exclude_end? ? 0 : 1)
61
+ end
62
+ end
63
+
64
+ def size
65
+ last - first + (exclude_end? ? 0 : 1)
66
+ end
67
+
68
+ def async_each_opts(rcv, selector, opts, *extra)
69
+ fanout_degree = opts.fetch(:fanout_degree, DEFAULT_FANOUT_DEGREE)
70
+ if size <= fanout_degree
71
+ each {|i| rcv.async_send_opts(selector, opts, i, *extra)}
72
+ else
73
+ fanout_opts = opts.merge(:fuzz => opts.fetch(:fanout_fuzz,
74
+ DEFAULT_FANOUT_FUZZ))
75
+ fanout_opts[:pri] = opts[:fanout_pri] || opts[:pri]
76
+ fanout_opts = fanout_opts.reject_hash{|k,v| nil.equal?(v)}
77
+ split_to(fanout_degree) do |subrange|
78
+ subrange.async_send_opts(:async_each_opts, fanout_opts, rcv, selector,
79
+ opts, *extra)
80
+ end
81
+ end
82
+ end
83
+
84
+ def async_each(rcv, selector, *extra)
85
+ async_each_opts(rcv, selector, {}, *extra)
86
+ end
87
+ end
88
+
89
+ HOOKS = [:after_create, :after_update, :after_save]
90
+
91
+ class << ActiveRecord::Base
92
+ HOOKS.each do |hook|
93
+ code = %Q{def async_#{hook}(*methods, &b) add_async_hook(#{hook.inspect}, *methods, &b) end}
94
+ class_eval(code, __FILE__, __LINE__ - 1)
95
+ end
96
+
97
+ def add_async_hook(hook, *args, &block)
98
+ if args && args.first.is_a?(Symbol)
99
+ method = args.shift
100
+ async_hooks[hook] << lambda{|o| o.send(method)}
101
+ else
102
+ async_hooks[hook] << block
103
+ end
104
+ end
105
+
106
+ def async_hooks
107
+ @async_hooks ||= Hash.new do |hash, hook|
108
+ ahook = :"_async_#{hook}"
109
+
110
+ # This is for the producer's benefit
111
+ send(hook){|o| async_send(ahook, o)}
112
+
113
+ # This is for the worker's benefit
114
+ code = "def #{ahook}(o) run_async_hooks(#{hook.inspect}, o) end"
115
+ instance_eval(code, __FILE__, __LINE__ - 1)
116
+
117
+ hash[hook] = []
118
+ end
119
+ end
120
+
121
+ def run_async_hooks(hook, o)
122
+ async_hooks[hook].each{|b| b.call(o)}
123
+ end
124
+
125
+ def send_to_instance(id, selector, *args)
126
+ x = find_by_id(id)
127
+ x.send(selector, *args) if x
128
+ end
129
+
130
+ def async_each_opts(selector, opts, *args)
131
+ min = opts.fetch(:min, minimum(:id))
132
+ max = opts.fetch(:max, maximum(:id))
133
+
134
+ (min..max).async_each_opts(self, :send_to_instance, opts, selector, *args)
135
+ end
136
+
137
+ def async_each(selector, *args)
138
+ async_each_opts(selector, {}, *args)
139
+ end
140
+ end
@@ -0,0 +1,182 @@
1
+ # async-observer - Rails plugin for asynchronous job execution
2
+
3
+ # Copyright (C) 2007 Philotic Inc.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+
19
+ require 'beanstalk-client'
20
+
21
+ module AsyncObserver; end
22
+
23
+ class AsyncObserver::Queue; end
24
+
25
+ class << AsyncObserver::Queue
26
+ DEFAULT_PRI = 512
27
+ DEFAULT_FUZZ = 0
28
+ DEFAULT_DELAY = 0
29
+ DEFAULT_TTR = 120
30
+ DEFAULT_TUBE = 'default'
31
+
32
+ attr_accessor :queue, :app_version, :after_put
33
+
34
+ # This is a fake worker instance for running jobs synchronously.
35
+ def sync_worker()
36
+ require 'async_observer/worker'
37
+ @sync_worker ||= AsyncObserver::Worker.new(binding)
38
+ end
39
+
40
+ # This runs jobs synchronously; it's used when no queue is configured.
41
+ def sync_run(obj)
42
+ body = YAML.dump(obj)
43
+ job = Beanstalk::Job.new(AsyncObserver::FakeConn.new(), 0, body)
44
+ sync_worker.dispatch(job)
45
+ sync_worker.do_all_work()
46
+ return 0, '0.0.0.0'
47
+ end
48
+
49
+ def put!(obj, pri=DEFAULT_PRI, delay=DEFAULT_DELAY, ttr=DEFAULT_TTR,
50
+ tube=DEFAULT_TUBE)
51
+ return sync_run(obj) if (:direct.equal?(pri) or !queue)
52
+ queue.connect()
53
+ queue.use(tube)
54
+ info = [queue.yput(obj, pri, delay, ttr), queue.last_server]
55
+ f = AsyncObserver::Queue.after_put
56
+ f.call(*info) if f
57
+ return info
58
+ end
59
+
60
+ SUBMIT_OPTS = [:pri, :fuzz, :delay, :ttr, :tube]
61
+
62
+ def put_call!(obj, sel, opts, args=[])
63
+ pri = opts.fetch(:pri, DEFAULT_PRI)
64
+ fuzz = opts.fetch(:fuzz, DEFAULT_FUZZ)
65
+ delay = opts.fetch(:delay, DEFAULT_DELAY)
66
+ ttr = opts.fetch(:ttr, DEFAULT_TTR)
67
+ tube = opts.fetch(:tube, (app_version or DEFAULT_TUBE))
68
+ worker_opts = opts.reject{|k,v| SUBMIT_OPTS.include?(k)}
69
+
70
+ pri = pri + rand(fuzz + 1) if !:direct.equal?(pri)
71
+
72
+ code = gen(obj, sel, args)
73
+ RAILS_DEFAULT_LOGGER.info("put #{pri} #{code}")
74
+ put!(pkg(code, worker_opts), pri, delay, ttr, tube)
75
+ end
76
+
77
+ def pkg(code, opts)
78
+ opts.merge(:type => :rails, :code => code)
79
+ end
80
+
81
+ # Be careful not to pass in a selector that's not valid ruby source.
82
+ def gen(obj, selector, args)
83
+ obj.rrepr + '.' + selector.to_s + '(' + gen_args(args) + ')'
84
+ end
85
+
86
+ def gen_args(args)
87
+ args.rrepr[1...-1]
88
+ end
89
+ end
90
+
91
+ class AsyncObserver::FakeConn
92
+ def delete(x)
93
+ end
94
+
95
+ def release(x, y, z)
96
+ end
97
+
98
+ def bury(x, y)
99
+ end
100
+
101
+ def addr
102
+ '<none>'
103
+ end
104
+
105
+ def job_stats(id)
106
+ {
107
+ 'id' => id,
108
+ 'state' => 'reserved',
109
+ 'age' => 0,
110
+ 'delay' => 0,
111
+ 'time-left' => 5000,
112
+ 'timeouts' => 0,
113
+ 'releases' => 0,
114
+ 'buries' => 0,
115
+ 'kicks' => 0,
116
+ }
117
+ end
118
+ end
119
+
120
+ # This is commented out to workaround (what we think is) a ruby bug in method
121
+ # lookup. Somehow the methods defined here are being used instead of ones in
122
+ # ActiveRecord::Base.
123
+ #class Object
124
+ # def rrepr()
125
+ # raise ArgumentError.new('no consistent external repr for ' + self.inspect)
126
+ # end
127
+ #end
128
+
129
+ class Symbol
130
+ def rrepr() inspect end
131
+ end
132
+
133
+ class Module
134
+ def rrepr() name end
135
+ end
136
+
137
+ class NilClass
138
+ def rrepr() inspect end
139
+ end
140
+
141
+ class FalseClass
142
+ def rrepr() inspect end
143
+ end
144
+
145
+ class TrueClass
146
+ def rrepr() inspect end
147
+ end
148
+
149
+ class Numeric
150
+ def rrepr() inspect end
151
+ end
152
+
153
+ class String
154
+ def rrepr() inspect end
155
+ end
156
+
157
+ class Array
158
+ def rrepr() '[' + map(&:rrepr).join(', ') + ']' end
159
+ end
160
+
161
+ class Hash
162
+ def rrepr() '{' + map{|k,v| k.rrepr + '=>' + v.rrepr}.join(', ') + '}' end
163
+ end
164
+
165
+ class Range
166
+ def rrepr() "(#{first.rrepr}#{exclude_end? ? '...' : '..'}#{last.rrepr})" end
167
+ end
168
+
169
+ class Time
170
+ def rrepr() "Time.parse('#{self.inspect}')" end
171
+ end
172
+
173
+ class Date
174
+ def rrepr() "Date.parse('#{self.inspect}')" end
175
+ end
176
+
177
+ module AsyncObserver::Extensions
178
+ def rrepr()
179
+ method = (respond_to? :get_cache) ? 'get_cache' : 'find'
180
+ "#{self.class.rrepr}.#{method}(#{id.rrepr})"
181
+ end
182
+ end