resque-scheduler 2.0.0.g → 2.0.0.h

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/HISTORY.md CHANGED
@@ -1,6 +1,6 @@
1
- ## 2.0.0.g (2012-01-24)
1
+ ## 2.0.0.h (2012-03-19)
2
2
 
3
- * Fixed "already initialized constant" warning (skddc)
3
+ * Adding plugin support with hooks (andreas)
4
4
 
5
5
  ## 2.0.0.f (2011-11-03)
6
6
 
data/README.markdown CHANGED
@@ -128,12 +128,6 @@ down when a particular job is supposed to be queue, they will simply "catch up"
128
128
  once they are started again. Jobs are guaranteed to run (provided they make it
129
129
  into the delayed queue) after their given queue_at time has passed.
130
130
 
131
- Similar to `before_enqueue` and `after_enqueue` hooks provided in Resque
132
- (>= 1.19.1), your jobs can specify one or more `before_schedule` and
133
- `after_schedule` hooks, to be run before or after scheduling. If *any* of your
134
- `before_schedule` hooks returns `false`, the job will *not* be scheduled and
135
- your `after_schedule` hooks will *not* be run.
136
-
137
131
  One other thing to note is that insertion into the delayed queue is O(log(n))
138
132
  since the jobs are stored in a redis sorted set (zset). I can't imagine this
139
133
  being an issue for someone since redis is stupidly fast even at log(n), but full
@@ -158,10 +152,15 @@ The schedule is a list of Resque worker classes with arguments and a
158
152
  schedule frequency (in crontab syntax). The schedule is just a hash, but
159
153
  is most likely stored in a YAML like so:
160
154
 
155
+ CancelAbandonedOrders:
156
+ cron: "*/5 * * * *"
157
+
161
158
  queue_documents_for_indexing:
162
159
  cron: "0 0 * * *"
163
160
  # you can use rufus-scheduler "every" syntax in place of cron if you prefer
164
161
  # every: 1hr
162
+ # By default the job name (hash key) will be taken as worker class name.
163
+ # If you want to have a different job name and class name, provide the 'class' option
165
164
  class: QueueDocuments
166
165
  queue: high
167
166
  args:
@@ -180,6 +179,15 @@ defined. If you're getting "uninitialized constant" errors, you probably
180
179
  need to either set the queue in the schedule or require your jobs in your
181
180
  "resque:setup" rake task.
182
181
 
182
+ You can provide options to "every" or "cron" via Array:
183
+
184
+ clear_leaderboards_moderator:
185
+ every: ["30s", :first_in => '120s']
186
+ class: CheckDaemon
187
+ queue: daemons
188
+ description: "This job will check Daemon every 30 seconds after 120 seconds after start"
189
+
190
+
183
191
  NOTE: Six parameter cron's are also supported (as they supported by
184
192
  rufus-scheduler which powers the resque-scheduler process). This allows you
