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/.gitignore
CHANGED
data/.rvmrc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
+
environment_id="ruby-1.9.3-p194@creeper"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.15.0 (master)" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
else
|
29
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
+
rvm --create "$environment_id" || {
|
31
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
+
return 1
|
33
|
+
}
|
34
|
+
fi
|
35
|
+
|
36
|
+
# If you use bundler, this might be useful to you:
|
37
|
+
# if [[ -s Gemfile ]] && {
|
38
|
+
# ! builtin command -v bundle >/dev/null ||
|
39
|
+
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
|
40
|
+
# }
|
41
|
+
# then
|
42
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
+
# gem install bundler
|
44
|
+
# fi
|
45
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
+
# then
|
47
|
+
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
|
48
|
+
# fi
|
data/Gemfile
CHANGED
@@ -3,4 +3,20 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in creeper.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem '
|
6
|
+
gem 'rails', '3.2.8'
|
7
|
+
|
8
|
+
gem 'slim'
|
9
|
+
gem 'sprockets'
|
10
|
+
gem 'sass'
|
11
|
+
|
12
|
+
group :test do
|
13
|
+
## guard ##
|
14
|
+
gem 'coolline'
|
15
|
+
gem 'guard'
|
16
|
+
gem 'guard-rspec'
|
17
|
+
gem 'guard-spork'
|
18
|
+
|
19
|
+
gem 'growl', require: !!(RUBY_PLATFORM =~ /darwin/i) ? 'growl' : false
|
20
|
+
gem 'libnotify', require: !!(RUBY_PLATFORM =~ /linux/i) ? 'libnotify' : false
|
21
|
+
gem 'terminal-notifier', require: !!(RUBY_PLATFORM =~ /darwin/i) ? 'growl' : false
|
22
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'spork', cucumber: false, test_unit: false, rspec: true, bundler: false, wait: 15, rspec_env: { 'RAILS_ENV' => 'test' } do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch('Gemfile.lock')
|
7
|
+
watch('spec/spec_helper.rb') { :rspec }
|
8
|
+
watch('test/test_helper.rb') { :test_unit }
|
9
|
+
watch(%r{features/support/}) { :cucumber }
|
10
|
+
watch(%r{^spec/support/(.+)\.rb$}) { :rspec }
|
11
|
+
end
|
12
|
+
|
13
|
+
guard 'rspec', version: 2, bundler: false, all_after_pass: !ENV['ALL'].nil?, all_on_start: !ENV['ALL'].nil?, cli: '--drb --colour --format nested' do
|
14
|
+
watch(%r{^spec/.+_spec\.rb$})
|
15
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
16
|
+
watch('spec/spec_helper.rb') { "spec" }
|
17
|
+
|
18
|
+
# Rails example
|
19
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
20
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
21
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
22
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
23
|
+
watch('config/routes.rb') { "spec/routing" }
|
24
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
25
|
+
|
26
|
+
# Capybara request specs
|
27
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
28
|
+
|
29
|
+
# Turnip features and steps
|
30
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
31
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
32
|
+
end
|
data/Rakefile
CHANGED
data/bin/creeper
CHANGED
@@ -1,62 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
opts.separator "Ruby options:"
|
15
|
-
|
16
|
-
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
|
17
|
-
$DEBUG = true
|
18
|
-
end
|
19
|
-
|
20
|
-
opts.separator "#{cmd} options:"
|
21
|
-
|
22
|
-
opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
|
23
|
-
options[:daemonize] = !!d
|
24
|
-
end
|
25
|
-
|
26
|
-
opts.on("-j", "--job-file FILE", "Creeper-specific job file") do |f|
|
27
|
-
options[:job_file] = f
|
28
|
-
end
|
29
|
-
|
30
|
-
opts.on("-r", "--runner-count NUMBER", "Threads to run (default: #{options[:runner_count]})") do |r|
|
31
|
-
options[:runner_count] = (r.to_i rescue 1)
|
32
|
-
end
|
33
|
-
|
34
|
-
opts.separator "Common options:"
|
35
|
-
|
36
|
-
opts.on_tail("-h", "--help", "Show this message") do
|
37
|
-
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
38
|
-
exit
|
39
|
-
end
|
40
|
-
|
41
|
-
opts.on_tail("-v", "--version", "Show version") do
|
42
|
-
puts "#{cmd} v#{Creeper::VERSION}"
|
43
|
-
exit
|
44
|
-
end
|
45
|
-
|
46
|
-
opts.parse! ARGV
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
unless options[:job_file]
|
51
|
-
$stderr.puts "ERROR: job file required", ''
|
52
|
-
puts op.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
3
|
+
require_relative '../lib/creeper/cli'
|
4
|
+
|
5
|
+
begin
|
6
|
+
cli = Creeper::CLI.instance
|
7
|
+
cli.parse
|
8
|
+
cli.run
|
9
|
+
rescue => e
|
10
|
+
raise e if $DEBUG
|
11
|
+
STDERR.puts e.message
|
12
|
+
STDERR.puts e.backtrace.join("\n")
|
53
13
|
exit 1
|
54
14
|
end
|
55
|
-
|
56
|
-
if $DEBUG
|
57
|
-
require 'pp'
|
58
|
-
pp(options)
|
59
|
-
end
|
60
|
-
|
61
|
-
Creeper::Launcher.launch!(options) if options[:daemonize]
|
62
|
-
Creeper.new(options).start.join
|
data/bin/creeperctl
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class Creeperctl
|
6
|
+
|
7
|
+
attr_reader :stage, :pidfile, :timeout
|
8
|
+
|
9
|
+
def initialize(stage, pidfile, timeout)
|
10
|
+
@stage = stage
|
11
|
+
@pidfile = pidfile
|
12
|
+
@timeout = timeout
|
13
|
+
|
14
|
+
done 'No pidfile given' if !pidfile
|
15
|
+
done "Pidfile #{pidfile} does not exist" if !File.exist?(pidfile)
|
16
|
+
done 'Invalid pidfile content' if pid == 0
|
17
|
+
|
18
|
+
fetch_process
|
19
|
+
|
20
|
+
begin
|
21
|
+
send(stage)
|
22
|
+
rescue NoMethodError
|
23
|
+
done 'Invalid control command'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_process
|
28
|
+
Process.getpgid(pid)
|
29
|
+
rescue Errno::ESRCH
|
30
|
+
done "Process doesn't exist"
|
31
|
+
end
|
32
|
+
|
33
|
+
def done(msg)
|
34
|
+
puts msg
|
35
|
+
exit(0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def pid
|
39
|
+
File.read(pidfile).to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def quiet
|
43
|
+
`kill -USR1 #{pid}`
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop
|
47
|
+
`kill -TERM #{pid}`
|
48
|
+
timeout.times do
|
49
|
+
begin
|
50
|
+
Process.getpgid(pid)
|
51
|
+
rescue Errno::ESRCH
|
52
|
+
FileUtils.rm_f pidfile
|
53
|
+
done 'Creeper shut down gracefully.'
|
54
|
+
end
|
55
|
+
sleep 1
|
56
|
+
end
|
57
|
+
`kill -9 #{pid}`
|
58
|
+
FileUtils.rm_f pidfile
|
59
|
+
done 'Creeper shut down forcefully.'
|
60
|
+
end
|
61
|
+
|
62
|
+
def shutdown
|
63
|
+
quiet
|
64
|
+
stop
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
stage = ARGV[0]
|
70
|
+
pidfile = ARGV[1]
|
71
|
+
timeout = ARGV[2].to_i
|
72
|
+
timeout = 10 if timeout == 0
|
73
|
+
|
74
|
+
Creeperctl.new(stage, pidfile, timeout)
|
data/config.ru
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'creeper'
|
2
|
+
|
3
|
+
Creeper.configure_client do |config|
|
4
|
+
config.redis = { :size => 1 }
|
5
|
+
end
|
6
|
+
|
7
|
+
#Creeper.redis {|conn| conn.flushdb }
|
8
|
+
#10.times do |idx|
|
9
|
+
#Creeper::Client.push('class' => 'HardWorker', 'args' => ['foo', 0.1, idx])
|
10
|
+
#end
|
11
|
+
|
12
|
+
#Creeper.redis { |conn| conn.zadd('retry', Time.now.utc.to_f + 3000, MultiJson.encode({
|
13
|
+
#'class' => 'HardWorker', 'args' => ['foo', 0.1, Time.now.to_f],
|
14
|
+
#'queue' => 'default', 'error_message' => 'No such method', 'error_class' => 'NoMethodError',
|
15
|
+
#'failed_at' => Time.now.utc, 'retry_count' => 0 })) }
|
16
|
+
|
17
|
+
require 'creeper/web'
|
18
|
+
run Creeper::Web
|
data/creeper.gemspec
CHANGED
@@ -1,25 +1,35 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'creeper/version'
|
3
5
|
|
4
6
|
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "creeper"
|
8
|
+
gem.version = Creeper::VERSION
|
5
9
|
gem.authors = ["Lyon Hill", "Andrew Bennett"]
|
6
10
|
gem.email = ["lyondhill@gmail.com", "potatosaladx@gmail.com"]
|
7
11
|
gem.description = %q{Creeper is an evented version of Stalker}
|
8
12
|
gem.summary = %q{A better solution for io bound jobs, same as stalker in functionality but more evented}
|
9
13
|
gem.homepage = "https://github.com/potatosalad/creeper"
|
10
14
|
|
11
|
-
gem.files = `git ls-files`.split(
|
12
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + ['creeperctl']
|
13
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
-
gem.name = "creeper"
|
15
18
|
gem.require_paths = ["lib"]
|
16
|
-
|
19
|
+
|
20
|
+
gem.add_dependency 'redis', '~> 3'
|
21
|
+
gem.add_dependency 'redis-namespace'
|
22
|
+
gem.add_dependency 'beanstalk-client'
|
23
|
+
gem.add_dependency 'connection_pool', '~> 0.9.2'
|
24
|
+
gem.add_dependency 'celluloid', '~> 0.11.1'
|
25
|
+
gem.add_dependency 'kgio'
|
26
|
+
gem.add_dependency 'multi_json', '~> 1'
|
17
27
|
|
18
28
|
gem.add_development_dependency 'pry'
|
19
29
|
gem.add_development_dependency 'rake'
|
20
30
|
gem.add_development_dependency 'rspec'
|
21
|
-
|
22
|
-
gem.
|
23
|
-
gem.
|
24
|
-
gem.
|
31
|
+
gem.add_development_dependency 'sinatra'
|
32
|
+
gem.add_development_dependency 'slim'
|
33
|
+
gem.add_development_dependency 'actionmailer', '~> 3'
|
34
|
+
gem.add_development_dependency 'activerecord', '~> 3'
|
25
35
|
end
|
data/lib/creeper.rb
CHANGED
@@ -1,443 +1,138 @@
|
|
1
|
-
require 'beanstalk-client'
|
2
|
-
require 'json'
|
3
|
-
require 'logger'
|
4
|
-
require 'thread'
|
5
|
-
require 'timeout'
|
6
|
-
require 'uri'
|
7
|
-
|
8
1
|
require 'creeper/version'
|
2
|
+
require 'creeper/logging'
|
3
|
+
require 'creeper/client'
|
4
|
+
require 'creeper/worker'
|
5
|
+
require 'creeper/beanstalk_connection'
|
6
|
+
require 'creeper/redis_connection'
|
7
|
+
require 'creeper/util'
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
class BadURL < RuntimeError; end
|
13
|
-
|
14
|
-
HANDLERS = {
|
15
|
-
named: {},
|
16
|
-
before_each: [],
|
17
|
-
before_named: {},
|
18
|
-
after_each: [],
|
19
|
-
after_named: {},
|
20
|
-
error_each: [],
|
21
|
-
error_named: {},
|
22
|
-
finalizers: []
|
23
|
-
}
|
24
|
-
|
25
|
-
WORKERS = {}
|
26
|
-
|
27
|
-
## default configuration ##
|
28
|
-
|
29
|
-
@patience_soft = 60
|
30
|
-
@patience_hard = 30
|
31
|
-
@pool_size = 2
|
32
|
-
@retry_count = 3
|
33
|
-
@reserve_timeout = 1
|
34
|
-
|
35
|
-
@lock = Mutex.new
|
36
|
-
|
37
|
-
##
|
38
|
-
|
39
|
-
class << self
|
40
|
-
|
41
|
-
## configuration ##
|
42
|
-
|
43
|
-
attr_reader :lock
|
44
|
-
attr_accessor :patience_soft, :patience_hard, :pool_size, :reserve_timeout, :retry_count
|
45
|
-
|
46
|
-
def beanstalk_url
|
47
|
-
lock.synchronize do
|
48
|
-
@beanstalk_url ||= ENV['BEANSTALK_URL'] || 'beanstalk://127.0.0.1/'
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def beanstalk_url=(beanstalk_url)
|
53
|
-
lock.synchronize do
|
54
|
-
@beanstalk_url = beanstalk_url
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def err_logger
|
59
|
-
lock.synchronize do
|
60
|
-
@err_logger ||= ::Logger.new($stderr)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def err_logger=(err_logger)
|
65
|
-
lock.synchronize do
|
66
|
-
@err_logger = err_logger
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def out_logger
|
71
|
-
lock.synchronize do
|
72
|
-
@out_logger ||= ::Logger.new($stdout)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def out_logger=(out_logger)
|
77
|
-
lock.synchronize do
|
78
|
-
@out_logger = out_logger
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def worker_pool
|
83
|
-
lock.synchronize do
|
84
|
-
@worker_pool
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def worker_pool=(worker_pool)
|
89
|
-
lock.synchronize do
|
90
|
-
@worker_pool = worker_pool
|
91
|
-
end
|
92
|
-
end
|
9
|
+
require 'creeper/legacy'
|
93
10
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|
11
|
+
require 'creeper/extensions/action_mailer'
|
12
|
+
require 'creeper/extensions/active_record'
|
13
|
+
require 'creeper/rails' if defined?(::Rails::Engine)
|
99
14
|
|
100
|
-
|
101
|
-
lock.synchronize do
|
102
|
-
@shutdown = shutdown
|
103
|
-
end
|
104
|
-
end
|
15
|
+
require 'multi_json'
|
105
16
|
|
106
|
-
|
107
|
-
|
108
|
-
## connection ##
|
109
|
-
|
110
|
-
def beanstalk
|
111
|
-
Thread.current[:beanstalk_pool_connection] ||= connect
|
112
|
-
end
|
113
|
-
|
114
|
-
def beanstalk_addresses
|
115
|
-
uris = beanstalk_url.split(/[\s,]+/)
|
116
|
-
uris.map do |uri|
|
117
|
-
beanstalk_host_and_port(uri)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def connect(addresses = nil)
|
122
|
-
Beanstalk::Pool.new(addresses || beanstalk_addresses)
|
123
|
-
end
|
124
|
-
|
125
|
-
def disconnect
|
126
|
-
Thread.current[:beanstalk_pool_connection].close rescue nil
|
127
|
-
Thread.current[:beanstalk_pool_connection] = nil
|
128
|
-
end
|
129
|
-
|
130
|
-
##
|
131
|
-
|
132
|
-
## daemon ##
|
133
|
-
|
134
|
-
def work(jobs = nil, size = nil)
|
135
|
-
require 'creeper/worker'
|
136
|
-
|
137
|
-
Creeper.pool_size = size || Creeper.pool_size
|
138
|
-
|
139
|
-
Creeper::Worker.work(jobs, Creeper.pool_size)
|
140
|
-
end
|
141
|
-
|
142
|
-
##
|
143
|
-
|
144
|
-
## handlers ##
|
145
|
-
|
146
|
-
def all_jobs
|
147
|
-
lock.synchronize do
|
148
|
-
HANDLERS[:named].keys
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def job(name, &block)
|
153
|
-
lock.synchronize do
|
154
|
-
HANDLERS[:named][name] = block
|
155
|
-
HANDLERS[:before_named][name] ||= []
|
156
|
-
HANDLERS[:after_named][name] ||= []
|
157
|
-
HANDLERS[:error_named][name] ||= []
|
158
|
-
HANDLERS[:named][name]
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def drop(name)
|
163
|
-
lock.synchronize do
|
164
|
-
HANDLERS[:named].delete(name)
|
165
|
-
HANDLERS[:before_named].delete(name)
|
166
|
-
HANDLERS[:after_named].delete(name)
|
167
|
-
HANDLERS[:error_named].delete(name)
|
168
|
-
true
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def handler_for(name)
|
173
|
-
lock.synchronize do
|
174
|
-
HANDLERS[:named][name]
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def before(name = nil, &block)
|
179
|
-
if name and name != :each
|
180
|
-
lock.synchronize do
|
181
|
-
HANDLERS[:before_named][name] << block
|
182
|
-
end
|
183
|
-
else
|
184
|
-
lock.synchronize do
|
185
|
-
HANDLERS[:before_each] << block
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def before_handlers_for(name)
|
191
|
-
lock.synchronize do
|
192
|
-
HANDLERS[:before_each] + HANDLERS[:before_named][name]
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
def after(name = nil, &block)
|
197
|
-
if name and name != :each
|
198
|
-
lock.synchronize do
|
199
|
-
HANDLERS[:after_named][name] << block
|
200
|
-
end
|
201
|
-
else
|
202
|
-
lock.synchronize do
|
203
|
-
HANDLERS[:after_each] << block
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def after_handlers_for(name)
|
209
|
-
lock.synchronize do
|
210
|
-
HANDLERS[:after_each] + HANDLERS[:after_named][name]
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def error(name = nil, &block)
|
215
|
-
if name and name != :each
|
216
|
-
lock.synchronize do
|
217
|
-
HANDLERS[:error_named][name] << block
|
218
|
-
end
|
219
|
-
else
|
220
|
-
lock.synchronize do
|
221
|
-
HANDLERS[:error_each] << block
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def error_handlers_for(name)
|
227
|
-
lock.synchronize do
|
228
|
-
HANDLERS[:error_each] + HANDLERS[:error_named][name]
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def finalizer(&block)
|
233
|
-
lock.synchronize do
|
234
|
-
HANDLERS[:finalizers] << block
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def finalizers
|
239
|
-
lock.synchronize do
|
240
|
-
HANDLERS[:finalizers]
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
##
|
245
|
-
|
246
|
-
## queue ##
|
247
|
-
|
248
|
-
def enqueue(job, data = {}, options = {})
|
249
|
-
# OutLogger.debug "#{Thread.current[:actor].inspect} Enqueueing #{job.inspect}, #{data.inspect}"#\n#{Celluloid::Actor.all.pretty_inspect}"
|
250
|
-
OutLogger.debug "[#{Thread.current[:actor] ? Thread.current[:actor].subject.number : nil}] Enqueueing #{job.inspect}, #{data.inspect}" if $DEBUG
|
251
|
-
enqueue!(job, data, options)
|
252
|
-
rescue Beanstalk::NotConnected => e
|
253
|
-
disconnected(self, :enqueue, job, data, options)
|
254
|
-
end
|
255
|
-
|
256
|
-
def enqueue!(job, data = {}, options = {})
|
257
|
-
priority = options[:priority] || options[:pri] || 65536
|
258
|
-
delay = [ 0, options[:delay].to_i ].max
|
259
|
-
time_to_run = options[:time_to_run] || options[:ttr] || 120
|
260
|
-
|
261
|
-
beanstalk.use job
|
262
|
-
beanstalk.put JSON.dump([ job, data ]), priority, delay, time_to_run
|
263
|
-
end
|
264
|
-
|
265
|
-
##
|
266
|
-
|
267
|
-
## workers ##
|
268
|
-
|
269
|
-
def error_work(worker, data, name, job)
|
270
|
-
(worker.stopped_at = Time.now).tap do |stopped_at|
|
271
|
-
error_message = "#{worker.prefix} Error after #{worker.time_in_milliseconds}ms #{worker.dump(job, name, data)}"
|
272
|
-
OutLogger.error error_message
|
273
|
-
end
|
274
|
-
end
|
17
|
+
module Creeper
|
275
18
|
|
276
|
-
|
277
|
-
lock.synchronize do
|
278
|
-
number = ((0..(WORKERS.keys.max || 0)+1).to_a - WORKERS.keys).first
|
279
|
-
WORKERS[number] = worker.tap do
|
280
|
-
worker.number = number
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
19
|
+
extend Creeper::Legacy::ClassMethods
|
284
20
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
21
|
+
DEFAULTS = {
|
22
|
+
:queues => [],
|
23
|
+
:concurrency => 25,
|
24
|
+
:require => '.',
|
25
|
+
:environment => nil,
|
26
|
+
:timeout => 8,
|
27
|
+
:enable_rails_extensions => true,
|
28
|
+
}
|
296
29
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
end
|
301
|
-
end
|
30
|
+
def self.options
|
31
|
+
@options ||= DEFAULTS.dup
|
32
|
+
end
|
302
33
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
end
|
307
|
-
end
|
34
|
+
def self.options=(opts)
|
35
|
+
@options = opts
|
36
|
+
end
|
308
37
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
38
|
+
##
|
39
|
+
# Configuration for Creeper server, use like:
|
40
|
+
#
|
41
|
+
# Creeper.configure_server do |config|
|
42
|
+
# config.redis = { :namespace => 'myapp', :size => 25, :url => 'redis://myhost:8877/mydb' }
|
43
|
+
# config.server_middleware do |chain|
|
44
|
+
# chain.add MyServerHook
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
def self.configure_server
|
48
|
+
yield self if server?
|
49
|
+
end
|
316
50
|
|
317
|
-
|
51
|
+
##
|
52
|
+
# Configuration for Creeper client, use like:
|
53
|
+
#
|
54
|
+
# Creeper.configure_client do |config|
|
55
|
+
# config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/mydb' }
|
56
|
+
# end
|
57
|
+
def self.configure_client
|
58
|
+
yield self unless server?
|
59
|
+
end
|
318
60
|
|
319
|
-
|
61
|
+
def self.server?
|
62
|
+
defined?(Creeper::CLI)
|
63
|
+
end
|
320
64
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
65
|
+
def self.beanstalk(&block)
|
66
|
+
if block_given?
|
67
|
+
yield beanstalk
|
68
|
+
else
|
69
|
+
@beanstalk ||= Creeper::BeanstalkConnection.create
|
325
70
|
end
|
71
|
+
end
|
326
72
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
if Thread.current[:beanstalk_connection_retries] >= retry_count
|
331
|
-
OutLogger.error "Unable to connect to beanstalk after #{Thread.current[:beanstalk_connection_retries]} attempts"
|
332
|
-
Thread.current[:beanstalk_connection_retries] = 0
|
333
|
-
return false
|
334
|
-
end
|
335
|
-
|
336
|
-
disconnect
|
337
|
-
|
338
|
-
Thread.current[:beanstalk_connection_retries] += 1
|
339
|
-
|
340
|
-
sleep Thread.current[:beanstalk_connection_retries] * 2
|
341
|
-
|
342
|
-
target.send(method, *args, &block)
|
73
|
+
def self.beanstalk=(hash)
|
74
|
+
if @beanstalk
|
75
|
+
@beanstalk.close rescue nil
|
343
76
|
end
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
# Attempt to shut down the supervision tree, if available
|
351
|
-
Celluloid::Supervisor.root.terminate if Celluloid::Supervisor.root
|
352
|
-
|
353
|
-
# Actors cannot self-terminate, you must do it for them
|
354
|
-
starts = working_actors.map do |actor|
|
355
|
-
begin
|
356
|
-
if actor.alive?
|
357
|
-
actor.stop! # fire and forget for those already working
|
358
|
-
actor.future(:start, true) # ensures that the mailbox is cleared out
|
359
|
-
end
|
360
|
-
rescue Celluloid::DeadActorError, Celluloid::MailboxError
|
361
|
-
end
|
362
|
-
end.compact
|
363
|
-
|
364
|
-
starts.each do |start|
|
365
|
-
begin
|
366
|
-
start.value
|
367
|
-
rescue Celluloid::DeadActorError, Celluloid::MailboxError
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
|
-
OutLogger.info "Graceful stop completed cleanly"
|
372
|
-
end
|
77
|
+
if hash.is_a?(Hash)
|
78
|
+
@beanstalk = BeanstalkConnection.create(hash)
|
79
|
+
elsif hash.is_a?(Beanstalk::Pool)
|
80
|
+
@beanstalk = hash
|
81
|
+
else
|
82
|
+
raise ArgumentError, "beanstalk= requires a Hash or Beanstalk::Pool"
|
373
83
|
end
|
84
|
+
end
|
374
85
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
# Attempt to shut down the supervision tree, if available
|
381
|
-
Celluloid::Supervisor.root.terminate if Celluloid::Supervisor.root
|
382
|
-
|
383
|
-
pool_managers.each do |pool_manager|
|
384
|
-
begin
|
385
|
-
pool_manager.terminate
|
386
|
-
rescue Celluloid::DeadActorError, Celluloid::MailboxError
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
# Actors cannot self-terminate, you must do it for them
|
391
|
-
working_actors.each do |actor|
|
392
|
-
begin
|
393
|
-
actor.terminate
|
394
|
-
rescue Celluloid::DeadActorError, Celluloid::MailboxError
|
395
|
-
end
|
396
|
-
end
|
86
|
+
def self.redis(&block)
|
87
|
+
@redis ||= Creeper::RedisConnection.create
|
88
|
+
raise ArgumentError, "requires a block" if !block
|
89
|
+
@redis.with(&block)
|
90
|
+
end
|
397
91
|
|
398
|
-
|
399
|
-
|
92
|
+
def self.redis=(hash)
|
93
|
+
if hash.is_a?(Hash)
|
94
|
+
@redis = RedisConnection.create(hash)
|
95
|
+
elsif hash.is_a?(ConnectionPool)
|
96
|
+
@redis = hash
|
97
|
+
else
|
98
|
+
raise ArgumentError, "redis= requires a Hash or ConnectionPool"
|
400
99
|
end
|
100
|
+
end
|
401
101
|
|
402
|
-
|
403
|
-
|
404
|
-
|
102
|
+
def self.client_middleware
|
103
|
+
@client_chain ||= Client.default_middleware
|
104
|
+
yield @client_chain if block_given?
|
105
|
+
@client_chain
|
106
|
+
end
|
405
107
|
|
406
|
-
|
407
|
-
|
108
|
+
def self.server_middleware
|
109
|
+
@server_chain ||= Processor.default_middleware
|
110
|
+
yield @server_chain if block_given?
|
111
|
+
@server_chain
|
112
|
+
end
|
408
113
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
actor.kill
|
413
|
-
actor.join
|
414
|
-
rescue Celluloid::DeadActorError, Celluloid::MailboxError
|
415
|
-
end
|
416
|
-
end
|
114
|
+
def self.server?
|
115
|
+
defined?(Creeper::CLI)
|
116
|
+
end
|
417
117
|
|
418
|
-
|
419
|
-
|
118
|
+
def self.load_json(string)
|
119
|
+
MultiJson.decode(string)
|
120
|
+
end
|
420
121
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
actor.is_a?(Celluloid::PoolManager) rescue false
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|
122
|
+
def self.dump_json(object)
|
123
|
+
MultiJson.encode(object)
|
124
|
+
end
|
428
125
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
actor.is_a?(Celluloid::PoolManager) rescue false
|
433
|
-
end
|
434
|
-
end
|
435
|
-
end
|
126
|
+
def self.logger
|
127
|
+
Creeper::Logging.logger
|
128
|
+
end
|
436
129
|
|
130
|
+
def self.logger=(log)
|
131
|
+
Creeper::Logging.logger = log
|
437
132
|
end
|
438
133
|
|
439
|
-
|
134
|
+
def self.poll_interval=(interval)
|
135
|
+
self.options[:poll_interval] = interval
|
136
|
+
end
|
440
137
|
|
441
|
-
|
442
|
-
require 'creeper/err_logger'
|
443
|
-
require 'creeper/out_logger'
|
138
|
+
end
|