resque-scheduler 2.0.0.d → 2.0.0.e

Sign up to get free protection for your applications and to get access to all the features.

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")