185
193
  to schedule jobs per second (ie: "30 * * * * *" would fire a job every 30
@@ -206,6 +214,21 @@ from the `config.time_zone` value, make sure it's the right format, e.g. with:
206
214
 
207
215
  A future version of resque-scheduler may do this for you.
208
216
 
217
+ #### Hooks
218
+
219
+ Similar to the `before_enqueue`- and `after_enqueue`-hooks provided in Resque
220
+ (>= 1.19.1), your jobs can specify one or more of the following hooks:
221
+
222
+ * `before_schedule`: Called with the job args before a job is placed on
223
+ the delayed queue. If the hook returns `false`, the job will not be placed on
224
+ the queue.
225
+ * `after_schedule`: Called with the job args after a job is placed on the
226
+ delayed queue. Any exception raised propagates up to the code with queued the
227
+ job.
228
+ * `before_delayed_enqueue`: Called with the job args after the job has been
229
+ removed from the delayed queue, but not yet put on a normal queue. It is
230
+ called before `before_enqueue`-hooks, and on the same job instance as the
231
+ `before_enqueue`-hooks will be invoked on. Return values are ignored.
209
232
 
210
233
  #### Support for resque-status (and other custom jobs)
211
234
 
@@ -103,6 +103,21 @@ module Resque
103
103
  Resque.redis.del(:schedules_changed)
104
104
  procline "Schedules Loaded"
105
105
  end
106
+
107
+ # modify interval type value to value with options if options available
108
+ def optionizate_interval_value(value)
109
+ args = value
110
+ if args.is_a?(::Array)
111
+ return args.first if args.size > 2 || !args.last.is_a?(::Hash)
112
+ # symbolize keys of hash for options
113
+ args[1] = args[1].inject({}) do |m, i|
114
+ key, value = i
115
+ m[(key.to_sym rescue key) || key] = value
116
+ m
117
+ end
118
+ end
119
+ args
120
+ end
106
121
 
107
122
  # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
108
123
  def load_schedule_job(name, config)
@@ -116,7 +131,8 @@ module Resque
116
131
  interval_types = %w{cron every}
117
132
  interval_types.each do |interval_type|
118
133
  if !config[interval_type].nil? && config[interval_type].length > 0
119
- @@scheduled_jobs[name] = rufus_scheduler.send(interval_type, config[interval_type]) do
134
+ args = optionizate_interval_value(config[interval_type])
135
+ @@scheduled_jobs[name] = rufus_scheduler.send(interval_type, *args) do
120
136
  log! "queueing #{config['class']} (#{name})"
121
137
  handle_errors { enqueue_from_config(config) }
122
138
  end
@@ -202,6 +218,7 @@ module Resque
202
218
  # for non-existent classes (for example: running scheduler in
203
219
  # one app that schedules for another
204
220
  if Class === klass
221
+ ResqueScheduler::Plugin.run_before_delayed_enqueue_hooks(klass, *params)
205
222
  Resque.enqueue(klass, *params)
206
223
  else
207
224
  # This will not run the before_hooks in rescue, but will at least
@@ -4,27 +4,36 @@ require 'resque/server'
4
4
  require 'resque_scheduler/version'
5
5
  require 'resque/scheduler'
6
6
  require 'resque_scheduler/server'
7
+ require 'resque_scheduler/plugin'
7
8
 
8
9
  module ResqueScheduler
9
10
 
10
11
  #
11
12
  # Accepts a new schedule configuration of the form:
12
13
  #
13
- # {'some_name' => {"cron" => "5/* * * *",
14
- # "class" => "DoSomeWork",
15
- # "args" => "work on this string",
16
- # "description" => "this thing works it"s butter off"},
17
- # ...}
14
+ # {
15
+ # "MakeTea" => {
16
+ # "every" => "1m" },
17
+ # "some_name" => {
18
+ # "cron" => "5/* * * *",
19
+ # "class" => "DoSomeWork",
20
+ # "args" => "work on this string",
21
+ # "description" => "this thing works it"s butter off" },
22
+ # ...
23
+ # }
18
24
  #
19
- # 'some_name' can be anything and is used only to describe and reference
20
- # the scheduled job
25
+ # Hash keys can be anything and are used to describe and reference
26
+ # the scheduled job. If the "class" argument is missing, the key
27
+ # is used implicitly as "class" argument - in the "MakeTea" example,
28
+ # "MakeTea" is used both as job name and resque worker class.
21
29
  #
22
- # :cron can be any cron scheduling string :job can be any resque job class
30
+ # :cron can be any cron scheduling string
23
31
  #
24
32
  # :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
25
33
  # for valid syntax. If :cron is present it will take precedence over :every.
26
34
  #
27
- # :class must be a resque worker class
35
+ # :class must be a resque worker class. If it is missing, the job name (hash key)
36
+ # will be used as :class.
28
37
  #
29
38
  # :args can be any yaml which will be converted to a ruby literal and
30
39
  # passed in a params. (optional)
@@ -36,6 +45,8 @@ module ResqueScheduler
36
45
  # params is an array, each element in the array is passed as a separate
37
46
  # param, otherwise params is passed in as the only parameter to perform.
38
47
  def schedule=(schedule_hash)
48
+ schedule_hash = prepare_schedule(schedule_hash)
49
+
39
50
  if Resque::Scheduler.dynamic
40
51
  schedule_hash.each do |name, job_spec|
41
52
  set_schedule(name, job_spec)
@@ -109,16 +120,11 @@ module ResqueScheduler
109
120
  # a queue in which the job will be placed after the
110
121
  # timestamp has passed.
111
122
  def enqueue_at_with_queue(queue, timestamp, klass, *args)
112
- before_hooks = before_schedule_hooks(klass).collect do |hook|
113
- klass.send(hook,*args)
114
- end
115
- return false if before_hooks.any? { |result| result == false }
123
+ return false unless Plugin.run_before_schedule_hooks(klass, *args)
116
124
 
117
125
  delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
118
126
 
119
- after_schedule_hooks(klass).collect do |hook|
120
- klass.send(hook,*args)
121
- end
127
+ Plugin.run_after_schedule_hooks(klass, *args)
122
128
  end
123
129
 
124
130
  # Identical to enqueue_at but takes number_of_seconds_from_now
@@ -264,12 +270,14 @@ module ResqueScheduler
264
270
  end
265
271
  end
266
272
 
267
- def before_schedule_hooks(klass)
268
- klass.methods.grep(/^before_schedule/).sort
269
- end
270
-
271
- def after_schedule_hooks(klass)
272
- klass.methods.grep(/^after_schedule/).sort
273
+ def prepare_schedule(schedule_hash)
274
+ prepared_hash = {}
275
+ schedule_hash.each do |name, job_spec|
276
+ job_spec = job_spec.dup
277
+ job_spec['class'] = name unless job_spec.key?('class') || job_spec.key?(:class)
278
+ prepared_hash[name] = job_spec
279
+ end
280
+ prepared_hash
273
281
  end
274
282
 
275
283
  end
@@ -0,0 +1,25 @@
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 method_missing(method_name, *args, &block)
17
+ if method_name =~ /^run_(.*)_hooks$/
18
+ job = args.shift
19
+ run_hooks job, $1, *args
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module ResqueScheduler
2
- VERSION = '2.0.0.g'
2
+ VERSION = '2.0.0.h'
3
3
  end
@@ -1,52 +1,23 @@
1
1
  require File.dirname(__FILE__) + '/test_helper'
2
2
 
3
3
  context "scheduling jobs with hooks" do
4
- class JobThatCannotBeScheduledWithoutArguments < Resque::Job
5
- @queue = :job_that_cannot_be_scheduled_without_arguments
6
- def self.perform(*x);end
7
- def self.before_schedule_return_nil_if_arguments_not_supplied(*args)
8
- counters[:before_schedule] += 1
9
- return false if args.empty?
10
- end
11
-
12
- def self.after_schedule_do_something(*args)
13
- counters[:after_schedule] += 1
14
- end
15
-
16
- class << self
17
- def counters
18
- @counters ||= Hash.new{|h,k| h[k]=0}
19
- end
20
- def clean
21
- counters.clear
22
- self
23
- end
24
- end
25
- end
26
-
27
4
  setup do
28
- Resque::Scheduler.dynamic = false
29
- Resque.redis.del(:schedules)
30
- Resque.redis.del(:schedules_changed)
31
- Resque::Scheduler.mute = true
32
- Resque::Scheduler.clear_schedule!
33
- Resque::Scheduler.send(:class_variable_set, :@@scheduled_jobs, {})
5
+ Resque.redis.flushall
34
6
  end
35
7
 
36
- test "before_schedule hook that does not return false should not block" do
37
- enqueue_time = Time.now + 12
38
- Resque.enqueue_at(enqueue_time, JobThatCannotBeScheduledWithoutArguments.clean, :foo)
39
- assert_equal(1, Resque.delayed_timestamp_size(enqueue_time.to_i), "delayed queue should have one entry now")
40
- assert_equal(1, JobThatCannotBeScheduledWithoutArguments.counters[:before_schedule], 'before_schedule was not run')
41
- assert_equal(1, JobThatCannotBeScheduledWithoutArguments.counters[:after_schedule], 'after_schedule was not run')
8
+ test "before_schedule hook that does not return false should be enqueued" do
9
+ enqueue_time = Time.now
10
+ SomeRealClass.expects(:before_schedule_example).with(:foo)
11
+ SomeRealClass.expects(:after_schedule_example).with(:foo)
12
+ Resque.enqueue_at(enqueue_time.to_i, SomeRealClass, :foo)
13
+ assert_equal(1, Resque.delayed_timestamp_size(enqueue_time.to_i), "job should be enqueued")
42
14
  end
43
15
 
44
- test "before_schedule hook that returns false should block" do
45
- enqueue_time = Time.now + 60
46
- assert_equal(0, JobThatCannotBeScheduledWithoutArguments.clean.counters[:before_schedule], 'before_schedule should be zero')
47
- Resque.enqueue_at(enqueue_time, JobThatCannotBeScheduledWithoutArguments.clean)
48
- assert_equal(0, Resque.delayed_timestamp_size(enqueue_time.to_i), "job should not have been put in queue")
49
- assert_equal(1, JobThatCannotBeScheduledWithoutArguments.counters[:before_schedule], 'before_schedule was not run')
50
- assert_equal(0, JobThatCannotBeScheduledWithoutArguments.counters[:after_schedule], 'after_schedule was run')
16
+ test "before_schedule hook that returns false should not be enqueued" do
17
+ enqueue_time = Time.now
18
+ SomeRealClass.expects(:before_schedule_example).with(:foo).returns(false)
19
+ SomeRealClass.expects(:after_schedule_example).never
20
+ Resque.enqueue_at(enqueue_time.to_i, SomeRealClass, :foo)
21
+ assert_equal(0, Resque.delayed_timestamp_size(enqueue_time.to_i), "job should not be enqueued")
51
22
  end
52
23
  end
@@ -25,7 +25,9 @@ context "Resque::Scheduler" do
25
25
  config = {'cron' => "* * * * *", 'class' => 'SomeRealClass', 'args' => "/tmp"}
26
26
 
27
27
  Resque::Job.expects(:create).with(SomeRealClass.queue, SomeRealClass, '/tmp')
28
- SomeRealClass.expects(:after_enqueue_example)
28
+ SomeRealClass.expects(:before_delayed_enqueue_example).with("/tmp")
29
+ SomeRealClass.expects(:before_enqueue_example).with("/tmp")
30
+ SomeRealClass.expects(:after_enqueue_example).with("/tmp")
29
31
 
30
32
  Resque::Scheduler.enqueue_from_config(config)
31
33
  end
@@ -69,6 +71,24 @@ context "Resque::Scheduler" do
69
71
  assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
70
72
  assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
71
73
  end
74
+
75
+ test "load_schedule_job with every with options" do
76
+ Resque::Scheduler.load_schedule_job("some_ivar_job", {'every' => ['30s', {'first_in' => '60s'}], 'class' => 'SomeIvarJob', 'args' => "/tmp"})
77
+
78
+ assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
79
+ assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
80
+ assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
81
+ assert Resque::Scheduler.scheduled_jobs["some_ivar_job"].params.keys.include?(:first_in)
82
+ end
83
+
84
+ test "load_schedule_job with cron with options" do
85
+ Resque::Scheduler.load_schedule_job("some_ivar_job", {'cron' => ['* * * * *', {'allow_overlapping' => 'true'}], 'class' => 'SomeIvarJob', 'args' => "/tmp"})
86
+
87
+ assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
88
+ assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
89
+ assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
90
+ assert Resque::Scheduler.scheduled_jobs["some_ivar_job"].params.keys.include?(:allow_overlapping)
91
+ end
72
92
 
73
93
  test "load_schedule_job without cron" do
74
94
  Resque::Scheduler.load_schedule_job("some_ivar_job", {'class' => 'SomeIvarJob', 'args' => "/tmp"})
@@ -165,6 +185,24 @@ context "Resque::Scheduler" do
165
185
  Resque.decode(Resque.redis.hget(:schedules, "my_ivar_job")))
