resque-scheduler 2.0.0.d → 2.0.0.e

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.

data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'bundler'
2
+ require 'rake/rdoctask'
2
3
  Bundler::GemHelper.install_tasks
3
4
 
4
5
  $LOAD_PATH.unshift 'lib'
@@ -11,3 +12,10 @@ task :test do
11
12
  require File.expand_path(f)
12
13
  end
13
14
  end
15
+
16
+ Rake::RDocTask.new do |rd|
17
+ rd.main = "README.markdown"
18
+ rd.rdoc_files.include("README.markdown", "lib/**/*.rb")
19
+ rd.rdoc_dir = 'doc'
20
+ end
21
+
@@ -11,17 +11,25 @@ module Resque
11
11
 
12
12
  # If true, logs more stuff...
13
13
  attr_accessor :verbose
14
-
14
+
15
15
  # If set, produces no output
16
16
  attr_accessor :mute
17
-
17
+
18
18
  # If set, will try to update the schulde in the loop
19
19
  attr_accessor :dynamic
20
20
 
21
+ # Amount of time in seconds to sleep between polls of the delayed
22
+ # queue. Defaults to 5
23
+ attr_writer :poll_sleep_amount
24
+
21
25
  # the Rufus::Scheduler jobs that are scheduled
22
26
  def scheduled_jobs
23
27
  @@scheduled_jobs
24
28
  end
29
+
30
+ def poll_sleep_amount
31
+ @poll_sleep_amount ||= 5 # seconds
32
+ end
25
33
 
26
34
  # Schedule all jobs and continually look for delayed jobs (never returns)
27
35
  def run
@@ -30,8 +38,12 @@ module Resque
30
38
  register_signal_handlers
31
39
 
32
40
  # Load the schedule into rufus
33
- procline "Loading Schedule"
34
- load_schedule!
41
+ # If dynamic is set, load that schedule otherwise use normal load
42
+ if dynamic
43
+ reload_schedule!
44
+ else
45
+ load_schedule!
46
+ end
35
47
 
36
48
  # Now start the scheduling part of the loop.
37
49
  loop do
@@ -53,33 +65,45 @@ module Resque
53
65
  def register_signal_handlers
54
66
  trap("TERM") { shutdown }
55
67
  trap("INT") { shutdown }
56
-
68
+
57
69
  begin
58
- trap('QUIT') { shutdown }
59
- trap('USR1') { kill_child }
70
+ trap('QUIT') { shutdown }
71
+ trap('USR1') { print_schedule }
60
72
  trap('USR2') { reload_schedule! }
61
73
  rescue ArgumentError
62
74
  warn "Signals QUIT and USR1 and USR2 not supported."
63
75
  end
64
76
  end
65
77
 
78
+ def print_schedule
79
+ if rufus_scheduler
80
+ log! "Scheduling Info\tLast Run"
81
+ scheduler_jobs = rufus_scheduler.all_jobs
82
+ scheduler_jobs.each do |k, v|
83
+ log! "#{v.t}\t#{v.last}\t"
84
+ end
85
+ end
86
+ end
87
+
66
88
  # Pulls the schedule from Resque.schedule and loads it into the
67
89
  # rufus scheduler instance
68
90
  def load_schedule!
91
+ procline "Loading Schedule"
92
+
69
93
  # Need to load the schedule from redis for the first time if dynamic
70
- Resque.reload_schedule! if dynamic
71
-
94
+ Resque.reload_schedule! if dynamic
95
+
72
96
  log! "Schedule empty! Set Resque.schedule" if Resque.schedule.empty?
73
-
97
+
74
98
  @@scheduled_jobs = {}
75
-
99
+
76
100
  Resque.schedule.each do |name, config|
77
101
  load_schedule_job(name, config)
78
102
  end
79
103
  Resque.redis.del(:schedules_changed)
80
104
  procline "Schedules Loaded"
81
105
  end
82
-
106
+
83
107
  # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
84
108
  def load_schedule_job(name, config)
85
109
  # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
