creeper 1.0.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. data/.gitignore +1 -0
  2. data/.rvmrc +48 -0
  3. data/Gemfile +17 -1
  4. data/Guardfile +32 -0
  5. data/Rakefile +9 -1
  6. data/bin/creeper +10 -58
  7. data/bin/creeperctl +74 -0
  8. data/config.ru +18 -0
  9. data/creeper.gemspec +19 -9
  10. data/lib/creeper.rb +108 -413
  11. data/lib/creeper/beanstalk_connection.rb +35 -0
  12. data/lib/creeper/cli.rb +225 -0
  13. data/lib/creeper/client.rb +93 -0
  14. data/lib/creeper/core_ext.rb +54 -0
  15. data/lib/creeper/exception_handler.rb +30 -0
  16. data/lib/creeper/extensions/action_mailer.rb +33 -0
  17. data/lib/creeper/extensions/active_record.rb +30 -0
  18. data/lib/creeper/extensions/generic_proxy.rb +26 -0
  19. data/lib/creeper/fetch.rb +94 -0
  20. data/lib/creeper/legacy.rb +46 -0
  21. data/lib/creeper/logging.rb +46 -0
  22. data/lib/creeper/manager.rb +164 -0
  23. data/lib/creeper/middleware/chain.rb +100 -0
  24. data/lib/creeper/middleware/server/active_record.rb +13 -0
  25. data/lib/creeper/middleware/server/logging.rb +31 -0
  26. data/lib/creeper/middleware/server/retry_jobs.rb +79 -0
  27. data/lib/creeper/middleware/server/timeout.rb +21 -0
  28. data/lib/creeper/paginator.rb +31 -0
  29. data/lib/creeper/processor.rb +116 -0
  30. data/lib/creeper/rails.rb +21 -0
  31. data/lib/creeper/redis_connection.rb +28 -0
  32. data/lib/creeper/testing.rb +44 -0
  33. data/lib/creeper/util.rb +45 -0
  34. data/lib/creeper/version.rb +1 -1
  35. data/lib/creeper/web.rb +248 -0
  36. data/lib/creeper/worker.rb +62 -313
  37. data/spec/dummy/.gitignore +15 -0
  38. data/spec/dummy/Gemfile +51 -0
  39. data/spec/dummy/README.rdoc +261 -0
  40. data/spec/dummy/Rakefile +7 -0
  41. data/spec/dummy/app/assets/images/rails.png +0 -0
  42. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  43. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  44. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  45. data/spec/dummy/app/controllers/work_controller.rb +71 -0
  46. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  47. data/spec/dummy/app/mailers/.gitkeep +0 -0
  48. data/spec/dummy/app/mailers/user_mailer.rb +9 -0
  49. data/spec/dummy/app/models/.gitkeep +0 -0
  50. data/spec/dummy/app/models/post.rb +8 -0
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/spec/dummy/app/views/user_mailer/greetings.html.erb +3 -0
  53. data/spec/dummy/app/views/work/index.html.erb +1 -0
  54. data/spec/dummy/app/workers/fast_worker.rb +10 -0
  55. data/spec/dummy/app/workers/hard_worker.rb +11 -0
  56. data/spec/dummy/app/workers/lazy_worker.rb +12 -0
  57. data/spec/dummy/app/workers/suicidal_worker.rb +33 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +68 -0
  60. data/spec/dummy/config/boot.rb +6 -0
  61. data/spec/dummy/config/creeper.yml +9 -0
  62. data/spec/dummy/config/database.yml +25 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +37 -0
  65. data/spec/dummy/config/environments/production.rb +67 -0
  66. data/spec/dummy/config/environments/test.rb +37 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/creeper.rb +8 -0
  69. data/spec/dummy/config/initializers/inflections.rb +15 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  71. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  72. data/spec/dummy/config/initializers/session_store.rb +8 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +5 -0
  75. data/spec/dummy/config/routes.rb +13 -0
  76. data/spec/dummy/db/migrate/20120123214055_create_posts.rb +10 -0
  77. data/spec/dummy/db/schema.rb +23 -0
  78. data/spec/dummy/db/seeds.rb +7 -0
  79. data/spec/dummy/lib/assets/.gitkeep +0 -0
  80. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  81. data/spec/dummy/log/.gitkeep +0 -0
  82. data/spec/dummy/public/404.html +26 -0
  83. data/spec/dummy/public/422.html +26 -0
  84. data/spec/dummy/public/500.html +25 -0
  85. data/spec/dummy/public/favicon.ico +0 -0
  86. data/spec/dummy/public/index.html +241 -0
  87. data/spec/dummy/public/robots.txt +5 -0
  88. data/spec/dummy/script/rails +6 -0
  89. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  90. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  91. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  92. data/spec/lib/creeper/cli_spec.rb +208 -0
  93. data/spec/lib/creeper/client_spec.rb +110 -0
  94. data/spec/lib/creeper/exception_handler_spec.rb +110 -0
  95. data/spec/lib/creeper/processor_spec.rb +92 -0
  96. data/spec/lib/creeper/testing_spec.rb +105 -0
  97. data/spec/lib/creeper_spec.rb +54 -120
  98. data/spec/spec_helper.rb +81 -7
  99. data/spec/support/config.yml +9 -0
  100. data/spec/support/fake_env.rb +0 -0
  101. data/spec/support/workers/base_worker.rb +11 -0
  102. data/spec/support/workers/my_worker.rb +4 -0
  103. data/spec/support/workers/queued_worker.rb +5 -0
  104. data/spec/support/workers/real_worker.rb +10 -0
  105. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  106. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  107. data/web/assets/javascripts/application.js +49 -0
  108. data/web/assets/javascripts/vendor/bootstrap.js +12 -0
  109. data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
  110. data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
  111. data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
  112. data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
  113. data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
  114. data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
  115. data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
  116. data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
  117. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
  118. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
  119. data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
  120. data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
  121. data/web/assets/javascripts/vendor/jquery.js +9266 -0
  122. data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
  123. data/web/assets/stylesheets/application.css +6 -0
  124. data/web/assets/stylesheets/layout.css +26 -0
  125. data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
  126. data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
  127. data/web/views/_paging.slim +15 -0
  128. data/web/views/_summary.slim +9 -0
  129. data/web/views/_workers.slim +14 -0
  130. data/web/views/index.slim +10 -0
  131. data/web/views/layout.slim +37 -0
  132. data/web/views/poll.slim +3 -0
  133. data/web/views/queue.slim +15 -0
  134. data/web/views/queues.slim +19 -0
  135. data/web/views/retries.slim +31 -0
  136. data/web/views/retry.slim +52 -0
  137. data/web/views/scheduled.slim +27 -0
  138. metadata +341 -23
  139. data/lib/creeper/celluloid_ext.rb +0 -42
  140. data/lib/creeper/creep.rb +0 -25
  141. data/lib/creeper/err_logger.rb +0 -37
  142. data/lib/creeper/launcher.rb +0 -44
  143. data/lib/creeper/out_logger.rb +0 -39
  144. data/spec/lib/creeper/session_spec.rb +0 -15
  145. data/spec/lib/creeper/worker_spec.rb +0 -21
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .DS_Store
5
6
  .yardoc
