creeper 1.0.9 → 2.0.0

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