@@ -128,7 +152,7 @@ module Resque
128
152
  end
129
153
  end
130
154
  end
131
-
155
+
132
156
  # Enqueues all delayed jobs for a timestamp
133
157
  def enqueue_delayed_items_for_timestamp(timestamp)
134
158
  item = nil
@@ -152,9 +176,12 @@ module Resque
152
176
  # Enqueues a job based on a config hash
153
177
  def enqueue_from_config(job_config)
154
178
  args = job_config['args'] || job_config[:args]
179
+
155
180
  klass_name = job_config['class'] || job_config[:class]
181
+ klass = constantize(klass_name) rescue klass_name
182
+
156
183
  params = args.is_a?(Hash) ? [args] : Array(args)
157
- queue = job_config['queue'] || job_config[:queue] || Resque.queue_from_class(constantize(klass_name))
184
+ queue = job_config['queue'] || job_config[:queue] || Resque.queue_from_class(klass)
158
185
  # Support custom job classes like those that inherit from Resque::JobWithStatus (resque-status)
159
186
  if (job_klass = job_config['custom_job_class']) && (job_klass != 'Resque::Job')
160
187
  # The custom job class API must offer a static "scheduled" method. If the custom
@@ -167,7 +194,14 @@ module Resque
167
194
  Resque::Job.create(queue, job_klass, *params)
168
195
  end
169
196
  else
170
- Resque::Job.create(queue, klass_name, *params)
197
+ # hack to avoid havoc for people shoving stuff into queues
198
+ # for non-existent classes (for example: running scheduler in
199
+ # one app that schedules for another
200
+ if Class === klass
201
+ Resque.enqueue(klass, *params)
202
+ else
203
+ Resque::Job.create(queue, klass, *params)
204
+ end
171
205
  end
172
206
  rescue
173
207
  log! "Failed to enqueue #{klass_name}:\n #{$!}"
@@ -185,13 +219,13 @@ module Resque
185
219
  @@scheduled_jobs = {}
186
220
  rufus_scheduler
187
221
  end
188
-
222
+
189
223
  def reload_schedule!
190
224
  procline "Reloading Schedule"
191
225
  clear_schedule!
192
226
  load_schedule!
193
227
  end
194
-
228
+
195
229
  def update_schedule
196
230
  if Resque.redis.scard(:schedules_changed) > 0
197
231
  procline "Updating schedule"
@@ -207,7 +241,7 @@ module Resque
207
241
  procline "Schedules Loaded"
208
242
  end
209
243
  end
210
-
244
+
211
245
  def unschedule_job(name)
212
246
  if scheduled_jobs[name]
213
247
  log "Removing schedule #{name}"
@@ -219,7 +253,7 @@ module Resque
219
253
  # Sleeps and returns true
220
254
  def poll_sleep
221
255
  @sleeping = true
222
- handle_shutdown { sleep 5 }
256
+ handle_shutdown { sleep poll_sleep_amount }
223
257
  @sleeping = false
224
258
  true
225
259
  end
@@ -238,7 +272,7 @@ module Resque
238
272
  # add "verbose" logic later
239
273
  log!(msg) if verbose
240
274
  end
241
-
275
+
242
276
  def procline(string)
243
277
  log! string
244
278
  $0 = "resque-scheduler-#{ResqueScheduler::Version}: #{string}"
@@ -10,23 +10,31 @@ module ResqueScheduler
10
10
  #
11
11
  # Accepts a new schedule configuration of the form:
12
12
  #
13
- # {some_name => {"cron" => "5/* * * *",
14
- # "class" => DoSomeWork,
13
+ # {'some_name' => {"cron" => "5/* * * *",
14
+ # "class" => "DoSomeWork",
15
15
  # "args" => "work on this string",
16
16
  # "description" => "this thing works it"s butter off"},
17
17
  # ...}
18
18
  #
19
- # :name can be anything and is used only to describe the scheduled job
19
+ # 'some_name' can be anything and is used only to describe and reference
20
+ # the scheduled job
21
+ #
20
22
  # :cron can be any cron scheduling string :job can be any resque job class