166
186
  end
167
187
 
188
+ test "schedule= uses job name as 'class' argument if it's missing" do
189
+ Resque::Scheduler.dynamic = true
190
+ Resque.schedule = {"SomeIvarJob" => {
191
+ 'cron' => "* * * * *", 'args' => "/tmp/75"
192
+ }}
193
+ assert_equal({'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/75"},
194
+ Resque.decode(Resque.redis.hget(:schedules, "SomeIvarJob")))
195
+ assert_equal('SomeIvarJob', Resque.schedule['SomeIvarJob']['class'])
196
+ end
197
+
198
+ test "schedule= does not mutate argument" do
199
+ schedule = {"SomeIvarJob" => {
200
+ 'cron' => "* * * * *", 'args' => "/tmp/75"
201
+ }}
202
+ Resque.schedule = schedule
203
+ assert !schedule['SomeIvarJob'].key?('class')
204
+ end
205
+
168
206
  test "set_schedule can set an individual schedule" do
169
207
  Resque.set_schedule("some_ivar_job", {
170
208
  'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/22"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.g
4
+ version: 2.0.0.h
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-24 00:00:00.000000000Z
12
+ date: 2012-03-19 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &2160604100 !ruby/object:Gem::Requirement
16
+ requirement: &2169473600 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2160604100
24
+ version_requirements: *2169473600
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: redis
27
- requirement: &2160602220 !ruby/object:Gem::Requirement
27
+ requirement: &2169465640 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 2.0.1
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2160602220
35
+ version_requirements: *2169465640
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: resque
38
- requirement: &2160601740 !ruby/object:Gem::Requirement
38
+ requirement: &2169464480 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.19.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *2160601740
46
+ version_requirements: *2169464480
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rufus-scheduler
49
- requirement: &2160601260 !ruby/object:Gem::Requirement
49
+ requirement: &2169462660 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *2160601260
57
+ version_requirements: *2169462660
58
58
  description: ! "Light weight job scheduling on top of Resque.\n Adds methods enqueue_at/enqueue_in
59
59
  to schedule jobs in the future.\n Also supports queueing jobs on a fixed, cron-like
60
60
  schedule."
@@ -72,6 +72,7 @@ files:
72
72
  - Rakefile
73
73
  - lib/resque/scheduler.rb
74
74
  - lib/resque_scheduler.rb
75
+ - lib/resque_scheduler/plugin.rb
75
76
  - lib/resque_scheduler/server.rb
76
77
  - lib/resque_scheduler/server/views/delayed.erb
77
78
  - lib/resque_scheduler/server/views/delayed_timestamp.erb
@@ -101,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
102
  version: '0'
102
103
  segments:
103
104
  - 0
104
- hash: -2970363216344829182
105
+ hash: -2739279222393439195
105
106
  required_rubygems_version: !ruby/object:Gem::Requirement
106
107
  none: false
107
108
  requirements:
@@ -110,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
111
  version: 1.3.6
111
112
  requirements: []
112
113
  rubyforge_project:
113
- rubygems_version: 1.8.5
114
+ rubygems_version: 1.8.17
114
115
  signing_key:
115
116
  specification_version: 3
116
117
  summary: Light weight job scheduling on top of Resque