rufus-scheduler 2.0.24 → 3.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.
Files changed (53) hide show
  1. data/CHANGELOG.txt +6 -0
  2. data/CREDITS.txt +4 -0
  3. data/README.md +1064 -0
  4. data/Rakefile +1 -4
  5. data/TODO.txt +145 -55
  6. data/lib/rufus/scheduler.rb +502 -26
  7. data/lib/rufus/{sc → scheduler}/cronline.rb +46 -17
  8. data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
  9. data/lib/rufus/scheduler/jobs.rb +548 -0
  10. data/lib/rufus/scheduler/util.rb +318 -0
  11. data/rufus-scheduler.gemspec +30 -4
  12. data/spec/cronline_spec.rb +29 -8
  13. data/spec/error_spec.rb +116 -0
  14. data/spec/job_array_spec.rb +39 -0
  15. data/spec/job_at_spec.rb +58 -0
  16. data/spec/job_cron_spec.rb +67 -0
  17. data/spec/job_every_spec.rb +71 -0
  18. data/spec/job_in_spec.rb +20 -0
  19. data/spec/job_interval_spec.rb +68 -0
  20. data/spec/job_repeat_spec.rb +308 -0
  21. data/spec/job_spec.rb +387 -115
  22. data/spec/lockfile_spec.rb +61 -0
  23. data/spec/parse_spec.rb +203 -0
  24. data/spec/schedule_at_spec.rb +129 -0
  25. data/spec/schedule_cron_spec.rb +66 -0
  26. data/spec/schedule_every_spec.rb +109 -0
  27. data/spec/schedule_in_spec.rb +80 -0
  28. data/spec/schedule_interval_spec.rb +128 -0
  29. data/spec/scheduler_spec.rb +831 -124
  30. data/spec/spec_helper.rb +65 -0
  31. data/spec/threads_spec.rb +75 -0
  32. metadata +64 -59
  33. data/README.rdoc +0 -661
  34. data/lib/rufus/otime.rb +0 -3
  35. data/lib/rufus/sc/jobqueues.rb +0 -160
  36. data/lib/rufus/sc/jobs.rb +0 -471
  37. data/lib/rufus/sc/rtime.rb +0 -363
  38. data/lib/rufus/sc/scheduler.rb +0 -636
  39. data/spec/at_in_spec.rb +0 -47
  40. data/spec/at_spec.rb +0 -125
  41. data/spec/blocking_spec.rb +0 -64
  42. data/spec/cron_spec.rb +0 -134
  43. data/spec/every_spec.rb +0 -304
  44. data/spec/exception_spec.rb +0 -113
  45. data/spec/in_spec.rb +0 -150
  46. data/spec/mutex_spec.rb +0 -159
  47. data/spec/rtime_spec.rb +0 -137
  48. data/spec/schedulable_spec.rb +0 -97
  49. data/spec/spec_base.rb +0 -87
  50. data/spec/stress_schedule_unschedule_spec.rb +0 -159
  51. data/spec/timeout_spec.rb +0 -148
  52. data/test/kjw.rb +0 -113
  53. data/test/t.rb +0 -20