21
- # :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage for
22
- # valid syntax. If :cron is present it will take precedence over :every.
23
+ #
24
+ # :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
25
+ # for valid syntax. If :cron is present it will take precedence over :every.
26
+ #
23
27
  # :class must be a resque worker class
24
- # :args can be any yaml which will be converted to a ruby literal and passed
25
- # in a params. (optional)
26
- # :rails_envs is the list of envs where the job gets loaded. Envs are comma separated (optional)
27
- # :description is just that, a description of the job (optional). If params is
28
- # an array, each element in the array is passed as a separate param,
29
- # otherwise params is passed in as the only parameter to perform.
28
+ #
29
+ # :args can be any yaml which will be converted to a ruby literal and
30
+ # passed in a params. (optional)
31
+ #
32
+ # :rails_envs is the list of envs where the job gets loaded. Envs are
33
+ # comma separated (optional)
34
+ #
35
+ # :description is just that, a description of the job (optional). If
36
+ # params is an array, each element in the array is passed as a separate
37
+ # param, otherwise params is passed in as the only parameter to perform.
30
38
  def schedule=(schedule_hash)
31
39
  if Resque::Scheduler.dynamic
32
40
  schedule_hash.each do |name, job_spec|
@@ -59,7 +67,15 @@ module ResqueScheduler
59
67
  end
60
68
  end
61
69
 
62
- # create or update a schedule with the provided name and configuration
70
+ # Create or update a schedule with the provided name and configuration.
71
+ #
72
+ # Note: values for class and custom_job_class need to be strings,
73
+ # not constants.
74
+ #
75
+ # Resque.set_schedule('some_job', {:class => 'SomeJob',
76
+ # :every => '15mins',
77
+ # :queue => 'high',
78
+ # :args => '/tmp/poop'})
63
79
  def set_schedule(name, config)
64
80
  existing_config = get_schedule(name)
65
81
  unless existing_config && existing_config == config
@@ -89,12 +105,26 @@ module ResqueScheduler
89
105
  delayed_push(timestamp, job_to_hash(klass, args))
90
106
  end
91
107
 
108
+ # Identical to +enqueue_at+, except you can also specify
109
+ # a queue in which the job will be placed after the
110
+ # timestamp has passed.
111
+ def enqueue_at_with_queue(queue, timestamp, klass, *args)
112
+ delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
113
+ end
114
+
92
115
  # Identical to enqueue_at but takes number_of_seconds_from_now
93
116
  # instead of a timestamp.
94
117
  def enqueue_in(number_of_seconds_from_now, klass, *args)
95
118
  enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
96
119
  end
97
120
 
121
+ # Identical to +enqueue_in+, except you can also specify
122
+ # a queue in which the job will be placed after the
123
+ # number of seconds has passed.
124
+ def enqueue_in_with_queue(queue, number_of_seconds_from_now, klass, *args)
125
+ enqueue_at_with_queue(queue, Time.now + number_of_seconds_from_now, klass, *args)
126
+ end
127
+
98
128
  # Used internally to stuff the item into the schedule sorted list.
99
129
  # +timestamp+ can be either in seconds or a datetime object
100
130
  # Insertion if O(log(n)).
@@ -183,9 +213,14 @@ module ResqueScheduler
183
213
  end
184
214
 
185
215
  private
216
+
186
217
  def job_to_hash(klass, args)
187
218
  {:class => klass.to_s, :args => args, :queue => queue_from_class(klass)}
188
219
  end
220
+
221
+ def job_to_hash_with_queue(queue, klass, args)
222
+ {:class => klass.to_s, :args => args, :queue => queue}
223
+ end
189
224
 
190
225
  def clean_up_timestamp(key, timestamp)
191
226
  # If the list is empty, remove it.
@@ -45,6 +45,11 @@ module ResqueScheduler
45
45
  Resque::Scheduler.enqueue_delayed_items_for_timestamp(timestamp.to_i) if timestamp.to_i > 0
46
46
  redirect u("/overview")
47
47
  end
