rufus-scheduler 1.0.14 → 2.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.
@@ -0,0 +1,51 @@
1
+
2
+ [o] spec for jobs in the past (in and at)
3
+ [o] :discard_past
4
+
5
+ [o] every
6
+ [o] cron
7
+
8
+ [o] CHECK every and unschedule !!!
9
+
10
+ [o] :tags
11
+ [o] timeout feature (at/in/every/cron) in Job class
12
+
13
+ [o] :first_in, :first_at
14
+
15
+ [x] :dont_reschedule (or block returns false ?)
16
+
17
+ [o] [get_]jobs methods
18
+ [o] find methods
19
+
20
+ [x] CTRL-C during tests : allow, trap_int...
21
+
22
+ [o] 1.9
23
+ [o] j1.2.0
24
+
25
+ [o] revise trigger block arity
26
+ use a compatibility switch ? yes
27
+
28
+ [o] synchronize @cron_jobs ?
29
+
30
+ [o] why not : make it work even if EM is not present
31
+ EmScheduler < Scheduler
32
+ FiberScheduler < Scheduler
33
+
34
+ [x] :blocking => 'blockname' idea, mutex = @mutexes['blockname'] ...
35
+ [o] eventually, make sleep frequency customizable
36
+
37
+ [o] PlainScheduler : name thread
38
+
39
+ [o] document :blocking
40
+
41
+ [o] README.rdoc
42
+ [o] fix jruby120 --em
43
+
44
+ [o] handle_exception (job, e)
45
+
46
+ [o] Schedulable
47
+
48
+ [o] Rufus::Scheduler.start_new() : autodetect EM ?
49
+ [o] check :blocking and every (reschedule blocking...)
50
+ [o] document :thread_name scheduler option
51
+
@@ -1,3 +1,3 @@
1
1
 
2
- require 'rufus/scheduler/scheduler'
2
+ require 'rufus/sc/scheduler'
3
3
 
@@ -1,3 +1,3 @@
1
1
 
2
- require 'rufus/scheduler/otime'
2
+ require 'rufus/sc/rtime.rb'
3
3
 
@@ -52,45 +52,34 @@ module Rufus
52
52
 
53
53
  items = line.split
54
54
 
55
- unless [ 5, 6 ].include?(items.length)
56
- raise \
57
- "cron '#{line}' string should hold 5 or 6 items, " +
58
- "not #{items.length}" \
55
+ unless items.length == 5 or items.length == 6
56
+ raise(
57
+ "cron '#{line}' string should hold 5 or 6 items, not #{items.length}")
59
58
  end
60
59
 
61
60
  offset = items.length - 5
62
61
 
63
- @seconds = if offset == 1
64
- parse_item(items[0], 0, 59)
65
- else
66
- [ 0 ]
67
- end
68
- @minutes = parse_item(items[0+offset], 0, 59)
69
- @hours = parse_item(items[1+offset], 0, 24)
70
- @days = parse_item(items[2+offset], 1, 31)
71
- @months = parse_item(items[3+offset], 1, 12)
72
- @weekdays = parse_weekdays(items[4+offset])
73
-
74
- #adjust_arrays()
62
+ @seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
63
+ @minutes = parse_item(items[0 + offset], 0, 59)
64
+ @hours = parse_item(items[1 + offset], 0, 24)
65
+ @days = parse_item(items[2 + offset], 1, 31)
66
+ @months = parse_item(items[3 + offset], 1, 12)
67
+ @weekdays = parse_weekdays(items[4 + offset])
75
68
  end
76
69
 
77
70
  #
78
71
  # Returns true if the given time matches this cron line.
79
72
  #
80
- # (the precision is passed as well to determine if it's
81
- # worth checking seconds and minutes)
82
- #
83
73
  def matches? (time)
84
- #def matches? (time, precision)
85
74
 
86
75
  time = Time.at(time) unless time.kind_of?(Time)
87
76
 
88
- return false unless sub_match? time.sec, @seconds
89
- return false unless sub_match? time.min, @minutes
90
- return false unless sub_match? time.hour, @hours
91
- return false unless sub_match? time.day, @days
92
- return false unless sub_match? time.month, @months
93
- return false unless sub_match? time.wday, @weekdays
77
+ return false unless sub_match?(time.sec, @seconds)
78
+ return false unless sub_match?(time.min, @minutes)
79
+ return false unless sub_match?(time.hour, @hours)
80
+ return false unless sub_match?(time.day, @days)
81
+ return false unless sub_match?(time.month, @months)
82
+ return false unless sub_match?(time.wday, @weekdays)
94
83
  true