data/lib/rufus/otime.rb DELETED
@@ -1,3 +0,0 @@
1
-
2
- require 'rufus/sc/rtime.rb'
3
-
@@ -1,160 +0,0 @@
1
- #--
2
- # Copyright (c) 2006-2013, 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
- # Triggers all the jobs that are scheduled for 'now'.
54
- #
55
- def trigger_matching_jobs
56
-
57
- now = Time.now
58
-
59
- while job = job_to_trigger(now)
60
- job.trigger
61
- end
62
- end
63
-
64
- # Adds this job to the map.
65
- #
66
- def <<(job)
67
-
68
- @mutex.synchronize do
69
- delete(job.job_id)
70
- @jobs << job
71
- @jobs.sort! { |j0, j1| j0.at <=> j1.at }
72
- end
73
- end
74
-
75
- # Removes a job (given its id). Returns nil if the job was not found.
76
- #
77
- def unschedule(job_id)
78
-
79
- @mutex.synchronize { delete(job_id) }
80
- end
81
-
82
- # Returns a mapping job_id => job
83
- #
84
- def to_h
85
-
86
- @jobs.inject({}) { |h, j| h[j.job_id] = j; h }
87
- end
88
-
89
- # Returns a list of jobs of the given type (:at|:in|:every)
90
- #
91
- def select(type)
92
-
93
- type = JOB_TYPES[type]
94
- @jobs.select { |j| j.is_a?(type) }
95
- end
96
-
97
- def size
98
-
99
- @jobs.size
100
- end
101
-
102
- protected
103
-
104
- def delete(job_id)
105
-
106
- if job = @jobs.find { |j| j.job_id == job_id }
107
- @jobs.delete(job)
108
- end
109
- end
110
-
111
- # Returns the next job to trigger. Returns nil if none eligible.
112
- #
113
- def job_to_trigger(now)
114
-
115
- @mutex.synchronize do
116
- if @jobs.size > 0 && now.to_f >= @jobs.first.at
117
- @jobs.shift
118
- else
119
- nil
120
- end
121
- end
122
- end
123
- end
124
-
125
- #
126
- # Tracking cron jobs.
127
- #
128
- class CronJobQueue < JobQueue
129
-
130
- def initialize
131
-
132
- super
133
- @last_cron_second = nil
134
- end
135
-
136
- def trigger_matching_jobs
137
-
138
- now = Time.now
139
-
140
- return if now.sec == @last_cron_second
141
- @last_cron_second = now.sec
142
- #
143
- # ensuring the crons are checked within 1 second (not 1.2 second)
144
-
145
- jobs = @mutex.synchronize { @jobs.dup }
146
-
147
- jobs.each { |job| job.trigger_if_matches(now) }
148
- end
149
-
150
- def <<(job)
151
-
152
- @mutex.synchronize do
153
- delete(job.job_id)
154
- @jobs << job
155
- end
156
- end
157
- end
158
- end
159
- end
160
-
data/lib/rufus/sc/jobs.rb DELETED
@@ -1,471 +0,0 @@
1
- #--
2
- # Copyright (c) 2006-2013, 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_accessor :scheduler
37
-
38
- # The initial, raw, scheduling info (at / in / every / cron)
39
- #
40
- attr_reader :t
41
-
42
- # Returns the thread instance of the last triggered job.
43
- # May be null (especially before the first trigger).
44
- #
45
- attr_reader :last_job_thread
46
-
47
- # The job parameters (passed via the schedule method)
48
- #
49
- attr_reader :params
50
-
51
- # The block to call when triggering
52
- #
53
- attr_reader :block
54
-
55
- # Last time the job executed
56
- # (for an {At|In}Job, it will mean 'not executed' if nil or when
57
- # it got executed if set)
58
- #
59
- # (
60
- # Last time job got triggered (most useful with EveryJob, but can be
61
- # useful with remaining instances of At/InJob (are they done ?))
62
- # )
63
- #
64
- attr_reader :last
65
-
66
- # The identifier for this job.
67
- #
68
- attr_reader :job_id
69
-
70
- # Instantiating the job.
71
- #
72
- def initialize(scheduler, t, params, &block)
73
-
74
- @scheduler = scheduler
75
- @t = t
76
- @params = params
77
- @block = block || params[:schedulable]
78
-
79
- raise_on_unknown_params
80
-
81
- @running = false
82
- @paused = false
83
-
84
- raise ArgumentError.new(
85
- 'no block or :schedulable passed, nothing to schedule'
86
- ) unless @block
87
-
88
- @params[:tags] = Array(@params[:tags])
89
-
90
- @job_id = params[:job_id] || "#{self.class.name}_#{self.object_id.to_s}"
91
-
92
- determine_at
93
- end
94
-
95
- # Returns true if this job is currently running (in the middle of #trigger)
96
- #
97
- # Note : paused? is not related to running?
98
- #
99
- def running
100
-
101
- @running
102
- end
103
-
104
- alias running? running
105
-
106
- # Returns true if this job is paused, false else.
107
- #
108
- # A paused job is still scheduled, but does not trigger.
109
- #
110
- # Note : paused? is not related to running?
111
- #
112
- def paused?
113
-
114
- @paused
115
- end
116
-
117
- # Pauses this job (sets the paused flag to true).
118
- #
119
- # Note that it will not pause the execution of a block currently 'running'.
120
- # Future triggering of the job will not occur until #resume is called.
121
- #
122
- # Note too that, during the pause time, the schedule kept the same. Calling
123
- # #resume will not force old triggers in.
124
- #
125
- def pause
126
-
127
- @paused = true
128
- end
129
-
130
- # Resumes this job (sets the paused flag to false).
131
- #
132
- # This job will trigger again.
133
- #
134
- def resume
135
-
136
- @paused = false
137
- end
138
-
139
- # Returns the list of tags attached to the job.
140
- #
141
- def tags
142
-
143
- @params[:tags]
144
- end
145
-
146
- # Sets the list of tags attached to the job (Usually they are set
147
- # via the schedule every/at/in/cron method).
148
- #
149
- def tags=(tags)
150
-
151
- @params[:tags] = Array(tags)
152
- end
153
-
154
- # Generally returns the string/float/integer used to schedule the job
155
- # (seconds, time string, date string)
156
- #
157
- def schedule_info
158
-
159
- @t
160
- end
161
-
162
- # Triggers the job.
163
- #
164
- def trigger(t=Time.now)
165
-
166
- return if @paused
167
-
168
- @last = t
169
- job_thread = nil
170
- to_job = nil
171
-
172
- return if @running and (params[:allow_overlapping] == false)
173
-
174
- @running = true
175
-
176
- @scheduler.send(:trigger_job, @params) do
177
- #
178
- # Note that #trigger_job is protected, hence the #send
179
- # (Only jobs know about this method of the scheduler)
180
-
181
- job_thread = Thread.current
182
-
183
- job_thread[
184
- "rufus_scheduler__trigger_thread__#{@scheduler.object_id}"
185
- ] = self
186
-
187
- @last_job_thread = job_thread
188
-
189
- # note that add_job and add_cron_job ensured that :blocking is
190
- # not used along :timeout
191
-
192
- if to = @params[:timeout]
193
-
194
- to_job = @scheduler.in(to, :parent => self, :tags => 'timeout') do
195
-
196
- if job_thread && job_thread.alive?
197
- job_thread.raise(Rufus::Scheduler::TimeOutError)
198
- end
199
- end
200
- end
201
-
202
- begin
203
-
204
- trigger_block
205
-
206
- job_thread[
207
- "rufus_scheduler__trigger_thread__#{@scheduler.object_id}"
208
- ] = nil
209
-
210
- job_thread = nil
211
-
212
- to_job.unschedule if to_job
213
-
214
- rescue (@scheduler.options[:exception] || Exception) => e
215
-
216
- @scheduler.do_handle_exception(self, e)
217
- end
218
-
219
- @running = false
220
- end
221
-
222
- end
223
-
224
- # Simply encapsulating the block#call/trigger operation, for easy
225
- # override.
226
- #
227
- def trigger_block
228
-
229
- @block.respond_to?(:call) ?
230
- @block.call(self) : @block.trigger(@params.merge(:job => self))
231
- end
232
-
233
- # Unschedules this job.
234
- #
235
- def unschedule
236
-
237
- @scheduler.unschedule(self.job_id)
238
- end
239
-
240
- protected
241
-
242
- def known_params
243
-
244
- [ :allow_overlapping,
245
- :blocking,
246
- :discard_past,
247
- :job_id,
248
- :mutex,
249
- :schedulable,
250
- :tags,
251
- :timeout ]
252
- end
253
-
254
- def self.known_params(*args)
255
-
256
- define_method :known_params do
257
- super() + args
258
- end
259
- end
260
-
261
- def raise_on_unknown_params
262
-
263
- rem = @params.keys - known_params
264
-
265
- raise(
266
- ArgumentError,
267
- "unknown option#{rem.size > 1 ? 's' : '' }: " +
268
- "#{rem.map(&:inspect).join(', ')}",
269
- caller[3..-1]
270
- ) if rem.any?
271
- end
272
- end
273
-
274
- #
275
- # The base class of at/in/every jobs.
276
- #
277
- class SimpleJob < Job
278
-
279
- # When the job is supposed to trigger
280
- #
281
- attr_reader :at
282
-
283
- # Last time it triggered
284
- #
285
- attr_reader :last
286
-
287
- def determine_at
288
-
289
- @at = Rufus.at_to_f(@t)
290
- end
291
-
292
- # Returns the next time (or the unique time) this job is meant to trigger
293
- #
294
- def next_time
295
-
296
- Time.at(@at)
297
- end
298
- end
299
-
300
- #
301
- # Job that occurs once, in a certain amount of time.
302
- #
303
- class InJob < SimpleJob
304
-
305
- known_params :parent
306
-
307
- # If this InJob is a timeout job, parent points to the job that
308
- # is subject to the timeout.
309
- #
310
- attr_reader :parent
311
-
312
- def initialize(scheduler, t, params)
313
-
314
- @parent = params[:parent]
315
- super
316
- end
317
-
318
- protected
319
-
320
- def determine_at
321
-
322
- iin = @t.is_a?(Fixnum) || @t.is_a?(Float) ?
323
- @t : Rufus.parse_duration_string(@t)
324
-
325
- @at = (Time.now + iin).to_f
326
- end
327
- end
328
-
329
- #
330
- # Job that occurs once, at a certain point in time.
331
- #
332
- class AtJob < SimpleJob
333
- end
334
-
335
- #
336
- # Recurring job with a certain frequency.
337
- #
338
- class EveryJob < SimpleJob
339
-
340
- known_params :first_in, :first_at
341
-
342
- # The frequency, in seconds, of this EveryJob
343
- #
344
- attr_reader :frequency
345
-
346
- def initialize(scheduler, t, params, &block)
347
-
348
- super
349
-
350
- determine_frequency
351
- determine_at
352
- end
353
-
354
- # Triggers the job (and reschedules it).
355
- #
356
- def trigger
357
-
358
- schedule_next
359
-
360
- super
361
- end
362
-
363
- protected
364
-
365
- def determine_frequency
366
-
367
- @frequency =
368
- if @t.is_a?(Fixnum) || @t.is_a?(Float)
369
- @t
370
- else
371
- Rufus.parse_duration_string(@t)
372
- end
373
-
374
- raise ArgumentError.new(
375
- 'cannot initialize an EveryJob with a <= 0.0 frequency'
376
- ) if @frequency <= 0.0
377
- end
378
-
379
- def determine_at
380
-
381
- return unless @frequency
382
-
383
- @last = @at
384
- # the first time, @last will be nil
385
-
386
- now = Time.now.to_f
387
-
388
- @at = if @last
389
- @last + @frequency
390
- else
391
- if fi = @params[:first_in]
392
- now + Rufus.duration_to_f(fi)
393
- elsif fa = @params[:first_at]
394
- Rufus.at_to_f(fa)
395
- else
396
- now + @frequency
397
- end
398
- end
399
-
400
- while @at < now do
401
- @at += @frequency
402
- end if @params[:discard_past]
403
- end
404
-
405
- # It's an every job, have to schedule next time it occurs...
406
- #
407
- def schedule_next
408
-
409
- determine_at
410
-
411
- @scheduler.send(:add_job, self)
412
- end
413
- end
414
-
415
- #
416
- # Recurring job, cron style.
417
- #
418
- class CronJob < Job
419
-
420
- # The CronLine instance, it holds all the info about the cron schedule
421
- #
422
- attr_reader :cron_line
423
-
424
- # The job parameters (passed via the schedule method)
425
- #
426
- attr_reader :params
427
-
428
- # The block to call when triggering
429
- #
430
- attr_reader :block
431
-
432
- # Creates a new CronJob instance.
433
- #
434
- def initialize(scheduler, cron_string, params, &block)
435
-
436
- super
437
-
438
- @cron_line = case @t
439
-
440
- when String then CronLine.new(@t)
441
- when CronLine then @t
442
-
443
- else raise ArgumentError.new(
444
- "cannot initialize a CronJob out of #{@t.inspect}")
445
- end
446
- end
447
-
448
- def trigger_if_matches(time)
449
-
450
- return if @paused
451
-
452
- trigger(time) if @cron_line.matches?(time)
453
- end
454
-
455
- # Returns the next time this job is meant to trigger
456
- #
457
- def next_time(from=Time.now)
458
-
459
- @cron_line.next_time(from)
460
- end
461
-
462
- protected
463
-
464
- def determine_at
465
-
466
- # empty
467
- end
468
- end
469
- end
470
- end
471
-