48
+
49
+ post "/delayed/clear" do
50
+ Resque.reset_delayed_queue
51
+ redirect u('delayed')
52
+ end
48
53
 
49
54
  end
50
55
 
@@ -1,11 +1,17 @@
1
1
  <h1>Delayed Jobs</h1>
2
+ <%- size = resque.delayed_queue_schedule_size %>
3
+ <% if size > 0 %>
4
+ <form method="POST" action="<%=u 'delayed/clear'%>" class='clear-delayed'>
5
+ <input type='submit' name='' value='Clear Delayed Jobs' />
6
+ </form>
7
+ <% end %>
2
8
 
3
9
  <p class='intro'>
4
10
  This list below contains the timestamps for scheduled delayed jobs.
5
11
  </p>
6
12
 
7
13
  <p class='sub'>
8
- Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.delayed_queue_schedule_size %></b> timestamps
14
+ Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%= size %></b> timestamps
9
15
  </p>
10
16
 
11
17
  <table>
@@ -16,7 +22,7 @@
16
22
  <th>Class</th>
17
23
  <th>Args</th>
18
24
  </tr>
19
- <% resque.delayed_queue_peek(start, start+20).each do |timestamp| %>
25
+ <% resque.delayed_queue_peek(start, 20).each do |timestamp| %>
20
26
  <tr>
21
27
  <td>
22
28
  <form action="<%= u "/delayed/queue_now" %>" method="post">
@@ -11,6 +11,7 @@ namespace :resque do
11
11
 
12
12
  File.open(ENV['PIDFILE'], 'w') { |f| f << Process.pid.to_s } if ENV['PIDFILE']
13
13
 
14
+ Resque::Scheduler.dynamic = true if ENV['DYNAMIC_SCHEDULE']
14
15
  Resque::Scheduler.verbose = true if ENV['VERBOSE']
15
16
  Resque::Scheduler.run
16
17
  end
@@ -23,4 +24,4 @@ namespace :resque do
23
24
  end
24
25
  end
25
26
 
26
- end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module ResqueScheduler
2
- Version = '2.0.0.d'
2
+ Version = '2.0.0.e'
3
3
  end
@@ -12,18 +12,17 @@ Gem::Specification.new do |s|
12
12
  s.description = %q{Light weight job scheduling on top of Resque.
13
13
  Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
14
14
  Also supports queueing jobs on a fixed, cron-like schedule.}
15
-
15
+
16
16
  s.required_rubygems_version = ">= 1.3.6"
17
17
  s.add_development_dependency "bundler", ">= 1.0.0"
18
18
 
19
19
  s.files = `git ls-files`.split("\n")
20
20
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
21
21
  s.require_path = 'lib'
22
-
22
+
23
23
  s.add_runtime_dependency(%q<redis>, [">= 2.0.1"])
24
- s.add_runtime_dependency(%q<resque>, [">= 1.8.0"])
24
+ s.add_runtime_dependency(%q<resque>, [">= 1.15.0"])
25
25
  s.add_runtime_dependency(%q<rufus-scheduler>, [">= 0"])
26
26
  s.add_development_dependency(%q<mocha>, [">= 0"])
27
27
  s.add_development_dependency(%q<rack-test>, [">= 0"])
28
-
29
28
  end
@@ -28,7 +28,35 @@ class Resque::DelayedQueueTest < Test::Unit::TestCase
28
28
  # Confirm the item came out correctly
29
29
  assert_equal('SomeIvarJob', item['class'], "Should be the same class that we queued")
30
30
  assert_equal(["path"], item['args'], "Should have the same arguments that we queued")