6
7
  Gemfile.lock
7
8
  InstalledFiles
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 'celluloid', git: 'git://github.com/celluloid/celluloid.git'
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
@@ -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
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ task default: :spec
@@ -1,62 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib')
4
-
5
- require 'creeper/launcher'
6
- require 'optparse'
7
-
8
- options = { runner_count: 1 }
9
-
10
- op = OptionParser.new("", 24, ' ') do |opts|
11
- cmd = File.basename($0)
12
- opts.banner = "Usage: #{cmd} [ruby options] [#{cmd} options]"
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
@@ -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)
@@ -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
@@ -1,25 +1,35 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/creeper/version', __FILE__)
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
- gem.version = Creeper::VERSION
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.add_dependency 'beanstalk-client'
23
- gem.add_dependency 'celluloid'
24
- gem.add_dependency 'kgio'
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
@@ -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
- module Creeper
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
- def shutdown?
95
- lock.synchronize do
96
- !!@shutdown
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
- def shutdown=(shutdown)
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
- def register_worker(worker)
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
- def shutdown_workers
286
- begin
287
- soft_shutdown_workers(Creeper.patience_soft)
288
- rescue Timeout::Error
289
- begin
290
- hard_shutdown_workers(Creeper.patience_hard)
291
- rescue Timeout::Error
292
- kill_shutdown_workers
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
- def start_work(worker, data, name, job)
298
- (worker.started_at = Time.now).tap do |started_at|
299
- OutLogger.info "#{worker.prefix} Working #{worker.dump(job, name, data)}"
300
- end
301
- end
30
+ def self.options
31
+ @options ||= DEFAULTS.dup
32
+ end
302
33
 
