resque-scheduler 2.5.5 → 3.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.

Potentially problematic release.


This version of resque-scheduler might be problematic. Click here for more details.

Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -6
  3. data/.rubocop.yml +18 -113
  4. data/.rubocop_todo.yml +29 -0
  5. data/.simplecov +3 -1
  6. data/.travis.yml +12 -4
  7. data/.vagrant-provision-as-vagrant.sh +15 -0
  8. data/.vagrant-provision.sh +23 -0
  9. data/.vagrant-skel/bash_profile +7 -0
  10. data/.vagrant-skel/bashrc +7 -0
  11. data/AUTHORS.md +5 -0
  12. data/Gemfile +1 -2
  13. data/HISTORY.md +18 -0
  14. data/README.md +39 -11
  15. data/ROADMAP.md +0 -6
  16. data/Rakefile +11 -19
  17. data/Vagrantfile +14 -0
  18. data/bin/resque-scheduler +2 -2
  19. data/examples/Rakefile +1 -1
  20. data/examples/config/initializers/resque-web.rb +2 -2
  21. data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +2 -4
  22. data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +1 -1
  23. data/examples/dynamic-scheduling/app/models/user.rb +2 -2
  24. data/examples/dynamic-scheduling/lib/tasks/resque.rake +2 -2
  25. data/lib/resque-scheduler.rb +3 -1
  26. data/lib/resque/scheduler.rb +112 -168
  27. data/lib/resque/scheduler/cli.rb +144 -0
  28. data/lib/resque/scheduler/configuration.rb +73 -0
  29. data/lib/resque/scheduler/delaying_extensions.rb +278 -0
  30. data/lib/resque/scheduler/env.rb +61 -0
  31. data/lib/resque/scheduler/extension.rb +13 -0
  32. data/lib/resque/scheduler/lock.rb +2 -1
  33. data/lib/resque/scheduler/lock/base.rb +6 -2
  34. data/lib/resque/scheduler/lock/basic.rb +4 -5
  35. data/lib/resque/scheduler/lock/resilient.rb +30 -37
  36. data/lib/resque/scheduler/locking.rb +94 -0
  37. data/lib/resque/scheduler/logger_builder.rb +72 -0
  38. data/lib/resque/scheduler/plugin.rb +31 -0
  39. data/lib/resque/scheduler/scheduling_extensions.rb +150 -0
  40. data/lib/resque/scheduler/server.rb +246 -0
  41. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +2 -1
  42. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_schedules.erb +0 -0
  43. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +0 -0
  44. data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
  45. data/lib/{resque_scheduler → resque/scheduler}/server/views/scheduler.erb +16 -1
  46. data/lib/{resque_scheduler → resque/scheduler}/server/views/search.erb +2 -1
  47. data/lib/{resque_scheduler → resque/scheduler}/server/views/search_form.erb +0 -0
  48. data/lib/resque/scheduler/signal_handling.rb +40 -0
  49. data/lib/{resque_scheduler → resque/scheduler}/tasks.rb +3 -5
  50. data/lib/resque/scheduler/util.rb +41 -0
  51. data/lib/resque/scheduler/version.rb +7 -0
  52. data/resque-scheduler.gemspec +21 -19
  53. data/script/migrate_to_timestamps_set.rb +5 -3
  54. data/tasks/resque_scheduler.rake +1 -1
  55. data/test/cli_test.rb +26 -69
  56. data/test/delayed_queue_test.rb +262 -169
  57. data/test/env_test.rb +41 -0
  58. data/test/resque-web_test.rb +169 -48
  59. data/test/scheduler_args_test.rb +73 -41
  60. data/test/scheduler_hooks_test.rb +9 -8
  61. data/test/scheduler_locking_test.rb +55 -36
  62. data/test/scheduler_setup_test.rb +52 -15
  63. data/test/scheduler_task_test.rb +15 -10
  64. data/test/scheduler_test.rb +215 -114
  65. data/test/support/redis_instance.rb +32 -33
  66. data/test/test_helper.rb +33 -36
  67. data/test/util_test.rb +11 -0
  68. metadata +113 -57
  69. data/lib/resque/scheduler_locking.rb +0 -91
  70. data/lib/resque_scheduler.rb +0 -386
  71. data/lib/resque_scheduler/cli.rb +0 -160
  72. data/lib/resque_scheduler/logger_builder.rb +0 -72
  73. data/lib/resque_scheduler/plugin.rb +0 -28
  74. data/lib/resque_scheduler/server.rb +0 -183
  75. data/lib/resque_scheduler/util.rb +0 -34
  76. data/lib/resque_scheduler/version.rb +0 -5
  77. data/test/redis-test.conf +0 -108
