glebpom-async_observer 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,221 @@
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
+ module AsyncObserver; end
21
+
22
+ class AsyncObserver::Worker
23
+
24
+ SLEEP_TIME = 60 if !defined?(SLEEP_TIME) # rails loads this file twice
25
+
26
+ class << self
27
+ attr_accessor :finish
28
+ attr_accessor :custom_error_handler
29
+ attr_accessor :before_filter
30
+ attr_writer :handle
31
+
32
+ def handle
33
+ @handle or raise 'no custom handler is defined'
34
+ end
35
+
36
+ def error_handler(&block)
37
+ self.custom_error_handler = block
38
+ end
39
+
40
+ def before_reserves
41
+ @before_reserves ||= []
42
+ end
43
+
44
+ def before_reserve(&block)
45
+ before_reserves << block
46
+ end
47
+
48
+ def run_before_reserve
49
+ before_reserves.each {|b| b.call}
50
+ end
51
+ end
52
+
53
+ def logger
54
+ $logger or RAILS_DEFAULT_LOGGER
55
+ end
56
+
57
+ def initialize(top_binding, options = {})
58
+ @top_binding = top_binding
59
+ @stop = false
60
+ @options = options
61
+ AsyncObserver::Queue.queue = @options[:servers]
62
+ end
63
+
64
+ def main_loop
65
+ trap('TERM') { @stop = true }
66
+ loop do
67
+ break if @stop
68
+ safe_dispatch(get_job)
69
+ end
70
+ end
71
+
72
+ def startup
73
+ tube = @options[:tube] || "default"
74
+ logger.info "Using tube #{tube}"
75
+ AsyncObserver::Queue.queue.watch(tube)
76
+ flush_logger
77
+ end
78
+
79
+ def shutdown
80
+ do_all_work
81
+ end
82
+
83
+ def run
84
+ startup
85
+ main_loop
86
+ rescue Interrupt
87
+ shutdown
88
+ end
89
+
90
+ def q_hint
91
+ @q_hint || AsyncObserver::Queue.queue
92
+ end
93
+
94
+ # This heuristic is to help prevent one queue from starving. The idea is that
95
+ # if the connection returns a job right away, it probably has more available.
96
+ # But if it takes time, then it's probably empty. So reuse the same
97
+ # connection as long as it stays fast. Otherwise, have no preference.
98
+ def reserve_and_set_hint
99
+ t1 = Time.now.utc
100
+ return job = q_hint.reserve
101
+ ensure
102
+ t2 = Time.now.utc
103
+ @q_hint = if brief?(t1, t2) and job then job.conn else nil end
104
+ end
105
+
106
+ def brief?(t1, t2)
107
+ ((t2 - t1) * 100).to_i.abs < 10
108
+ end
109
+
110
+ def get_job
111
+ loop do
112
+ begin
113
+ AsyncObserver::Queue.queue.connect
114
+ self.class.run_before_reserve
115
+ return reserve_and_set_hint
116
+ rescue Interrupt => ex
117
+ raise ex
118
+ rescue SignalException => ex
119
+ raise ex
120
+ rescue Beanstalk::DeadlineSoonError
121
+ # Do nothing; immediately try again, giving the user a chance to
122
+ # clean up in the before_reserve hook.
123
+ logger.info 'Job deadline soon; you should clean up.'
124
+ rescue Exception => ex
125
+ @q_hint = nil # in case there's something wrong with this conn
126
+ logger.info(
127
+ "#{ex.class}: #{ex}\n" + ex.backtrace.join("\n"))
128
+ logger.info 'something is wrong. We failed to get a job.'
129
+ logger.info "sleeping for #{SLEEP_TIME}s..."
130
+ sleep(SLEEP_TIME)
131
+ end
132
+ end
133
+ end
134
+
135
+ def dispatch(job)
136
+ ActiveRecord::Base.verify_active_connections!
137
+ return run_ao_job(job) if async_observer_job?(job)
138
+ return run_other(job)
139
+ end
140
+
141
+ def safe_dispatch(job)
142
+ logger.info "got #{job.inspect}:\n" + job.body
143
+ job.stats.each do |k,v|
144
+ logger.info "#{k}=#{v}"
145
+ end
146
+ begin
147
+ return dispatch(job)
148
+ rescue Interrupt => ex
149
+ begin job.release rescue :ok end
150
+ raise ex
151
+ rescue Exception => ex
152
+ handle_error(job, ex)
153
+ ensure
154
+ flush_logger
155
+ end
156
+ end
157
+
158
+ def flush_logger
159
+ if defined?(logger) &&
160
+ logger.respond_to?(:flush)
161
+ logger.flush
162
+ end
163
+ end
164
+
165
+ def handle_error(job, ex)
166
+ if self.class.custom_error_handler
167
+ self.class.custom_error_handler.call(job, ex)
168
+ else
169
+ self.class.default_handle_error(job, ex)
170
+ end
171
+ end
172
+
173
+ def self.default_handle_error(job, ex)
174
+ logger.info "Job failed: #{job.server}/#{job.id}"
175
+ logger.info("#{ex.class}: #{ex}\n" + ex.backtrace.join("\n"))
176
+ if job.stats['releases'] > 10
177
+ job.bury
178
+ logger.info "BURY job due to many releases"
179
+ else
180
+ job.decay
181
+ end
182
+ rescue Beanstalk::UnexpectedResponse
183
+ end
184
+
185
+ def run_ao_job(job)
186
+ logger.info 'running as async observer job'
187
+ f = self.class.before_filter
188
+ f.call(job) if f
189
+ job.delete if job.ybody[:delete_first]
190
+ run_code(job)
191
+ job.delete unless job.ybody[:delete_first]
192
+ rescue ActiveRecord::RecordNotFound => ex
193
+ unless job.ybody[:delete_first]
194
+ if job.age > 60
195
+ job.delete # it's old; this error is most likely permanent
196
+ else
197
+ job.decay # it could be replication delay so retry quietly
198
+ end
199
+ end
200
+ end
201
+
202
+ def run_code(job)
203
+ eval(job.ybody[:code], @top_binding, "(beanstalk job #{job.id})", 1)
204
+ end
205
+
206
+ def async_observer_job?(job)
207
+ begin job.ybody[:type] == :rails rescue false end
208
+ end
209
+
210
+ def run_other(job)
211
+ logger.info 'trying custom handler'
212
+ self.class.handle.call(job)
213
+ end
214
+
215
+ def do_all_work
216
+ logger.info 'finishing all running jobs. interrupt again to kill them.'
217
+ f = self.class.finish
218
+ f.call if f
219
+ end
220
+ end
221
+
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: glebpom-async_observer
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Gleb Pomykalov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-13 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: daemonizer
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: beanstalk-client
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rails
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 7
58
+ segments:
59
+ - 2
60
+ - 2
61
+ - 0
62
+ version: 2.2.0
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ description: async_observer provides deep integration with Beanstalk. Fork from http://github.com/kristjan/async_observer
66
+ email: glebpom@gmail.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files:
72
+ - README
73
+ files:
74
+ - .gitignore
75
+ - COPYING
76
+ - README
77
+ - Rakefile
78
+ - VERSION
79
+ - glebpom-async_observer.gemspec
80
+ - init.rb
81
+ - lib/async_observer/daemonizer_handler.rb
82
+ - lib/async_observer/extend.rb
83
+ - lib/async_observer/queue.rb
84
+ - lib/async_observer/worker.rb
85
+ has_rdoc: true
86
+ homepage: http://github.com/glebpom/async_observer
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options:
91
+ - --charset=UTF-8
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project:
115
+ rubygems_version: 1.3.7
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: async_observer provides deep integration with Beanstalk. Fork from http://github.com/kristjan/async_observer
119
+ test_files: []
120
+