creeper 1.0.9 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rvmrc +48 -0
- data/Gemfile +17 -1
- data/Guardfile +32 -0
- data/Rakefile +9 -1
- data/bin/creeper +10 -58
- data/bin/creeperctl +74 -0
- data/config.ru +18 -0
- data/creeper.gemspec +19 -9
- data/lib/creeper.rb +108 -413
- data/lib/creeper/beanstalk_connection.rb +35 -0
- data/lib/creeper/cli.rb +225 -0
- data/lib/creeper/client.rb +93 -0
- data/lib/creeper/core_ext.rb +54 -0
- data/lib/creeper/exception_handler.rb +30 -0
- data/lib/creeper/extensions/action_mailer.rb +33 -0
- data/lib/creeper/extensions/active_record.rb +30 -0
- data/lib/creeper/extensions/generic_proxy.rb +26 -0
- data/lib/creeper/fetch.rb +94 -0
- data/lib/creeper/legacy.rb +46 -0
- data/lib/creeper/logging.rb +46 -0
- data/lib/creeper/manager.rb +164 -0
- data/lib/creeper/middleware/chain.rb +100 -0
- data/lib/creeper/middleware/server/active_record.rb +13 -0
- data/lib/creeper/middleware/server/logging.rb +31 -0
- data/lib/creeper/middleware/server/retry_jobs.rb +79 -0
- data/lib/creeper/middleware/server/timeout.rb +21 -0
- data/lib/creeper/paginator.rb +31 -0
- data/lib/creeper/processor.rb +116 -0
- data/lib/creeper/rails.rb +21 -0
- data/lib/creeper/redis_connection.rb +28 -0
- data/lib/creeper/testing.rb +44 -0
- data/lib/creeper/util.rb +45 -0
- data/lib/creeper/version.rb +1 -1
- data/lib/creeper/web.rb +248 -0
- data/lib/creeper/worker.rb +62 -313
- data/spec/dummy/.gitignore +15 -0
- data/spec/dummy/Gemfile +51 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/images/rails.png +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/work_controller.rb +71 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/mailers/user_mailer.rb +9 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/post.rb +8 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/user_mailer/greetings.html.erb +3 -0
- data/spec/dummy/app/views/work/index.html.erb +1 -0
- data/spec/dummy/app/workers/fast_worker.rb +10 -0
- data/spec/dummy/app/workers/hard_worker.rb +11 -0
- data/spec/dummy/app/workers/lazy_worker.rb +12 -0
- data/spec/dummy/app/workers/suicidal_worker.rb +33 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +68 -0
- data/spec/dummy/config/boot.rb +6 -0
- data/spec/dummy/config/creeper.yml +9 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/creeper.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +13 -0
- data/spec/dummy/db/migrate/20120123214055_create_posts.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/tasks/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/index.html +241 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/dummy/vendor/plugins/.gitkeep +0 -0
- data/spec/lib/creeper/cli_spec.rb +208 -0
- data/spec/lib/creeper/client_spec.rb +110 -0
- data/spec/lib/creeper/exception_handler_spec.rb +110 -0
- data/spec/lib/creeper/processor_spec.rb +92 -0
- data/spec/lib/creeper/testing_spec.rb +105 -0
- data/spec/lib/creeper_spec.rb +54 -120
- data/spec/spec_helper.rb +81 -7
- data/spec/support/config.yml +9 -0
- data/spec/support/fake_env.rb +0 -0
- data/spec/support/workers/base_worker.rb +11 -0
- data/spec/support/workers/my_worker.rb +4 -0
- data/spec/support/workers/queued_worker.rb +5 -0
- data/spec/support/workers/real_worker.rb +10 -0
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/javascripts/application.js +49 -0
- data/web/assets/javascripts/vendor/bootstrap.js +12 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
- data/web/assets/javascripts/vendor/jquery.js +9266 -0
- data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
- data/web/assets/stylesheets/application.css +6 -0
- data/web/assets/stylesheets/layout.css +26 -0
- data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
- data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
- data/web/views/_paging.slim +15 -0
- data/web/views/_summary.slim +9 -0
- data/web/views/_workers.slim +14 -0
- data/web/views/index.slim +10 -0
- data/web/views/layout.slim +37 -0
- data/web/views/poll.slim +3 -0
- data/web/views/queue.slim +15 -0
- data/web/views/queues.slim +19 -0
- data/web/views/retries.slim +31 -0
- data/web/views/retry.slim +52 -0
- data/web/views/scheduled.slim +27 -0
- metadata +341 -23
- data/lib/creeper/celluloid_ext.rb +0 -42
- data/lib/creeper/creep.rb +0 -25
- data/lib/creeper/err_logger.rb +0 -37
- data/lib/creeper/launcher.rb +0 -44
- data/lib/creeper/out_logger.rb +0 -39
- data/spec/lib/creeper/session_spec.rb +0 -15
- data/spec/lib/creeper/worker_spec.rb +0 -21
data/lib/creeper/web.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'slim'
|
3
|
+
require 'sprockets'
|
4
|
+
require 'creeper/paginator'
|
5
|
+
|
6
|
+
module Creeper
|
7
|
+
class SprocketsMiddleware
|
8
|
+
def initialize(app, options={})
|
9
|
+
@app = app
|
10
|
+
@root = options[:root]
|
11
|
+
path = options[:path] || 'assets'
|
12
|
+
@matcher = /^\/#{path}\/*/
|
13
|
+
@environment = ::Sprockets::Environment.new(@root)
|
14
|
+
@environment.append_path 'assets/javascripts'
|
15
|
+
@environment.append_path 'assets/javascripts/vendor'
|
16
|
+
@environment.append_path 'assets/stylesheets'
|
17
|
+
@environment.append_path 'assets/stylesheets/vendor'
|
18
|
+
@environment.append_path 'assets/images'
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
# Solve the problem of people requesting /creeper when they need to request /creeper/ so
|
23
|
+
# that relative links in templates resolve correctly.
|
24
|
+
return [301, { 'Location' => "#{env['SCRIPT_NAME']}/", 'Content-Type' => 'text/html' }, ['redirecting']] if env['SCRIPT_NAME'] == env['REQUEST_PATH']
|
25
|
+
|
26
|
+
return @app.call(env) unless @matcher =~ env["PATH_INFO"]
|
27
|
+
env['PATH_INFO'].sub!(@matcher,'')
|
28
|
+
@environment.call(env)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Web < Sinatra::Base
|
33
|
+
include Creeper::Paginator
|
34
|
+
|
35
|
+
dir = File.expand_path(File.dirname(__FILE__) + "/../../web")
|
36
|
+
set :views, "#{dir}/views"
|
37
|
+
set :root, "#{dir}/public"
|
38
|
+
set :slim, :pretty => true
|
39
|
+
use SprocketsMiddleware, :root => dir
|
40
|
+
|
41
|
+
helpers do
|
42
|
+
|
43
|
+
def reset_worker_list
|
44
|
+
Creeper.redis do |conn|
|
45
|
+
workers = conn.smembers('workers')
|
46
|
+
workers.each do |name|
|
47
|
+
conn.srem('workers', name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def workers
|
53
|
+
@workers ||= begin
|
54
|
+
Creeper.redis do |conn|
|
55
|
+
conn.smembers('workers').map do |w|
|
56
|
+
msg = conn.get("worker:#{w}")
|
57
|
+
msg ? [w, Creeper.load_json(msg)] : nil
|
58
|
+
end.compact.sort { |x| x[1] ? -1 : 1 }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def processed
|
64
|
+
Creeper.redis { |conn| conn.get('stat:processed') } || 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def failed
|
68
|
+
Creeper.redis { |conn| conn.get('stat:failed') } || 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def zcard(name)
|
72
|
+
Creeper.redis { |conn| conn.zcard(name) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def queues
|
76
|
+
@queues ||= Creeper.redis do |conn|
|
77
|
+
conn.smembers('queues').map do |q|
|
78
|
+
[q, conn.llen("queue:#{q}") || 0]
|
79
|
+
end.sort { |x,y| x[1] <=> y[1] }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def backlog
|
84
|
+
# queues.map {|name, size| size }.inject(0) {|memo, val| memo + val }
|
85
|
+
beanstalk_stats['current-jobs-ready']
|
86
|
+
end
|
87
|
+
|
88
|
+
def buried
|
89
|
+
beanstalk_stats['current-jobs-buried']
|
90
|
+
end
|
91
|
+
|
92
|
+
def scheduled
|
93
|
+
beanstalk_stats['current-jobs-delayed']
|
94
|
+
end
|
95
|
+
|
96
|
+
def retries_with_score(score)
|
97
|
+
Creeper.redis do |conn|
|
98
|
+
results = conn.zrangebyscore('retry', score, score)
|
99
|
+
results.map { |msg| Creeper.load_json(msg) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def beanstalk_stats
|
104
|
+
@beanstalk_stats ||= Creeper.beanstalk do |conn|
|
105
|
+
conn.stats
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def beanstalk_location
|
110
|
+
Creeper.beanstalk { |conn| conn.instance_variable_get(:@addrs) }.join(', ')
|
111
|
+
end
|
112
|
+
|
113
|
+
def location
|
114
|
+
Creeper.redis { |conn| conn.client.location }
|
115
|
+
end
|
116
|
+
|
117
|
+
def root_path
|
118
|
+
"#{env['SCRIPT_NAME']}/"
|
119
|
+
end
|
120
|
+
|
121
|
+
def current_status
|
122
|
+
return 'idle' if workers.size == 0
|
123
|
+
return 'active'
|
124
|
+
end
|
125
|
+
|
126
|
+
def relative_time(time)
|
127
|
+
%{<time datetime="#{time.getutc.iso8601}">#{time}</time>}
|
128
|
+
end
|
129
|
+
|
130
|
+
def display_args(args, count=100)
|
131
|
+
args.map { |arg| a = arg.inspect; a.size > count ? "#{a[0..count]}..." : a }.join(", ")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
get "/" do
|
136
|
+
slim :index
|
137
|
+
end
|
138
|
+
|
139
|
+
get "/poll" do
|
140
|
+
slim :poll, layout: false
|
141
|
+
end
|
142
|
+
|
143
|
+
get "/queues" do
|
144
|
+
@queues = queues
|
145
|
+
slim :queues
|
146
|
+
end
|
147
|
+
|
148
|
+
get "/queues/:name" do
|
149
|
+
halt 404 unless params[:name]
|
150
|
+
@count = (params[:count] || 25).to_i
|
151
|
+
@name = params[:name]
|
152
|
+
(@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count)
|
153
|
+
@messages = @messages.map {|msg| Creeper.load_json(msg) }
|
154
|
+
slim :queue
|
155
|
+
end
|
156
|
+
|
157
|
+
post "/reset" do
|
158
|
+
reset_worker_list
|
159
|
+
redirect root_path
|
160
|
+
end
|
161
|
+
|
162
|
+
post "/queues/:name" do
|
163
|
+
Creeper.redis do |conn|
|
164
|
+
conn.del("queue:#{params[:name]}")
|
165
|
+
conn.srem("queues", params[:name])
|
166
|
+
end
|
167
|
+
redirect "#{root_path}queues"
|
168
|
+
end
|
169
|
+
|
170
|
+
get "/retries/:score" do
|
171
|
+
halt 404 unless params[:score]
|
172
|
+
@score = params[:score].to_f
|
173
|
+
@retries = retries_with_score(@score)
|
174
|
+
redirect "#{root_path}retries" if @retries.empty?
|
175
|
+
slim :retry
|
176
|
+
end
|
177
|
+
|
178
|
+
get '/retries' do
|
179
|
+
@count = (params[:count] || 25).to_i
|
180
|
+
(@current_page, @total_size, @retries) = page("retry", params[:page], @count)
|
181
|
+
@retries = @retries.map {|msg, score| [Creeper.load_json(msg), score] }
|
182
|
+
slim :retries
|
183
|
+
end
|
184
|
+
|
185
|
+
get '/scheduled' do
|
186
|
+
@count = (params[:count] || 25).to_i
|
187
|
+
(@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
|
188
|
+
@scheduled = @scheduled.map {|msg, score| [Creeper.load_json(msg), score] }
|
189
|
+
slim :scheduled
|
190
|
+
end
|
191
|
+
|
192
|
+
post '/scheduled' do
|
193
|
+
halt 404 unless params[:score]
|
194
|
+
halt 404 unless params['delete']
|
195
|
+
params[:score].each do |score|
|
196
|
+
s = score.to_f
|
197
|
+
process_score('schedule', s, :delete)
|
198
|
+
end
|
199
|
+
redirect "#{root_path}scheduled"
|
200
|
+
end
|
201
|
+
|
202
|
+
post '/retries' do
|
203
|
+
halt 404 unless params[:score]
|
204
|
+
params[:score].each do |score|
|
205
|
+
s = score.to_f
|
206
|
+
if params['retry']
|
207
|
+
process_score('retry', s, :retry)
|
208
|
+
elsif params['delete']
|
209
|
+
process_score('retry', s, :delete)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
redirect "#{root_path}retries"
|
213
|
+
end
|
214
|
+
|
215
|
+
post "/retries/:score" do
|
216
|
+
halt 404 unless params[:score]
|
217
|
+
score = params[:score].to_f
|
218
|
+
if params['retry']
|
219
|
+
process_score('retry', score, :retry)
|
220
|
+
elsif params['delete']
|
221
|
+
process_score('retry', score, :delete)
|
222
|
+
end
|
223
|
+
redirect "#{root_path}retries"
|
224
|
+
end
|
225
|
+
|
226
|
+
def process_score(set, score, operation)
|
227
|
+
case operation
|
228
|
+
when :retry
|
229
|
+
raise "Not Implemented"
|
230
|
+
Creeper.redis do |conn|
|
231
|
+
results = conn.zrangebyscore(set, score, score)
|
232
|
+
conn.zremrangebyscore(set, score, score)
|
233
|
+
results.map do |message|
|
234
|
+
msg = Creeper.load_json(message)
|
235
|
+
msg['retry_count'] = msg['retry_count'] - 1
|
236
|
+
conn.rpush("queue:#{msg['queue']}", Creeper.dump_json(msg))
|
237
|
+
end
|
238
|
+
end
|
239
|
+
when :delete
|
240
|
+
Creeper.redis do |conn|
|
241
|
+
conn.zremrangebyscore(set, score, score)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
data/lib/creeper/worker.rb
CHANGED
@@ -1,331 +1,80 @@
|
|
1
|
-
require 'creeper'
|
2
|
-
|
3
|
-
require 'celluloid'
|
4
|
-
require 'creeper/celluloid_ext'
|
5
|
-
# require 'em-jack'
|
1
|
+
require 'creeper/client'
|
2
|
+
require 'creeper/core_ext'
|
6
3
|
|
7
4
|
module Creeper
|
8
5
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# Creeper.worker_pool = Creeper::Worker.pool(options)
|
44
|
-
|
45
|
-
# sleep 1
|
46
|
-
|
47
|
-
# EM.run do
|
48
|
-
# trap(:INT) { Creeper.shutdown = true; EM.stop }
|
49
|
-
# trap(:TERM) { Creeper.shutdown = true; EM.stop }
|
50
|
-
# trap(:QUIT) { Creeper.shutdown = true; EM.stop }
|
51
|
-
|
52
|
-
# jack = EMJack::Connection.new(Creeper.beanstalk_url)
|
53
|
-
|
54
|
-
# reserve_loop = ->(timeout) do
|
55
|
-
# r = jack.reserve(timeout)
|
56
|
-
# r.callback do |job|
|
57
|
-
# Creeper.worker_pool.work! job
|
58
|
-
# EM.next_tick { reserve_loop.call(timeout) }
|
59
|
-
# end
|
60
|
-
# r.errback do
|
61
|
-
# EM.next_tick { reserve_loop.call(timeout) }
|
62
|
-
# end
|
63
|
-
# end
|
64
|
-
|
65
|
-
# watch_tubes = ->(list) do
|
66
|
-
# if list.empty?
|
67
|
-
# reserve_loop.call(Creeper.reserve_timeout)
|
68
|
-
# else
|
69
|
-
# w = jack.watch(list.shift)
|
70
|
-
# w.callback do
|
71
|
-
# watch_tubes.call(list)
|
72
|
-
# end
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
|
76
|
-
# watch_tubes.call(tubes)
|
77
|
-
|
78
|
-
# end
|
79
|
-
|
80
|
-
# end
|
81
|
-
|
82
|
-
def self.jobs_for(jobs = nil)
|
83
|
-
case jobs
|
84
|
-
when :all, nil
|
85
|
-
Creeper.all_jobs
|
86
|
-
else
|
87
|
-
Array(jobs)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
include Celluloid
|
92
|
-
|
93
|
-
attr_accessor :number
|
94
|
-
attr_reader :jobs
|
95
|
-
|
96
|
-
def initialize(jobs = nil)
|
97
|
-
@jobs = self.class.jobs_for(jobs)
|
98
|
-
|
99
|
-
Creeper.register_worker(self)
|
100
|
-
OutLogger.info "#{prefix} Working #{self.jobs.size} jobs: [ #{self.jobs.join(' ')} ]"
|
101
|
-
end
|
102
|
-
|
103
|
-
def dump(job, name = nil, data = nil)
|
104
|
-
"#{name.inspect rescue nil} { data: #{(data.inspect rescue nil)}, job: #{(job.inspect rescue nil)} }"
|
105
|
-
end
|
106
|
-
|
107
|
-
def prefix
|
108
|
-
"[#{number_format % number} - #{'%x' % Thread.current.object_id}]"
|
109
|
-
end
|
110
|
-
|
111
|
-
def number_format
|
112
|
-
"%#{Creeper.pool_size.to_s.length}d"
|
113
|
-
end
|
114
|
-
|
115
|
-
def time_in_milliseconds
|
116
|
-
((stopped_at - started_at).to_f * 1000).to_i
|
117
|
-
end
|
118
|
-
|
119
|
-
def started_at
|
120
|
-
Thread.current[:creeper_started_at]
|
121
|
-
end
|
122
|
-
|
123
|
-
def started_at=(started_at)
|
124
|
-
Thread.current[:creeper_started_at] = started_at
|
125
|
-
end
|
126
|
-
|
127
|
-
def stopped_at
|
128
|
-
Thread.current[:creeper_stopped_at]
|
129
|
-
end
|
130
|
-
|
131
|
-
def stopped_at=(stopped_at)
|
132
|
-
Thread.current[:creeper_stopped_at] = stopped_at
|
133
|
-
end
|
134
|
-
|
135
|
-
## beanstalk ##
|
136
|
-
|
137
|
-
def beanstalk
|
138
|
-
Creeper.beanstalk
|
139
|
-
end
|
140
|
-
|
141
|
-
def ignore(tube)
|
142
|
-
beanstalk.ignore(tube)
|
143
|
-
rescue Beanstalk::NotConnected => e
|
144
|
-
disconnected(self, :ignore, tube) || raise
|
145
|
-
end
|
146
|
-
|
147
|
-
def list_tubes_watched(cached = false)
|
148
|
-
beanstalk.list_tubes_watched
|
149
|
-
rescue Beanstalk::NotConnected => e
|
150
|
-
disconnected(self, :list_tubes_watched, cached) || raise
|
151
|
-
end
|
152
|
-
|
153
|
-
def reserve(timeout = nil)
|
154
|
-
beanstalk.reserve(timeout)
|
155
|
-
rescue Beanstalk::NotConnected => e
|
156
|
-
disconnected(self, :reserve, timeout) || raise
|
157
|
-
end
|
158
|
-
|
159
|
-
def watch(tube)
|
160
|
-
beanstalk.watch(tube)
|
161
|
-
rescue Beanstalk::NotConnected => e
|
162
|
-
disconnected(self, :watch, tube) || raise
|
163
|
-
end
|
164
|
-
|
165
|
-
##
|
166
|
-
|
167
|
-
## work ##
|
168
|
-
|
169
|
-
def start(short_circuit = false)
|
170
|
-
return false if short_circuit
|
171
|
-
exit if stopped?
|
172
|
-
return true if working?
|
173
|
-
prepare if not prepared?
|
174
|
-
|
175
|
-
begin
|
176
|
-
job = reserve Creeper.reserve_timeout
|
177
|
-
rescue Beanstalk::TimedOut
|
178
|
-
OutLogger.debug "#{prefix} Back to the unemployment line" if $DEBUG
|
179
|
-
return false
|
6
|
+
##
|
7
|
+
# Include this module in your worker class and you can easily create
|
8
|
+
# asynchronous jobs:
|
9
|
+
#
|
10
|
+
# class HardWorker
|
11
|
+
# include Creeper::Worker
|
12
|
+
#
|
13
|
+
# def perform(*args)
|
14
|
+
# # do some work
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Then in your Rails app, you can do this:
|
19
|
+
#
|
20
|
+
# HardWorker.perform_async(1, 2, 3)
|
21
|
+
#
|
22
|
+
# Note that perform_async is a class method, perform is an instance method.
|
23
|
+
module Worker
|
24
|
+
def self.included(base)
|
25
|
+
base.extend(ClassMethods)
|
26
|
+
base.class_attribute :creeper_options_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
Creeper.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
|
35
|
+
include Creeper::Legacy::WorkerMethods
|
36
|
+
|
37
|
+
def perform_async(*args)
|
38
|
+
client_push('class' => self, 'args' => args)
|
180
39
|
end
|
181
40
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
OutLogger.debug "#{prefix} Got #{job.inspect}" if $DEBUG
|
187
|
-
|
188
|
-
work! job # asynchronously go to work
|
189
|
-
rescue SystemExit => e
|
190
|
-
job.release rescue nil
|
191
|
-
Creeper.unregister_worker(self)
|
192
|
-
rescue => e
|
193
|
-
job.release rescue nil
|
194
|
-
Creeper.unregister_worker(self, "start loop error")
|
195
|
-
raise
|
196
|
-
end
|
197
|
-
|
198
|
-
def finalize
|
199
|
-
Creeper.finalizers.each do |finalizer|
|
200
|
-
begin
|
201
|
-
case finalizer.arity
|
202
|
-
when 1
|
203
|
-
finalizer.call(self)
|
204
|
-
else
|
205
|
-
finalizer.call
|
206
|
-
end
|
207
|
-
rescue => e
|
208
|
-
OutLogger.crash "#{prefix} finalizer error", e
|
209
|
-
end
|
41
|
+
def perform_in(interval, *args)
|
42
|
+
int = interval.to_f
|
43
|
+
ts = (int < 1_000_000_000 ? Time.now.to_f + int : int)
|
44
|
+
client_push('class' => self, 'args' => args, 'at' => ts)
|
210
45
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
name, data = JSON.parse(job.body)
|
225
|
-
|
226
|
-
Creeper.start_work(self, data, name, job)
|
227
|
-
|
228
|
-
begin
|
229
|
-
Creeper.before_handlers_for(name).each do |handler|
|
230
|
-
process(handler, data, name, job)
|
231
|
-
end
|
232
|
-
|
233
|
-
Creeper.handler_for(name).tap do |handler|
|
234
|
-
process(handler, data, name, job)
|
235
|
-
end
|
236
|
-
|
237
|
-
Creeper.after_handlers_for(name).each do |handler|
|
238
|
-
process(handler, data, name, job)
|
239
|
-
end
|
46
|
+
alias_method :perform_at, :perform_in
|
47
|
+
|
48
|
+
##
|
49
|
+
# Allows customization for this type of Worker.
|
50
|
+
# Legal options:
|
51
|
+
#
|
52
|
+
# :queue - use a named queue for this Worker, default 'default'
|
53
|
+
# :retry - enable the RetryJobs middleware for this Worker, default *true*
|
54
|
+
# :timeout - timeout the perform method after N seconds, default *nil*
|
55
|
+
# :backtrace - whether to save any error backtrace in the retry payload to display in web UI,
|
56
|
+
# can be true, false or an integer number of lines to save, default *false*
|
57
|
+
def creeper_options(opts={})
|
58
|
+
self.creeper_options_hash = get_creeper_options.merge(stringify_keys(opts || {}))
|
240
59
|
end
|
241
60
|
|
242
|
-
|
61
|
+
DEFAULT_OPTIONS = { 'retry' => true, 'queue' => 'default' }
|
243
62
|
|
244
|
-
|
245
|
-
|
246
|
-
# start! unless stopped? or EM.reactor_running?
|
247
|
-
start! unless stopped? # continue processing, even when end of links is reached
|
248
|
-
|
249
|
-
rescue Beanstalk::NotConnected => e
|
250
|
-
disconnected(self, :work, job) || begin
|
251
|
-
job.release rescue nil
|
252
|
-
Creeper.unregister_worker(self)
|
253
|
-
raise
|
63
|
+
def get_creeper_options # :nodoc:
|
64
|
+
self.creeper_options_hash ||= DEFAULT_OPTIONS
|
254
65
|
end
|
255
|
-
rescue SystemExit => e
|
256
|
-
job.release rescue nil
|
257
|
-
Creeper.unregister_worker(self)
|
258
|
-
rescue => e
|
259
|
-
|
260
|
-
job.bury rescue nil
|
261
|
-
|
262
|
-
Creeper.error_work(self, data, name, job)
|
263
66
|
|
264
|
-
|
265
|
-
|
266
|
-
|
67
|
+
def stringify_keys(hash) # :nodoc:
|
68
|
+
hash.keys.each do |key|
|
69
|
+
hash[key.to_s] = hash.delete(key)
|
267
70
|
end
|
71
|
+
hash
|
268
72
|
end
|
269
73
|
|
270
|
-
|
271
|
-
|
272
|
-
raise
|
273
|
-
ensure
|
274
|
-
Thread.current[:creeper_started_at] = nil
|
275
|
-
Thread.current[:creeper_stopped_at] = nil
|
276
|
-
Thread.current[:creeper_working] = false
|
277
|
-
end
|
278
|
-
|
279
|
-
def process(handler, data, name, job)
|
280
|
-
case handler.arity
|
281
|
-
when 3
|
282
|
-
handler.call(data, name, job)
|
283
|
-
when 2
|
284
|
-
handler.call(data, name)
|
285
|
-
when 1
|
286
|
-
handler.call(data)
|
287
|
-
else
|
288
|
-
handler.call
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
##
|
293
|
-
|
294
|
-
## flags ##
|
295
|
-
|
296
|
-
def prepared?
|
297
|
-
Thread.current[:creeper_prepared] == true
|
298
|
-
end
|
299
|
-
|
300
|
-
def stopped?
|
301
|
-
Thread.current[:creeper_stopped] == true || Creeper.shutdown?
|
302
|
-
end
|
303
|
-
|
304
|
-
def working?
|
305
|
-
Thread.current[:creeper_working] == true
|
306
|
-
end
|
307
|
-
|
308
|
-
##
|
309
|
-
|
310
|
-
protected
|
311
|
-
|
312
|
-
def disconnected(target, method, *args, &block)
|
313
|
-
Creeper.send(:disconnected, target, method, *args, &block)
|
314
|
-
end
|
315
|
-
|
316
|
-
def prepare
|
317
|
-
jobs.each do |job|
|
318
|
-
watch(job)
|
319
|
-
end
|
320
|
-
|
321
|
-
list_tubes_watched.each do |server, tubes|
|
322
|
-
tubes.each do |tube|
|
323
|
-
ignore(tube) unless jobs.include?(tube)
|
324
|
-
end
|
74
|
+
def client_push(*args) # :nodoc:
|
75
|
+
Creeper::Client.push(*args)
|
325
76
|
end
|
326
77
|
|
327
|
-
Thread.current[:creeper_prepared] = true
|
328
78
|
end
|
329
|
-
|
330
79
|
end
|
331
|
-
end
|
80
|
+
end
|