@@ -1,160 +0,0 @@
1
- # vim:fileencoding=utf-8
2
-
3
- require 'optparse'
4
-
5
- module ResqueScheduler
6
- class Cli
7
- BANNER = <<-EOF.gsub(/ {6}/, '')
8
- Usage: resque-scheduler [options]
9
-
10
- Runs a resque scheduler process directly (rather than via rake).
11
-
12
- EOF
13
- OPTIONS = [
14
- {
15
- args: ['-n', '--app-name [APP_NAME]', 'Application name for procline'],
16
- callback: ->(options) { ->(n) { options[:app_name] = n } }
17
- },
18
- {
19
- args: ['-B', '--background', 'Run in the background [BACKGROUND]'],
20
- callback: ->(options) { ->(b) { options[:background] = b } }
21
- },
22
- {
23
- args: ['-D', '--dynamic-schedule',
24
- 'Enable dynamic scheduling [DYNAMIC_SCHEDULE]'],
25
- callback: ->(options) { ->(d) { options[:dynamic] = d } }
26
- },
27
- {
28
- args: ['-E', '--environment [RAILS_ENV]', 'Environment name'],
29
- callback: ->(options) { ->(e) { options[:env] = e } }
30
- },
31
- {
32
- args: ['-I', '--initializer-path [INITIALIZER_PATH]',
33
- 'Path to optional initializer ruby file'],
34
- callback: ->(options) { ->(i) { options[:initializer_path] = i } }
35
- },
36
- {
37
- args: ['-i', '--interval [RESQUE_SCHEDULER_INTERVAL]',
38
- 'Interval for checking if a scheduled job must run'],
39
- callback: ->(options) { ->(i) { options[:poll_sleep_amount] = i } }
40
- },
41
- {
42
- args: ['-l', '--logfile [LOGFILE]', 'Log file name'],
43
- callback: ->(options) { ->(l) { options[:logfile] = l } }
44
- },
45
- {
46
- args: ['-F', '--logformat [LOGFORMAT]', 'Log output format'],
47
- callback: ->(options) { ->(f) { options[:logformat] = f } }
48
- },
49
- {
50
- args: ['-P', '--pidfile [PIDFILE]', 'PID file name'],
51
- callback: ->(options) { ->(p) { options[:pidfile] = p } }
52
- },
53
- {
54
- args: ['-q', '--quiet', 'Run with minimal output [QUIET] (or [MUTE])'],
55
- callback: ->(options) { ->(q) { options[:mute] = q } }
56
- },
57
- {
58
- args: ['-v', '--verbose', 'Run with verbose output [VERBOSE]'],
59
- callback: ->(options) { ->(v) { options[:verbose] = v } }
60
- }
61
- ].freeze
62
-
63
- def self.run!(argv = ARGV, env = ENV)
64
- new(argv, env).run!
65
- end
66
-
67
- def initialize(argv = ARGV, env = ENV)
68
- @argv = argv
69
- @env = env
70
- end
71
-
72
- def run!
73
- pre_run
74
- run_forever
75
- end
76
-
77
- def pre_run
78
- parse_options
79
- pre_setup
80
- setup_env
81
- end
82
-
83
- def parse_options
84
- OptionParser.new do |opts|
85
- opts.banner = BANNER
86
- OPTIONS.each do |opt|
87
- opts.on(*opt[:args], &(opt[:callback].call(options)))
88
- end
89
- end.parse!(argv.dup)
90
- end
91
-
92
- def pre_setup
93
- if options[:initializer_path]
94
- load options[:initializer_path].to_s.strip
95
- else
96
- false
97
- end
98
- end
99
-
100
- def setup_env
101
- require 'resque'
102
- require 'resque/scheduler'
103
-
104
- # Need to set this here for conditional Process.daemon redirect of
105
- # stderr/stdout to /dev/null
106
- Resque::Scheduler.mute = !!options[:mute]
107
-
108
- if options[:background]
109
- unless Process.respond_to?('daemon')
110
- abort 'background option is set, which requires ruby >= 1.9'
111
- end
112
-
113
- Process.daemon(true, !Resque::Scheduler.mute)
114
- Resque.redis.client.reconnect
115
- end
116
-
117
- File.open(options[:pidfile], 'w') do |f|
118
- f.puts $PROCESS_ID
119
- end if options[:pidfile]
120
-
121
- Resque::Scheduler.configure do |c|
122
- # These settings are somewhat redundant given the defaults present
123
- # in the attr reader methods. They are left here for clarity and
124
- # to serve as an example of how to use `.configure`.
125
-
126
- c.app_name = options[:app_name]
127
- c.dynamic = !!options[:dynamic]
128
- c.env = options[:env]
129
- c.logfile = options[:logfile]
130
- c.logformat = options[:logformat]
131
- c.poll_sleep_amount = Float(options[:poll_sleep_amount] || '5')
132
- c.verbose = !!options[:verbose]
133
- end
134
- end
135
-
136
- def run_forever
137
- Resque::Scheduler.run
138
- end
139
-
140
- private
141
-
142
- attr_reader :argv, :env
143
-
144
- def options
145
- @options ||= {
146
- app_name: env['APP_NAME'],
147
- background: env['BACKGROUND'],
148
- dynamic: env['DYNAMIC_SCHEDULE'],
149
- env: env['RAILS_ENV'],
150
- initializer_path: env['INITIALIZER_PATH'],
151
- logfile: env['LOGFILE'],
152
- logformat: env['LOGFORMAT'],
153
- mute: env['MUTE'] || env['QUIET'],
154
- pidfile: env['PIDFILE'],
155
- poll_sleep_amount: env['RESQUE_SCHEDULER_INTERVAL'],
156
- verbose: env['VERBOSE']
157
- }
158
- end
159
- end
160
- end
@@ -1,72 +0,0 @@
1
- # vim:fileencoding=utf-8
2
-
3
- require 'mono_logger'
4
-
5
- module ResqueScheduler
6
- # Just builds a logger, with specified verbosity and destination.
7
- # The simplest example:
8
- #
9
- # ResqueScheduler::LoggerBuilder.new.build
10
- class LoggerBuilder
11
- # Initializes new instance of the builder
12
- #
13
- # Pass :opts Hash with
14
- # - :mute if logger needs to be silent for all levels. Default - false
15
- # - :verbose if there is a need in debug messages. Default - false
16
- # - :log_dev to output logs into a desired file. Default - STDOUT
17
- # - :format log format, either 'text' or 'json'. Default - 'text'
18
- #
19
- # Example:
20
- #
21
- # LoggerBuilder.new(
22
- # :mute => false, :verbose => true, :log_dev => 'log/scheduler.log'
23
- # )
24
- def initialize(opts={})
25
- @muted = !!opts[:mute]
26
- @verbose = !!opts[:verbose]
27
- @log_dev = opts[:log_dev] || $stdout
28
- @format = opts[:format] || 'text'
29
- end
30
-
31
- # Returns an instance of MonoLogger
32
- def build
33
- logger = MonoLogger.new(@log_dev)
34
- logger.level = level
35
- logger.formatter = send(:"#{@format}_formatter")
36
- logger
37
- end
38
-
39
- private
40
-
41
- def level
42
- if @verbose && !@muted
43
- MonoLogger::DEBUG
44
- elsif !@muted
45
- MonoLogger::INFO
46
- else
47
- MonoLogger::FATAL
48
- end
49
- end
50
-
51
- def text_formatter
52
- proc do |severity, datetime, progname, msg|
53
- "resque-scheduler: [#{severity}] #{datetime.iso8601}: #{msg}\n"
54
- end
55
- end
56
-
57
- def json_formatter
58
- proc do |severity, datetime, progname, msg|
59
- require 'json'
60
- JSON.dump(
61
- {
62
- :name => 'resque-scheduler',
63
- :progname => progname,
64
- :level => severity,
65
- :timestamp => datetime.iso8601,
66
- :msg => msg
67
- }
68
- ) + "\n"
69
- end
70
- end
71
- end
72
- end
@@ -1,28 +0,0 @@
1
- module ResqueScheduler
2
- module Plugin
3
- extend self
4
- def hooks(job, pattern)
5
- job.methods.grep(/^#{pattern}/).sort
6
- end
7
-
8
- def run_hooks(job, pattern, *args)
9
- results = hooks(job, pattern).collect do |hook|
10
- job.send(hook, *args)
11
- end
12
-
13
- results.all? { |result| result != false }
14
- end
15
-
16
- def run_before_delayed_enqueue_hooks(klass, *args)
17
- run_hooks(klass, 'before_delayed_enqueue', *args)
18
- end
19
-
20
- def run_before_schedule_hooks(klass, *args)
21
- run_hooks(klass, 'before_schedule', *args)
22
- end
23
-
24
- def run_after_schedule_hooks(klass, *args)
25
- run_hooks(klass, 'after_schedule', *args)
26
- end
27
- end
28
- end
@@ -1,183 +0,0 @@
1
- require 'resque_scheduler'
2
- require 'resque/server'
3
- require 'json'
4
-
5
- # Extend Resque::Server to add tabs
6
- module ResqueScheduler
7
- module Server
8
- def self.included(base)
9
- base.class_eval do
10
- helpers do
11
- def format_time(t)
12
- t.strftime('%Y-%m-%d %H:%M:%S %z')
13
- end
14
-
15
- def queue_from_class_name(class_name)
16
- Resque.queue_from_class(ResqueScheduler::Util.constantize(class_name))
17
- end
18
-
19
- def find_job(worker)
20
- worker = worker.downcase
21
- results = Array.new
22
-
23
- # Check working jobs
24
- working = Resque.working
25
- working = [working] unless working.is_a?(Array)
26
- work = working.select do |w|
27
- w.job && w.job["payload"] && w.job['payload']['class'].downcase.include?(worker)
28
- end
29
- work.each do |w|
30
- results += [w.job['payload'].merge({'queue' => w.job['queue'], 'where_at' => 'working'})]
31
- end
32
-
33
- # Check delayed Jobs
34
- dels = Array.new
35
- Resque.delayed_queue_peek(0, Resque.delayed_queue_schedule_size).each do |d|
36
- Resque.delayed_timestamp_peek(d, 0, Resque.delayed_timestamp_size(d)).each do |j|
37
- dels << j.merge!({'timestamp' => d})
38
- end
39
- end
40
- results += dels.select do |j|
41
- j['class'].downcase.include?(worker) && j.merge!({'where_at' => 'delayed'})
42
- end
43
-
44
- # Check Queues
45
- Resque.queues.each do |queue|
46
- queued = Resque.peek(queue, 0, Resque.size(queue))
47
- queued = [queued] unless queued.is_a?(Array)
48
- results += queued.select do |j|
49
- j['class'].downcase.include?(worker) && j.merge!({'queue' => queue, 'where_at' => 'queued'})
50
- end
51
- end
52
- results
53
- end
54
-
55
- def schedule_interval(config)
56
- if config['every']
57
- schedule_interval_every(config['every'])
58
- elsif config['cron']
59
- 'cron: ' + config['cron'].to_s
60
- else
61
- 'Not currently scheduled'
62
- end
63
- end
64
-
65
- def schedule_interval_every(every)
66
- every = [*every]
67
- s = 'every: ' << every.first
68
-
69
- return s unless every.length > 1
70
-
71
- s << ' ('
72
- meta = every.last.map do |key, value|
73
- "#{key.to_s.gsub(/_/, ' ')} #{value}"
74
- end
75
- s << meta.join(', ') << ')'
76
- end
77
-
78
- def schedule_class(config)
79
- if config['class'].nil? && !config['custom_job_class'].nil?
80
- config['custom_job_class']
81
- else
82
- config['class']
83
- end
84
- end
85
-
86
- def scheduled_in_this_env?(name)
87
- return true if Resque.schedule[name]['rails_env'].nil?
88
- Resque.schedule[name]['rails_env'] == Resque::Scheduler.env
89
- end
90
- end
91
-
92
- get "/schedule" do
93
- Resque.reload_schedule! if Resque::Scheduler.dynamic
94
- # Is there a better way to specify alternate template locations with sinatra?
95
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/scheduler.erb'))
96
- end
97
-
98
- post "/schedule/requeue" do
99
- @job_name = params['job_name'] || params[:job_name]
100
- config = Resque.schedule[@job_name]
101
- @parameters = config['parameters'] || config[:parameters]
102
- if @parameters
103
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/requeue-params.erb'))
104
- else
105
- Resque::Scheduler.enqueue_from_config(config)
106
- redirect u("/overview")
107
- end
108
- end
109
-
110
- post "/schedule/requeue_with_params" do
111
- job_name = params['job_name'] || params[:job_name]
112
- config = Resque.schedule[job_name]
113
- # Build args hash from post data (removing the job name)
114
- submitted_args = params.reject {|key, value| key == 'job_name' || key == :job_name}
115
-
116
- # Merge constructed args hash with existing args hash for
117
- # the job, if it exists
118
- config_args = config['args'] || config[:args] || {}
119
- config_args = config_args.merge(submitted_args)
120
-
121
- # Insert the args hash into config and queue the resque job
122
- config = config.merge('args' => config_args)
123
- Resque::Scheduler.enqueue_from_config(config)
124
- redirect u("/overview")
125
- end
126
-
127
- get "/delayed" do
128
- # Is there a better way to specify alternate template locations with sinatra?
129
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed.erb'))
130
- end
131
-
132
- get "/delayed/jobs/:klass" do
133
- begin
134
- klass = ResqueScheduler::Util::constantize(params[:klass])
135
- @args = JSON.load(URI.decode(params[:args]))
136
- @timestamps = Resque.scheduled_at(klass, *@args)
137
- rescue => err
138
- @timestamps = []
139
- end
140
-
141
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_schedules.erb'))
142
- end
143
-
144
- post "/delayed/search" do
145
- # Is there a better way to specify alternate template locations with sinatra?
146
- @jobs = find_job(params[:search])
147
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/search.erb'))
148
- end
149
-
150
- get "/delayed/:timestamp" do
151
- # Is there a better way to specify alternate template locations with sinatra?
152
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_timestamp.erb'))
153
- end
154
-
155
- post "/delayed/queue_now" do
156
- timestamp = params['timestamp']
157
- Resque::Scheduler.enqueue_delayed_items_for_timestamp(timestamp.to_i) if timestamp.to_i > 0
158
- redirect u("/overview")
159
- end
160
-
161
- post "/delayed/cancel_now" do
162
- klass = ResqueScheduler::Util.constantize params['klass']
163
- timestamp = params['timestamp']
164
- args = Resque.decode params['args']
165
- Resque.remove_delayed_job_from_timestamp(timestamp, klass, *args)
166
- redirect u("/delayed")
167
- end
168
-
169
- post "/delayed/clear" do
170
- Resque.reset_delayed_queue
171
- redirect u('delayed')
172
- end
173
- end
174
- end
175
- end
176
- end
177
-
178
- Resque::Server.tabs << 'Schedule'
179
- Resque::Server.tabs << 'Delayed'
180
-
181
- Resque::Server.class_eval do
182
- include ResqueScheduler::Server
183
- end
@@ -1,34 +0,0 @@
1
- module ResqueScheduler
2
- class Util
3
- # In order to upgrade to resque(1.25) which has deprecated following methods ,
4
- # we just added these usefull helpers back to use in Resque Scheduler.
5
- # refer to: https://github.com/resque/resque-scheduler/pull/273
6
-
7
- def self.constantize(camel_cased_word)
8
- camel_cased_word = camel_cased_word.to_s
9
-
10
- if camel_cased_word.include?('-')
11
- camel_cased_word = classify(camel_cased_word)
12
- end
13
-
14
- names = camel_cased_word.split('::')
15
- names.shift if names.empty? || names.first.empty?
16
-
17
- constant = Object
18
- names.each do |name|
19
- args = Module.method(:const_get).arity != 1 ? [false] : []
20
-
21
- if constant.const_defined?(name, *args)
22
- constant = constant.const_get(name)
23
- else
24
- constant = constant.const_missing(name)
25
- end
26
- end
27
- constant
28
- end
29
-
30
- def self.classify(dashed_word)
31
- dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
32
- end
33
- end
34
- end