95
84
  end
96
85
 
@@ -128,27 +117,29 @@ module Rufus
128
117
  #
129
118
  # (Thanks to K Liu for the note and the examples)
130
119
  #
131
- def next_time time=Time.now
120
+ def next_time (time=Time.now)
121
+
132
122
  time -= time.usec * 1e-6
133
123
  time += 1
134
124
 
135
125
  loop do
136
- unless date_match? time
126
+
127
+ unless date_match?(time)
137
128
  time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
138
129
  next
139
130
  end
140
131
 
141
- unless sub_match? time.hour, @hours
132
+ unless sub_match?(time.hour, @hours)
142
133
  time += (60 - time.min) * 60 - time.sec
143
134
  next
144
135
  end
145
136
 
146
- unless sub_match? time.min, @minutes
137
+ unless sub_match?(time.min, @minutes)
147
138
  time += 60 - time.sec
148
139
  next
149
140
  end
150
141
 
151
- unless sub_match? time.sec, @seconds
142
+ unless sub_match?(time.sec, @seconds)
152
143
  time += 1
153
144
  next
154
145
  end
@@ -161,25 +152,6 @@ module Rufus
161
152
 
162
153
  private
163
154
 
164
- #--
165
- # adjust values to Ruby
166
- #
167
- #def adjust_arrays()
168
- # @hours = @hours.collect { |h|
169
- # if h == 24
170
- # 0
171
- # else
172
- # h
173
- # end
174
- # } if @hours
175
- # @weekdays = @weekdays.collect { |wd|
176
- # wd - 1
177
- # } if @weekdays
178
- #end
179
- #
180
- # dead code, keeping it as a reminder
181
- #++
182
-
183
155
  WDS = %w[ sun mon tue wed thu fri sat ]
184
156
  #
185
157
  # used by parse_weekday()
@@ -188,25 +160,20 @@ module Rufus
188
160
 
189
161
  item = item.downcase
190
162
 
191
- WDS.each_with_index do |day, index|
192
- item = item.gsub day, "#{index}"
193
- end
194
-
195
- r = parse_item item, 0, 7
163
+ WDS.each_with_index { |day, index| item = item.gsub(day, index.to_s) }
196
164
 
197
- return r unless r.is_a?(Array)
165
+ r = parse_item(item, 0, 7)
198
166
 
199
- r.collect { |e| e == 7 ? 0 : e }.uniq
167
+ r.is_a?(Array) ?
168
+ r.collect { |e| e == 7 ? 0 : e }.uniq :
169
+ r
200
170
  end
201
171
 
202
172
  def parse_item (item, min, max)
203
173
 
204
- return nil \
205
- if item == "*"
206
- return parse_list(item, min, max) \
207
- if item.index(",")
208
- return parse_range(item, min, max) \
209
- if item.index("*") or item.index("-")
174
+ return nil if item == '*'
175
+ return parse_list(item, min, max) if item.index(',')
176
+ return parse_range(item, min, max) if item.index('*') or item.index('-')
210
177
 
211
178
  i = Integer(item)
212
179
 
@@ -218,33 +185,31 @@ module Rufus
218
185
 
219
186
  def parse_list (item, min, max)
220
187
 
221
- items = item.split(",")
222
-
223
- items.inject([]) { |r, i| r.push(parse_range(i, min, max)) }.flatten
188
+ item.split(',').inject([]) { |r, i|
189
+ r.push(parse_range(i, min, max))
190
+ }.flatten
224
191
  end
225
192
 
226
193
  def parse_range (item, min, max)
227
194
 
228
- i = item.index("-")
229
- j = item.index("/")
195
+ i = item.index('-')
196
+ j = item.index('/')
230
197
 
231
198
  return item.to_i if (not i and not j)
232
199
 
233
- inc = 1
234
-
235
- inc = Integer(item[j+1..-1]) if j
200
+ inc = j ? Integer(item[j+1..-1]) : 1
236
201
 
237
202
  istart = -1
238
203
  iend = -1
239
204
 
240
205
  if i
241
206
 
242
- istart = Integer(item[0..i-1])
207
+ istart = Integer(item[0..i - 1])
243
208
 
244
209
  if j
245
- iend = Integer(item[i+1..j])
210
+ iend = Integer(item[i + 1..j])
246
211
  else
