beanstalker 0.0.1

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.
@@ -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
+