beanstalker 0.0.1

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