31
-
31
+
32
+ # And now confirm the keys are gone
33
+ assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
34
+ assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
35
+ end
36
+
37
+ def test_enqueue_at_with_queue_adds_correct_list_and_zset_and_queue
38
+
39
+ timestamp = Time.now - 1 # 1 second ago (in the past, should come out right away)
40
+
41
+ assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
42
+
43
+ Resque.enqueue_at_with_queue('critical', timestamp, SomeIvarJob, "path")
44
+
45
+ # Confirm the correct keys were added
46
+ assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
47
+ assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
48
+
49
+ read_timestamp = Resque.next_delayed_timestamp
50
+
51
+ # Confirm the timestamp came out correctly
52
+ assert_equal(timestamp.to_i, read_timestamp, "The timestamp we pull out of redis should match the one we put in")
53
+ item = Resque.next_item_for_timestamp(read_timestamp)
54
+
55
+ # Confirm the item came out correctly
56
+ assert_equal('SomeIvarJob', item['class'], "Should be the same class that we queued")
57
+ assert_equal(["path"], item['args'], "Should have the same arguments that we queued")
58
+ assert_equal('critical', item['queue'], "Should have the queue that we asked for")
59
+
32
60
  # And now confirm the keys are gone
33
61
  assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
34
62
  assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
@@ -126,8 +154,7 @@ class Resque::DelayedQueueTest < Test::Unit::TestCase
126
154
  Resque.enqueue_at(t, SomeIvarJob)
127
155
 
128
156
  # 2 SomeIvarJob jobs should be created in the "ivar" queue
129
- Resque::Job.expects(:create).twice.with('ivar', 'SomeIvarJob', nil)
130
- Resque.expects(:queue_from_class).never # Should NOT need to load the class
157
+ Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob, nil)
131
158
  Resque::Scheduler.handle_delayed_items
132
159
  end
133
160
 
@@ -137,23 +164,21 @@ class Resque::DelayedQueueTest < Test::Unit::TestCase
137
164
  Resque.enqueue_at(t, SomeIvarJob)
138
165
 
139
166
  # 2 SomeIvarJob jobs should be created in the "ivar" queue
140
- Resque::Job.expects(:create).twice.with('ivar', 'SomeIvarJob', nil)
141
- Resque.expects(:queue_from_class).never # Should NOT need to load the class
167
+ Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob, nil)
142
168
  Resque::Scheduler.handle_delayed_items(t)
143
169
  end
144
-
170
+
145
171
  def test_enqueue_delayed_items_for_timestamp
146
172
  t = Time.now + 60
147
-
173
+
148
174
  Resque.enqueue_at(t, SomeIvarJob)
149
175
  Resque.enqueue_at(t, SomeIvarJob)
150
176
 
151
177
  # 2 SomeIvarJob jobs should be created in the "ivar" queue
152
- Resque::Job.expects(:create).twice.with('ivar', 'SomeIvarJob', nil)
153
- Resque.expects(:queue_from_class).never # Should NOT need to load the class
178
+ Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob, nil)
154
179
 
155
180
  Resque::Scheduler.enqueue_delayed_items_for_timestamp(t)
156
-
181
+
157
182
  # delayed queue for timestamp should be empty
158
183
  assert_equal(0, Resque.delayed_timestamp_peek(t, 0, 3).length)
159
184
  end
@@ -165,7 +190,7 @@ class Resque::DelayedQueueTest < Test::Unit::TestCase
165
190
  # Since we didn't specify :queue when calling delayed_push, it will be forced
166
191
  # to load the class to figure out the queue. This is the upgrade case from 1.0.4
167
192
  # to 1.0.5.
168
- Resque::Job.expects(:create).once.with(:ivar, 'SomeIvarJob', nil)
193
+ Resque::Job.expects(:create).once.with(:ivar, SomeIvarJob, nil)
169
194
 
170
195
  Resque::Scheduler.handle_delayed_items
171
196
  end
@@ -206,7 +231,7 @@ class Resque::DelayedQueueTest < Test::Unit::TestCase
206
231
  assert_equal(2, Resque.remove_delayed(SomeIvarJob, "bar"))
207
232
  assert_equal(1, Resque.delayed_queue_schedule_size)
208
233
  end
209
-
234
+
210
235
  def test_remove_specific_item_in_group_of_other_items_at_different_timestamps
211
236
  t = Time.now + 120
212
237
  Resque.enqueue_at(t, SomeIvarJob, "foo")