303
- def stop_work(worker, data, name, job)
304
- (worker.stopped_at = Time.now).tap do |stopped_at|
305
- OutLogger.info "#{worker.prefix} Finished in #{worker.time_in_milliseconds}ms #{worker.dump(job, name, data)}"
306
- end
307
- end
34
+ def self.options=(opts)
35
+ @options = opts
36
+ end
308
37
 
309
- def unregister_worker(worker, reason = nil)
310
- reason ||= 'Stopping'
311
- OutLogger.info "#{worker.prefix} #{reason}"
312
- lock.synchronize do
313
- WORKERS.delete(worker.number)
314
- end
315
- end
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
- protected
61
+ def self.server?
62
+ defined?(Creeper::CLI)
63
+ end
320
64
 
321
- def beanstalk_host_and_port(uri_string)
322
- uri = URI.parse(uri_string)
323
- raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
324
- "#{uri.host}:#{uri.port || 11300}"
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
- def disconnected(target, method, *args, &block)
328
- Thread.current[:beanstalk_connection_retries] ||= 0
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
- def soft_shutdown_workers(timeout)
346
- Timeout.timeout(timeout) do
347
- actors = Celluloid::Actor.all
348
- OutLogger.info "Gracefully stopping #{actors.size} actors..." if actors.size > 0
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
- def hard_shutdown_workers(timeout)
376
- Timeout.timeout(timeout) do
377
- actors = Celluloid::Actor.all
378
- OutLogger.info "Terminating #{actors.size} actors..." if actors.size > 0
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
- OutLogger.info "Termination completed cleanly"
399
- end
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
- def kill_shutdown_workers
403
- actors = Celluloid::Actor.all
404
- OutLogger.info "Killing #{actors.size} actors..." if actors.size > 0
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
- # Attempt to shut down the supervision tree, if available
407
- Celluloid::Supervisor.root.kill if Celluloid::Supervisor.root
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
- # Actors cannot self-terminate, you must do it for them
410
- Celluloid::Actor.all.each do |actor|
411
- begin
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
- OutLogger.info "Killing completed cleanly"
419
- end
118
+ def self.load_json(string)
119
+ MultiJson.decode(string)
120
+ end
420
121
 
421
- def pool_managers
422
- Celluloid::Actor.all.tap do |actors|
423
- actors.keep_if do |actor|
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
- def working_actors
430
- Celluloid::Actor.all.tap do |actors|
431
- actors.delete_if do |actor|
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
- end
134
+ def self.poll_interval=(interval)
135
+ self.options[:poll_interval] = interval
136
+ end
440
137
 
441
- require 'creeper/creep'
442
- require 'creeper/err_logger'
443
- require 'creeper/out_logger'
138
+ end