rufus-scheduler 2.0.24 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
-