sskirby-resque-scheduler 1.10.8

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg
2
+ nbproject
3
+ .rvmrc
data/HISTORY.md ADDED
@@ -0,0 +1,78 @@
1
+ ## 1.9.6 (2010-10-08)
2
+
3
+ * Support for custom job classes (like resque-status) (mattetti)
4
+
5
+ ## 1.9.5 (2010-09-09)
6
+
7
+ * Updated scheduler rake task to allow for an alternate setup task
8
+ to avoid loading the entire stack. (chewbranca)
9
+ * Fixed sig issue on win32 (#25)
10
+
11
+ ## 1.9.4 (2010-07-29)
12
+
13
+ * Adding ability to remove jobs from delayed queue (joshsz)
14
+ * Fixing issue #23 (removing .present? reference)
15
+
16
+ ## 1.9.3 (2010-07-07)
17
+
18
+ * Bug fix (#19)
19
+
20
+ ## 1.9.2 (2010-06-16)
21
+
22
+ * Fixing issue with redis gem 2.0.1 and redis server 1.2.6 (dbackeus)
23
+
24
+ ## 1.9.1 (2010-06-04)
25
+
26
+ * Fixing issue with redis server 1.2.6 and redis gem 2.0.1
27
+
28
+ ## 1.9.0 (2010-06-04)
29
+
30
+ * Adding redis 2.0 support (bpo)
31
+
32
+ ## 1.8.2 (2010-06-04)
33
+
34
+ * Adding queue now functionality to delayed timestamps (daviddoan)
35
+
36
+ ## 1.8.1 (2010-05-19)
37
+
38
+ * Adding rails_env for scheduled jobs to support scoping jobs by
39
+ RAILS_ENV (gravis).
40
+ * Fixing ruby 1.8.6 compatibility issue.
41
+ * Adding gemspec for bundler support.
42
+
43
+ ## 1.8.0 (2010-04-14)
44
+
45
+ * Moving version to match corresponding resque version
46
+ * Sorting schedule on Scheduler tab
47
+ * Adding tests for resque-web (gravis)
48
+
49
+ ## 1.0.5 (2010-03-01)
50
+
51
+ * Fixed support for overriding queue from schedule config.
52
+ * Removed resque-web dependency on loading the job classes for "Queue Now",
53
+ provided "queue" is specified in the schedule.
54
+ * The queue is now stored with the job and arguments in the delayed queue so
55
+ there is no longer a need for the scheduler to load job classes to introspect
56
+ the queue.
57
+
58
+ ## 1.0.4 (2010-02-26)
59
+
60
+ * Added support for specifying the queue to put the job onto. This allows for
61
+ you to have one job that can go onto multiple queues and be able to schedule
62
+ jobs without having to load the job classes.
63
+
64
+ ## 1.0.3 (2010-02-11)
65
+
66
+ * Added support for scheduled jobs with empty crons. This is helpful to have
67
+ jobs that you don't want on a schedule, but do want to be able to queue by
68
+ clicking a button.
69
+
70
+ ## 1.0.2 (2010-02-?)
71
+
72
+ * Change Delayed Job tab to display job details if only 1 job exists
73
+ for a given timestamp
74
+
75
+ ## 1.0.1 (2010-01-?)
76
+
77
+ * Bugfix: delayed jobs close together resulted in a 5 second sleep
78
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 Ben VandenBos
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.markdown ADDED
@@ -0,0 +1,275 @@
1
+ resque-scheduler
2
+ ===============
3
+
4
+ Resque-scheduler is an extension to [Resque](http://github.com/defunkt/resque)
5
+ that adds support for queueing items in the future.
6
+
7
+ Requires redis >=1.1.
8
+
9
+
10
+ Job scheduling is supported in two different way:
11
+
12
+ ### Recurring (scheduled)
13
+
14
+ Recurring (or scheduled) jobs are logically no different than a standard cron
15
+ job. They are jobs that run based on a fixed schedule which is set at startup.
16
+
17
+ The schedule is a list of Resque worker classes with arguments and a
18
+ schedule frequency (in crontab syntax). The schedule is just a hash, but
19
+ is most likely stored in a YAML like so:
20
+
21
+ queue_documents_for_indexing:
22
+ cron: "0 0 * * *"
23
+ class: QueueDocuments
24
+ args:
25
+ description: "This job queues all content for indexing in solr"
26
+
27
+ clear_leaderboards_contributors:
28
+ cron: "30 6 * * 1"
29
+ class: ClearLeaderboards
30
+ args: contributors
31
+ description: "This job resets the weekly leaderboard for contributions"
32
+
33
+ A queue option can also be specified. When job will go onto the specified queue
34
+ if it is available (Even if @queue is specified in the job class). When the
35
+ queue is given it is not necessary for the scheduler to load the class.
36
+
37
+ clear_leaderboards_moderator:
38
+ cron: "30 6 * * 1"
39
+ class: ClearLeaderboards
40
+ queue: scoring
41
+ args: moderators
42
+ description: "This job resets the weekly leaderboard for moderators"
43
+
44
+ And then set the schedule wherever you configure Resque, like so:
45
+
46
+ require 'resque_scheduler'
47
+ Resque.schedule = YAML.load_file(File.join(File.dirname(__FILE__), '../resque_schedule.yml'))
48
+
49
+ Keep in mind, scheduled jobs behave like crons: if your scheduler process (more
50
+ on that later) is not running when a particular job is supposed to be queued,
51
+ it will NOT be ran later when the scheduler process is started back up. In that
52
+ sense, you can sort of think of the scheduler process as crond. Delayed jobs,
53
+ however, are different.
54
+
55
+ A big shout out to [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler)
56
+ for handling the heavy lifting of the actual scheduling engine.
57
+
58
+ ### Delayed jobs
59
+
60
+ Delayed jobs are one-off jobs that you want to be put into a queue at some point
61
+ in the future. The classic example is sending email:
62
+
63
+ Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :user_id => current_user.id)
64
+
65
+ This will store the job for 5 days in the resque delayed queue at which time the
66
+ scheduler process will pull it from the delayed queue and put it in the
67
+ appropriate work queue for the given job and it will be processed as soon as
68
+ a worker is available.
69
+
70
+ NOTE: The job does not fire **exactly** at the time supplied. Rather, once that
71
+ time is in the past, the job moves from the delayed queue to the actual resque
72
+ work queue and will be completed as workers as free to process it.
73
+
74
+ Also supported is `Resque.enqueue_in` which takes an amount of time in seconds
75
+ in which to queue the job.
76
+
77
+ The delayed queue is stored in redis and is persisted in the same way the
78
+ standard resque jobs are persisted (redis writing to disk). Delayed jobs differ
79
+ from scheduled jobs in that if your scheduler process is down or workers are
80
+ down when a particular job is supposed to be queue, they will simply "catch up"
81
+ once they are started again. Jobs are guaranteed to run (provided they make it
82
+ into the delayed queue) after their given queue_at time has passed.
83
+
84
+ One other thing to note is that insertion into the delayed queue is O(log(n))
85
+ since the jobs are stored in a redis sorted set (zset). I can't imagine this
86
+ being an issue for someone since redis is stupidly fast even at log(n), but full
87
+ disclosure is always best.
88
+
89
+ *Removing Delayed jobs*
90
+
91
+ If you have the need to cancel a delayed job, you can do so thusly:
92
+
93
+ # after you've enqueued a job like:
94
+ Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :user_id => current_user.id)
95
+ # remove the job with exactly the same parameters:
96
+ Resque.remove_delayed(SendFollowUpEmail, :user_id => current_user.id)
97
+
98
+ ### Schedule jobs per environment
99
+
100
+ Resque-Scheduler allows to create schedule jobs for specific envs. The arg
101
+ `rails_env` (optional) can be used to determine which envs are concerned by the
102
+ job:
103
+
104
+ create_fake_leaderboards:
105
+ cron: "30 6 * * 1"
106
+ class: CreateFakeLeaderboards
107
+ queue: scoring
108
+ args:
109
+ rails_env: demo
110
+ description: "This job will auto-create leaderboards for our online demo"
111
+
112
+ The scheduled job create_fake_leaderboards will be created only if the
113
+ environment variable `RAILS_ENV` is set to demo:
114
+
115
+ $ RAILS_ENV=demo rake resque:scheduler
116
+
117
+ NOTE: If you have added the 2 lines bellow to your Rails Rakefile
118
+ (ie: lib/tasks/resque-scheduler.rake), the rails env is loaded automatically
119
+ and you don't have to specify RAILS_ENV if the var is correctly set in
120
+ environment.rb
121
+
122
+ Alternatively, you can use your resque initializer to avoid loading the entire
123
+ rails stack.
124
+
125
+ $ rake resque:scheduler INITIALIZER_PATH=config/initializers/resque.rb
126
+
127
+
128
+ Multiple envs are allowed, separated by commas:
129
+
130
+ create_fake_leaderboards:
131
+ cron: "30 6 * * 1"
132
+ class: CreateFakeLeaderboards
133
+ queue: scoring
134
+ args:
135
+ rails_env: demo, staging, production
136
+ description: "This job will auto-create leaderboards"
137
+
138
+ NOTE: If you specify the `rails_env` arg without setting RAILS_ENV as an
139
+ environment variable, the job won't be loaded.
140
+
141
+ ### Dynamic Schedules
142
+
143
+ If needed you can also have schedules that are dynamically defined and updated inside of your application. This can be completed by loading the schedule initially wherever you configure Resque and setting `Resque::Scheduler.dynamic` to `true`. Then subsequently updating the "`schedules`" key in redis, namespaced to the Resque namespace. The "`schedules`" key is expected to be a redis hash data type, where the key is the name of the schedule and the value is a JSON encoded hash of the schedule configuration.
144
+
145
+ When the scheduler loops it will look for differences between the existing schedule and the current schedule in redis. If there are differences it will make the necessary changes to the running schedule.
146
+
147
+ To force the scheduler to reload the schedule you just send it the `USR2` signal.
148
+
149
+ Convenience methods are provided to add/update, delete, and retrieve individual schedule items from the `schedules` in redis:
150
+
151
+ * `Resque.set_schedule(name, config)`
152
+ * `Resque.get_schedule(name)`
153
+ * `Resque.remove_schedule(name)`
154
+
155
+ For example:
156
+
157
+ Resque.set_schedule("create_fake_leaderboards", {
158
+ :cron => "30 6 * * 1",
159
+ :class => "CreateFakeLeaderboards",
160
+ :queue => scoring
161
+ })
162
+
163
+ ### Support for customized Job classes
164
+
165
+ Some Resque extensions like [resque-status](http://github.com/quirkey/resque-status) use custom job classes with a slightly different API signature.
166
+ Resque-scheduler isn't trying to support all existing and future custom job classes, instead it supports a schedule flag so you can extend your custom class
167
+ and make it support scheduled job.
168
+
169
+ Let's pretend we have a JobWithStatus class called FakeLeaderboard
170
+
171
+ class FakeLeaderboard < Resque::JobWithStatus
172
+ def perfom
173
+ # do something and keep track of the status
174
+ end
175
+ end
176
+
177
+ create_fake_leaderboards:
178
+ cron: "30 6 * * 1"
179
+ queue: scoring
180
+ custom_job_class: FakeLeaderboard
181
+ args:
182
+ rails_env: demo
183
+ description: "This job will auto-create leaderboards for our online demo and the status will update as the worker makes progress"
184
+
185
+ If your extension doesn't support scheduled job, you would need to extend the custom job class to support the #scheduled method:
186
+
187
+ module Resque
188
+ class JobWithStatus
189
+ # Wrapper API to forward a Resque::Job creation API call into a JobWithStatus call.
190
+ def self.scheduled(queue, klass, *args)
191
+ create(args)
192
+ end
193
+ end
194
+ end
195
+
196
+
197
+ Resque-web additions
198
+ --------------------
199
+
200
+ Resque-scheduler also adds to tabs to the resque-web UI. One is for viewing
201
+ (and manually queueing) the schedule and one is for viewing pending jobs in
202
+ the delayed queue.
203
+
204
+ The Schedule tab:
205
+
206
+ ![The Schedule Tab](http://img.skitch.com/20100111-km2f5gmtpbq23enpujbruj6mgk.png)
207
+
208
+ The Delayed tab:
209
+
210
+ ![The Delayed Tab](http://img.skitch.com/20100111-ne4fcqtc5emkcuwc5qtais2kwx.jpg)
211
+
212
+ Get get these to show up you need to pass a file to `resque-web` to tell it to
213
+ include the `resque-scheduler` plugin. You probably already have a file somewhere
214
+ where you configure `resque`. It probably looks something like this:
215
+
216
+ require 'resque' # include resque so we can configure it
217
+ Resque.redis = "redis_server:6379" # tell Resque where redis lives
218
+
219
+ Now, you want to add the following:
220
+
221
+ require 'resque_scheduler' # include the resque_scheduler (this makes the tabs show up)
222
+
223
+ And if you have a schedule you want to set, add this:
224
+
225
+ Resque.schedule = YAML.load_file(File.join(RAILS_ROOT, 'config/resque_schedule.yml')) # load the schedule
226
+
227
+ Now make sure you're passing that file to resque-web like so:
228
+
229
+ resque-web ~/yourapp/config/resque_config.rb
230
+
231
+ That should make the scheduler tabs show up in `resque-web`.
232
+
233
+
234
+
235
+ Installation and the Scheduler process
236
+ --------------------------------------
237
+
238
+ To install:
239
+
240
+ gem install resque-scheduler
241
+
242
+ You'll need to add this to your rakefile:
243
+
244
+ require 'resque_scheduler/tasks'
245
+ task "resque:setup" => :environment
246
+
247
+ The scheduler process is just a rake task which is responsible for both queueing
248
+ items from the schedule and polling the delayed queue for items ready to be
249
+ pushed on to the work queues. For obvious reasons, this process never exits.
250
+
251
+ $ rake resque:scheduler
252
+
253
+ Supported environment variables are `VERBOSE` and `MUTE`. If either is set to
254
+ any nonempty value, they will take effect. `VERBOSE` simply dumps more output
255
+ to stdout. `MUTE` does the opposite and silences all output. `MUTE` supercedes
256
+ `VERBOSE`.
257
+
258
+ NOTE: You DO NOT want to run >1 instance of the scheduler. Doing so will result
259
+ in the same job being queued more than once. You only need one instnace of the
260
+ scheduler running per resque instance (regardless of number of machines).
261
+
262
+
263
+ Plagurism alert
264
+ ---------------
265
+
266
+ This was intended to be an extension to resque and so resulted in a lot of the
267
+ code looking very similar to resque, particularly in resque-web and the views. I
268
+ wanted it to be similar enough that someone familiar with resque could easily
269
+ work on resque-scheduler.
270
+
271
+
272
+ Contributing
273
+ ------------
274
+
275
+ For bugs or suggestions, please just open an issue in github.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ load File.expand_path('tasks/resque_scheduler.rake')
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+
5
+ task :default => :test
6
+
7
+ desc "Run tests"
8
+ task :test do
9
+ Dir['test/*_test.rb'].each do |f|
10
+ require File.expand_path(f)
11
+ end
12
+ end
13
+
14
+
15
+ desc "Build a gem"
16
+ task :gem => [ :test, :gemspec, :build ]
17
+
18
+ begin
19
+ begin
20
+ require 'jeweler'
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: "
23
+ puts "gem install jeweler"
24
+ end
25
+
26
+ require 'resque_scheduler/version'
27
+
28
+ Jeweler::Tasks.new do |gemspec|
29
+ gemspec.name = "sskirby-resque-scheduler"
30
+ gemspec.summary = "Light weight job scheduling on top of Resque"
31
+ gemspec.description = %{Light weight job scheduling on top of Resque.
32
+ Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
33
+ Also supports queueing jobs on a fixed, cron-like schedule.}
34
+ gemspec.email = "sskirby@gmail.com"
35
+ gemspec.homepage = "http://github.com/sskirby/resque-scheduler"
36
+ gemspec.authors = ["Ben VandenBos", "Brian Landau", "Sean Kirby", "Tanzeeb Khalili"]
37
+ gemspec.version = ResqueScheduler::Version
38
+
39
+ gemspec.add_dependency "redis", ">= 2.0.1"
40
+ gemspec.add_dependency "resque", ">= 1.8.0"
41
+ gemspec.add_dependency "rufus-scheduler"
42
+ gemspec.add_development_dependency "jeweler"
43
+ gemspec.add_development_dependency "mocha"
44
+ gemspec.add_development_dependency "rack-test"
45
+ end
46
+
47
+ Jeweler::GemcutterTasks.new
48
+ end
@@ -0,0 +1,230 @@
1
+ require 'rufus/scheduler'
2
+ require 'thwait'
3
+
4
+ module Resque
5
+
6
+ class Scheduler
7
+
8
+ extend Resque::Helpers
9
+
10
+ class << self
11
+
12
+ # If true, logs more stuff...
13
+ attr_accessor :verbose
14
+
15
+ # If set, produces no output
16
+ attr_accessor :mute
17
+
18
+ # If set, will try to update the schulde in the loop
19
+ attr_accessor :dynamic
20
+
21
+ # the Rufus::Scheduler jobs that are scheduled
22
+ def scheduled_jobs
23
+ @@scheduled_jobs
24
+ end
25
+
26
+ # Schedule all jobs and continually look for delayed jobs (never returns)
27
+ def run
28
+
29
+ # trap signals
30
+ register_signal_handlers
31
+
32
+ # Load the schedule into rufus
33
+ load_schedule!
34
+
35
+ # Now start the scheduling part of the loop.
36
+ loop do
37
+ handle_delayed_items
38
+ update_schedule if dynamic
39
+ poll_sleep
40
+ end
41
+
42
+ # never gets here.
43
+ end
44
+
45
+ # For all signals, set the shutdown flag and wait for current
46
+ # poll/enqueing to finish (should be almost istant). In the
47
+ # case of sleeping, exit immediately.
48
+ def register_signal_handlers
49
+ trap("TERM") { shutdown }
50
+ trap("INT") { shutdown }
51
+
52
+ begin
53
+ trap('QUIT') { shutdown }
54
+ trap('USR1') { kill_child }
55
+ trap('USR2') { reload_schedule! }
56
+ rescue ArgumentError
57
+ warn "Signals QUIT and USR1 and USR2 not supported."
58
+ end
59
+ end
60
+
61
+ # Pulls the schedule from Resque.schedule and loads it into the
62
+ # rufus scheduler instance
63
+ def load_schedule!
64
+ log! "Schedule empty! Set Resque.schedule" if Resque.schedule.empty?
65
+
66
+ @@scheduled_jobs = {}
67
+
68
+ Resque.schedule.each do |name, config|
69
+ load_schedule_job(name, config)
70
+ end
71
+ end
72
+
73
+ # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
74
+ def load_schedule_job(name, config)
75
+ # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
76
+ # required for the jobs to be scheduled. If rails_env is missing, the
77
+ # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
78
+ # to.
79
+ if config['rails_env'].nil? || rails_env_matches?(config)
80
+ log! "Scheduling #{name} "
81
+ if !config['cron'].nil? && config['cron'].length > 0
82
+ @@scheduled_jobs[name] = rufus_scheduler.cron config['cron'] do
83
+ log! "queuing #{config['class']} (#{name})"
84
+ enqueue_from_config(config)
85
+ end
86
+ else
87
+ log! "no cron found for #{config['class']} (#{name}) - skipping"
88
+ end
89
+ end
90
+ end
91
+
92
+ # Returns true if the given schedule config hash matches the current
93
+ # ENV['RAILS_ENV']
94
+ def rails_env_matches?(config)
95
+ config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/,'').split(',').include?(ENV['RAILS_ENV'])
96
+ end
97
+
98
+ # Handles queueing delayed items
99
+ def handle_delayed_items
100
+ item = nil
101
+ begin
102
+ if timestamp = Resque.next_delayed_timestamp
103
+ enqueue_delayed_items_for_timestamp(timestamp)
104
+ end
105
+ # continue processing until there are no more ready timestamps
106
+ end while !timestamp.nil?
107
+ end
108
+
109
+ # Enqueues all delayed jobs for a timestamp
110
+ def enqueue_delayed_items_for_timestamp(timestamp)
111
+ item = nil
112
+ begin
113
+ handle_shutdown do
114
+ if item = Resque.next_item_for_timestamp(timestamp)
115
+ log "queuing #{item['class']} [delayed]"
116
+ klass = constantize(item['class'])
117
+ queue = item['queue'] || Resque.queue_from_class(klass)
118
+ # Support custom job classes like job with status
119
+ if (job_klass = item['custom_job_class']) && (job_klass != 'Resque::Job')
120
+ # custom job classes not supporting the same API calls must implement the #schedule method
121
+ constantize(job_klass).scheduled(queue, item['class'], *item['args'])
122
+ else
123
+ Resque::Job.create(queue, klass, *item['args'])
124
+ end
125
+ end
126
+ end
127
+ # continue processing until there are no more ready items in this timestamp
128
+ end while !item.nil?
129
+ end
130
+
131
+ def handle_shutdown
132
+ exit if @shutdown
133
+ yield
134
+ exit if @shutdown
135
+ end
136
+
137
+ # Enqueues a job based on a config hash
138
+ def enqueue_from_config(config)
139
+ args = config['args'] || config[:args]
140
+ klass_name = config['class'] || config[:class]
141
+ klass = constantize(klass_name)
142
+ params = args.nil? ? [] : Array(args)
143
+ queue = config['queue'] || config[:queue] || Resque.queue_from_class(klass)
144
+ # Support custom job classes like job with status
145
+ if (job_klass = config['custom_job_class']) && (job_klass != 'Resque::Job')
146
+ # custom job classes not supporting the same API calls must implement the #schedule method
147
+ constantize(job_klass).scheduled(queue, klass_name, *params)
148
+ else
149
+ Resque::Job.create(queue, klass, *params)
150
+ end
151
+ end
152
+
153
+ def rufus_scheduler
154
+ @rufus_scheduler ||= Rufus::Scheduler.start_new
155
+ end
156
+
157
+ # Stops old rufus scheduler and creates a new one. Returns the new
158
+ # rufus scheduler
159
+ def clear_schedule!
160
+ rufus_scheduler.stop
161
+ @rufus_scheduler = nil
162
+ @@scheduled_jobs = {}
163
+ rufus_scheduler
164
+ end
165
+
166
+ def reload_schedule!
167
+ log! "Reloading Schedule..."
168
+ clear_schedule!
169
+ Resque.reload_schedule!
170
+ load_schedule!
171
+ end
172
+
173
+ def update_schedule
174
+ schedule_from_redis = Resque.get_schedules
175
+ if !schedule_from_redis.nil? && schedule_from_redis != Resque.schedule
176
+ log "Updating schedule..."
177
+ # unload schedules that no longer exist
178
+ (Resque.schedule.keys - schedule_from_redis.keys).each do |name|
179
+ unschedule_job(name)
180
+ end
181
+
182
+ # find changes and stop and reload or add new
183
+ schedule_from_redis.each do |name, config|
184
+ if (Resque.schedule[name].nil? || Resque.schedule[name].empty?) || (config != Resque.schedule[name])
185
+ unschedule_job(name)
186
+ load_schedule_job(name, config)
187
+ end
188
+ end
189
+
190
+ # load new schedule into Resque.schedule
191
+ Resque.schedule = schedule_from_redis
192
+ end
193
+ end
194
+
195
+ def unschedule_job(name)
196
+ if scheduled_jobs[name]
197
+ log "Removing schedule #{name}"
198
+ scheduled_jobs[name].unschedule
199
+ @@scheduled_jobs.delete(name)
200
+ end
201
+ end
202
+
203
+ # Sleeps and returns true
204
+ def poll_sleep
205
+ @sleeping = true
206
+ handle_shutdown { sleep 5 }
207
+ @sleeping = false
208
+ true
209
+ end
210
+
211
+ # Sets the shutdown flag, exits if sleeping
212
+ def shutdown
213
+ @shutdown = true
214
+ exit if @sleeping
215
+ end
216
+
217
+ def log!(msg)
218
+ puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} #{msg}" unless mute
219
+ end
220
+
221
+ def log(msg)
222
+ # add "verbose" logic later
223
+ log!(msg) if verbose
224
+ end
225
+
226
+ end
227
+
228
+ end
229
+
230
+ end
@@ -0,0 +1,42 @@
1
+ <h1>Delayed Jobs</h1>
2
+
3
+ <p class='intro'>
4
+ This list below contains the timestamps for scheduled delayed jobs.
5
+ </p>
6
+
7
+ <p class='sub'>
8
+ Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.delayed_queue_schedule_size %></b> timestamps
9
+ </p>
10
+
11
+ <table>
12
+ <tr>
13
+ <th></th>
14
+ <th>Timestamp</th>
15
+ <th>Job count</th>
16
+ <th>Class</th>
17
+ <th>Args</th>
18
+ </tr>
19
+ <% resque.delayed_queue_peek(start, start+20).each do |timestamp| %>
20
+ <tr>
21
+ <td>
22
+ <form action="<%= url "/delayed/queue_now" %>" method="post">
23
+ <input type="hidden" name="timestamp" value="<%= timestamp.to_i %>">
24
+ <input type="submit" value="Queue now">
25
+ </form>
26
+ </td>
27
+ <td><a href="<%= url "delayed/#{timestamp}" %>"><%= format_time(Time.at(timestamp)) %></a></td>
28
+ <td><%= delayed_timestamp_size = resque.delayed_timestamp_size(timestamp) %></td>
29
+ <% job = resque.delayed_timestamp_peek(timestamp, 0, 1).first %>
30
+ <td>
31
+ <% if job && delayed_timestamp_size == 1 %>
32
+ <%= h(job['class']) %>
33
+ <% else %>
34
+ <a href="<%= url "delayed/#{timestamp}" %>">see details</a>
35
+ <% end %>
36
+ </td>
37
+ <td><%= h(job['args'].inspect) if job && delayed_timestamp_size == 1 %></td>
38
+ </tr>
39
+ <% end %>
40
+ </table>
41
+
42
+ <%= partial :next_more, :start => start, :size => size %>