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.
- data/.gitignore +1 -0
- data/COPYING +674 -0
- data/README +27 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/worker +70 -0
- data/init.rb +1 -0
- data/lib/async_observer/daemonize.rb +35 -0
- data/lib/async_observer/extend.rb +140 -0
- data/lib/async_observer/queue.rb +182 -0
- data/lib/async_observer/util.rb +31 -0
- data/lib/async_observer/worker.rb +263 -0
- metadata +91 -0
@@ -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
|
+
|