247
- iend = Integer(item[i+1..-1])
212
+ iend = Integer(item[i + 1..-1])
248
213
  end
249
214
 
250
215
  else # case */x
@@ -260,7 +225,6 @@ module Rufus
260
225
 
261
226
  value = istart
262
227
  loop do
263
-
264
228
  result << value
265
229
  value = value + inc
266
230
  break if value > iend
@@ -269,14 +233,14 @@ module Rufus
269
233
  result
270
234
  end
271
235
 
272
- def sub_match? value, values
236
+ def sub_match?(value, values)
273
237
  values.nil? || values.include?(value)
274
238
  end
275
239
 
276
- def date_match? date
277
- return false unless sub_match? date.day, @days
278
- return false unless sub_match? date.month, @months
279
- return false unless sub_match? date.wday, @weekdays
240
+ def date_match?(date)
241
+ return false unless sub_match?(date.day, @days)
242
+ return false unless sub_match?(date.month, @months)
243
+ return false unless sub_match?(date.wday, @weekdays)
280
244
  true
281
245
  end
282
246
  end
@@ -0,0 +1,157 @@
1
+ #--
2
+ # Copyright (c) 2006-2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ require 'thread'
27
+
28
+
29
+ module Rufus
30
+ module Scheduler
31
+
32
+ #
33
+ # Tracking at/in/every jobs.
34
+ #
35
+ # In order of trigger time.
36
+ #
37
+ class JobQueue
38
+
39
+ # Mapping :at|:in|:every to their respective job classes.
40
+ #
41
+ JOB_TYPES = {
42
+ :at => Rufus::Scheduler::AtJob,
43
+ :in => Rufus::Scheduler::InJob,
44
+ :every => Rufus::Scheduler::EveryJob
45
+ }
46
+
47
+ def initialize
48
+
49
+ @mutex = Mutex.new
50
+ @jobs = []
51
+ end
52
+
53
+ # Returns the next job to trigger. Returns nil if none eligible.
54
+ #
55
+ def job_to_trigger
56
+
57
+ @mutex.synchronize do
58
+ if @jobs.size > 0 && Time.now.to_f >= @jobs.first.at
59
+ @jobs.shift
60
+ else
61
+ nil
62
+ end
63
+ end
64
+ end
65
+
66
+ # Adds this job to the map.
67
+ #
68
+ def << (job)
69
+
70
+ @mutex.synchronize do
71
+ delete(job.job_id)
72
+ @jobs << job
73
+ @jobs.sort! { |j0, j1| j0.at <=> j1.at }
74
+ end
75
+ end
76
+
77
+ # Removes a job (given its id). Returns nil if the job was not found.
78
+ #
79
+ def unschedule (job_id)
80
+
81
+ @mutex.synchronize { delete(job_id) }
82
+ end
83
+
84
+ # Returns a mapping job_id => job
85
+ #
86
+ def to_h
87
+
88
+ @jobs.inject({}) { |h, j| h[j.job_id] = j; h }
89
+ end
90
+
91
+ # Returns a list of jobs of the given type (:at|:in|:every)
92
+ #
93
+ def select (type)
94
+
95
+ type = JOB_TYPES[type]
96
+ @jobs.select { |j| j.is_a?(type) }
97
+ end
98
+
99
+ def size
100
+
101
+ @jobs.size
102
+ end
103
+
104
+ protected
105
+
106
+ def delete (job_id)
107
+ j = @jobs.find { |j| j.job_id == job_id }
108
+ @jobs.delete(j) if j
109
+ j
110
+ end
111
+ end
112
+
113
+ #
114
+ # Tracking cron jobs.
115
+ #
116
+ # (mostly synchronizing access to the map of cron jobs)
117
+ #
118
+ class CronJobQueue
119
+
120
+ def initialize
121
+
122
+ @mutex = Mutex.new
123
+ @jobs = {}
124
+ end
125
+
126
+ def unschedule (job_id)
127
+
128
+ @mutex.synchronize { @jobs.delete(job_id) }
129
+ end
130
+
131
+ def trigger_matching_jobs (now)
132
+
133
+ js = @mutex.synchronize { @jobs.values }
134
+ # maybe this sync is a bit paranoid
135
+
136
+ js.each { |job| job.trigger_if_matches(now) }
137
+ end
138
+
139
+ def << (job)
140
+
141
+ @mutex.synchronize { @jobs[job.job_id] = job }
142
+ end
143
+
144
+ def size
145
+
146
+ @jobs.size
147
+ end
148
+
149
+ def to_h
150
+
151
+ @jobs.dup
152
+ end
153
+ end
154
+
155
+ end
156
+ end
157
+
@@ -0,0 +1,339 @@
1
+ #--
2
+ # Copyright (c) 2006-2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus
27
+ module Scheduler
28
+
29
+ #
30
+ # The base class for all types of jobs.
31
+ #
32
+ class Job
33
+
34
+ # A reference to the scheduler owning this job
35
+ #
36
+ attr_reader :scheduler
37
+
38
+ # The initial, raw, scheduling info (at / in / every / cron)
39
+ #
40
+ attr_reader :t
41
+
42
+ # When the job is actually running, this attribute will hold the
43
+ # thread in which the job is running.
44
+ # Can be used to determine if a job is actually running.
45
+ #
46
+ attr_reader :job_thread
47
+
48
+ # The job parameters (passed via the schedule method)
49
+ #
50
+ attr_reader :params
51
+
52
+ # The block to call when triggering
53
+ #
54
+ attr_reader :block
55
+
56
+ # Last time the job executed
57
+ # (for an {At|In}Job, it will mean 'not executed' if nil or when
58
+ # it got executed if set)
59
+ #
60
+ # (
61
+ # Last time job got triggered (most useful with EveryJob, but can be
62
+ # useful with remaining instances of At/InJob (are they done ?))
63
+ # )
64
+ #
65
+ attr_reader :last
66
+
67
+ # The identifier for this job.
68
+ #
69
+ attr_reader :job_id
70
+
71
+
72
+ # Instantiating the job.
73
+ #
74
+ def initialize (scheduler, t, params, &block)
75
+
76
+ @scheduler = scheduler
77
+ @t = t
78
+ @params = params
79
+ @block = block || params[:schedulable]
80
+
81
+ raise ArgumentError.new(
82
+ 'no block or :schedulable passed, nothing to schedule'
83
+ ) unless @block
84
+
85
+ @params[:tags] = Array(@params[:tags])
86
+
87
+ @job_id = params[:job_id] || "#{self.class.name}_#{self.object_id.to_s}"
88
+
89
+ determine_at
90
+ end
91
+
92
+ # Returns the list of tags attached to the job.
93
+ #
94
+ def tags
95
+
96
+ @params[:tags]
97
+ end
98
+
99
+ # Sets the list of tags attached to the job (Usually they are set
100
+ # via the schedule every/at/in/cron method).
101
+ #
102
+ def tags= (tags)
103
+
104
+ @params[:tags] = Array(tags)
105
+ end
106
+
107
+ # Generally returns the string/float/integer used to schedule the job
108
+ # (seconds, time string, date string)
109
+ #
110
+ def schedule_info
111
+
112
+ @t
113
+ end
114
+
115
+ # Triggers the job.
116
+ #
117
+ def trigger (t=Time.now)
118
+
119
+ @last = t
120
+
121
+ @scheduler.send(:trigger_job, @params[:blocking]) do
122
+ #
123
+ # Note that #trigger_job is protected, hence the #send
124
+ # (Only jobs know about this method of the scheduler)
125
+
126
+ @job_thread = Thread.current
127
+
128
+ begin
129
+
130
+ #args = prepare_args
131
+ #@block.call(*args)
132
+
133
+ #@block.call(self)
134
+
135
+ @block.respond_to?(:call) ?
136
+ @block.call(self) : @block.trigger(@params)
137
+
138
+ @job_thread = nil
139
+
140
+ rescue Exception => e
141
+
142
+ @scheduler.handle_exception(self, e)
143
+ end
144
+ end
145
+
146
+ # note that add_job and add_cron_job ensured that :blocking is
147
+ # not used along :timeout
148
+
149
+ if to = @params[:timeout]
150
+
151
+ @scheduler.in(to, :tags => 'timeout') do
152
+
153
+ # at this point, @job_thread might be set
154
+
155
+ @job_thread.raise(Rufus::Scheduler::TimeOutError) \
156
+ if @job_thread and @job_thread.alive?
157
+ end
158
+ end
159
+ end
160
+
161
+ # Unschedules this job.
162
+ #
163
+ def unschedule
164
+
165
+ @scheduler.unschedule(self.job_id)
166
+ end
167
+
168
+ #--
169
+ #protected
170
+ #
171
+ # Prepare the args given the triggered block arity.
172
+ #
173
+ #def prepare_args
174
+ # if @scheduler.options[:onezero_block_arity]
175
+ # case @block.arity
176
+ # when 0 then []
177
+ # when 1 then [ @params ]
178
+ # when 2 then [ @job_id, @params ]
179
+ # #else [ @job_id, schedule_info, @params ]
180
+ # else [ @job_id, self, @params ]
181
+ # end
182
+ # else
183
+ # [ self ]
184
+ # end
185
+ #end
186
+ #++
187
+ end
188
+
189
+ #
190
+ # The base class of at/in/every jobs.
191
+ #
192
+ class SimpleJob < Job
193
+
194
+ # When the job is supposed to trigger
195
+ #
196
+ attr_reader :at
197
+
198
+ attr_reader :last
199
+
200
+ def determine_at
201
+
202
+ @at = Rufus.at_to_f(@t)
203
+ end
204
+ end
205
+
206
+ #
207
+ # Job that occurs once, in a certain amount of time.
208
+ #
209
+ class InJob < SimpleJob
210
+
211
+ protected
212
+
213
+ def determine_at
214
+
215
+ iin = @t.is_a?(Fixnum) || @t.is_a?(Float) ?
216
+ @t : Rufus.parse_duration_string(@t)
217
+
218
+ @at = (Time.now + iin).to_f
219
+ end
220
+ end
221
+
222
+ #
223
+ # Job that occurs once, at a certain point in time.
224
+ #
225
+ class AtJob < SimpleJob
226
+ end
227
+
228
+ #
229
+ # Recurring job with a certain frequency.
230
+ #
231
+ class EveryJob < SimpleJob
232
+
233
+ # The frequency, in seconds, of this EveryJob
234
+ #
235
+ attr_reader :frequency
236
+
237
+ def initialize (scheduler, t, params, &block)
238
+ super
239
+ determine_frequency
240
+ determine_at
241
+ end
242
+
243
+ # Triggers the job (and reschedules it).
244
+ #
245
+ def trigger
246
+
247
+ schedule_next
248
+
249
+ super
250
+
251
+ #unschedule if @params[:dont_reschedule]
252
+ # obsolete
253
+ end
254
+
255
+ protected
256
+
257
+ def determine_frequency
258
+
259
+ @frequency = @t.is_a?(Fixnum) || @t.is_a?(Float) ?
260
+ @t : Rufus.parse_duration_string(@t)
261
+ end
262
+
263
+ def determine_at
264
+
265
+ return unless @frequency
266
+
267
+ @last = @at
268
+ # the first time, @last will be nil
269
+
270
+ @at = if @last
271
+ @last + @frequency
272
+ else
273
+ if fi = @params[:first_in]
274
+ Time.now.to_f + Rufus.duration_to_f(fi)
275
+ elsif fa = @params[:first_at]
276
+ Rufus.at_to_f(fa)
277
+ else
278
+ Time.now.to_f + @frequency
279
+ end
280
+ end
281
+ end
282
+
283
+ # It's an every job, have to schedule next time it occurs...
284
+ #
285
+ def schedule_next
286
+
287
+ determine_at
288
+
289
+ @scheduler.send(:add_job, self)
290
+ end
291
+ end
292
+
293
+ #
294
+ # Recurring job, cron style.
295
+ #
296
+ class CronJob < Job
297
+
298
+ # The CronLine instance, it holds all the info about the cron schedule
299
+ #
300
+ attr_reader :cron_line
301
+
302
+ # The job parameters (passed via the schedule method)
303
+ #
304
+ attr_reader :params
305
+
306
+ # The block to call when triggering
307
+ #
308
+ attr_reader :block
309
+
310
+ # Creates a new CronJob instance.
311
+ #
312
+ def initialize (scheduler, cron_string, params, &block)
313
+
314
+ super
315
+
316
+ @cron_line = case @t
317
+
318
+ when String then CronLine.new(@t)
319
+ when CronLine then @t
320
+
321
+ else raise "cannot initialize a CronJob out of #{@t.inspect}"
322
+ end
323
+ end
324
+
325
+ def trigger_if_matches (time)
326
+
327
+ trigger(time) if @cron_line.matches?(time)
328
+ end
329
+
330
+ protected
331
+
332
+ def determine_at
333
+ # empty
334
+ end
335
+ end
336
+
337
+ end
338
+ end
339
+