rufus-scheduler 2.0.23 → 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 +12 -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/{sc → scheduler}/cronline.rb +46 -17
  7. data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
  8. data/lib/rufus/scheduler/jobs.rb +548 -0
  9. data/lib/rufus/scheduler/util.rb +318 -0
  10. data/lib/rufus/scheduler.rb +502 -26
  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 -65
  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/Rakefile CHANGED
@@ -1,8 +1,5 @@
1
1
 
2
- $:.unshift('.') # 1.9.2
3
-
4
2
  require 'rubygems'
5
- require 'rubygems/user_interaction' if Gem::RubyGemsVersion == '1.5.0'
6
3
 
7
4
  require 'rake'
8
5
  require 'rake/clean'
@@ -42,7 +39,7 @@ desc %{
42
39
  task :build do
43
40
 
44
41
  sh "gem build #{GEMSPEC_FILE}"
45
- sh "mkdir pkg" rescue nil
42
+ sh "mkdir -p pkg"
46
43
  sh "mv #{GEMSPEC.name}-#{GEMSPEC.version}.gem pkg/"
47
44
  end
48
45
 
data/TODO.txt CHANGED
@@ -1,57 +1,147 @@
1
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
-
52
- [o] unify cron_jobs#trigger_matching_jobs(now) and jobs#job_to_trigger
53
- [o] pluggable job queues
54
-
55
- [ ] Joel's complaint about timeout jobs gone ballistic
56
- [x] move trigger_job out of the scheduler
2
+ [o] merge schedule_queue and unschedule_queue (and merge [un]schedule steps)
3
+ [x] OR stop using queue, since we've got the thread-safe JobArray
4
+ [x] if possible, drop the mutex in JobArray
5
+ NO, that mutex is necessary for Scheduler#jobs (on JRuby an co)...
6
+ [o] named mutexes
7
+ [o] drop the schedule queue, rely on the mutex in JobArray
8
+ [o] def jobs; (@jobs.to_a + running_jobs).uniq; end
9
+ [o] replace @unscheduled by @unscheduled_at
10
+ [o] make sure #jobs doesn't return unscheduled jobs
11
+ [o] job tags and find_by_tag(t) (as in rs 2.x)
12
+ [o] require tzinfo anyway (runtime dep)
13
+ [o] document frequency
14
+ [o] accept :frequency => '5s'
15
+ [o] timeout (as in rufus-scheduler 2.x)
16
+ [o] Rufus::Scheduler#running_jobs (as in rufus-scheduler 2.x)
17
+ [o] Rufus::Scheduler#terminate_all_jobs
18
+ [o] Rufus::Scheduler::Job#kill
19
+ [x] Rufus::Scheduler#kill_all_jobs
20
+ [o] Rufus::Scheduler#shutdown(:terminate or :kill (or nothing))
21
+ [o] RepeatJob #pause / #resume (think about discard past)
22
+ [o] Rufus::Scheduler.start_new (backward comp) (with deprec note?)
23
+ [o] pass job to scheduled block? What does rs 2.x do?
24
+ [o] :first[_in|_at] for RepeatJob
25
+ [o] :last[_in|_at] for RepeatJob
26
+ [o] :times for RepeatJob (how many recurrences)
27
+ [o] fix issue #39 (first_at parses as UTC)
28
+ [o] about issue #43, raise if cron/every job frequency < scheduler frequency
29
+ [o] unlock spec/parse_spec.rb:30 "parse datimes with timezones"
30
+ [o] some kind of Schedulable (Xyz#call(job, time))
31
+ [o] add Jruby and Rubinius to Travis
32
+ [o] make Job #first_at= / #last_at= automatically parse strings?
33
+ [o] bring in Kratob's spec about mutex vs timeout and adapt 3.0 to it,
34
+ https://github.com/jmettraux/rufus-scheduler/pull/67
35
+ [x] :unschedule_if => lambda { |job| ... }
36
+ [o] OR look at how it was done in rs 2.0.x, some return value?
37
+ no, pass the job as arg to the block, then let the block do job.unschedule
38
+ so, document schedule.every('10d') { |j| j.unschedule if x?() }
39
+ [x] remove the time in job.trigger(time)
40
+ [o] add spec for job queued then unscheduled
41
+ [o] add spec for Scheduler#shutdown and work threads
42
+ [o] at some point, bring back rbx19 to Travis
43
+ [o] move the parse/util part of scheduler.rb to util.rb
44
+ [o] rescue KillSignal in job thread loop to kill just the job
45
+ [o] add spec for raise if scheduling a job while scheduler is shutting down
46
+ [o] schedule_in(2.days.from_now) {}
47
+ at and in could understand each others time parameter, ftw...
48
+ use the new #parse_to_time? no
49
+ [o] do repeat jobs reschedule after timing out? yes
50
+ [o] schedule_interval('20s')?
51
+ [x] Scheduler#reschedule(job) (new copy of the job)
52
+ [x] #free_all_work_threads is missing an implementation
53
+ [x] rescue StandardError
54
+ :on_error => :crash[_scheduler]
55
+ :on_error => :ignore
56
+ :on_error => ...
57
+ [o] on_error: what about TimeoutError in that scheme?
58
+ TimeoutError goes to $stderr, like a normal error
59
+ [o] link to SO for support
60
+ - sublink to "how to report bugs effectively"
61
+ [o] link to #ruote for support
62
+ [x] lockblock? pass a block to teach the scheduler how to lock?
63
+ is not necessary, @scheduler = Scheduler.new if should_start?
64
+ the surrounding Ruby code checks
65
+ [o] introduce job "vars", as in
66
+ http://stackoverflow.com/questions/18202848/how-to-have-a-variable-that-will-available-to-particular-scheduled-task-whenever
67
+ or job['key'] Job #[] and #[]=, as with Thread #[] #[]=
68
+ job-local variables #keys #key?
69
+ [o] thread-safety for job-local variables?
70
+ [x] discard past? discard_past => true or => "1d"
71
+ default would be discard_past => "1m" or scheduler freq * 2 ?
72
+ jobs would adjust their next_time until it fits the window...
73
+ ~~ discard past by default
74
+ [o] expanded block/schedulable (it's "callable")
75
+ ```
76
+ scheduler.every '10m' do
77
+ def pre
78
+ return false if Backend.down?
79
+ # ...
80
+ end
81
+ def post
82
+ # ...
83
+ end
84
+ def trigger
85
+ puts "oh hai!"
86
+ end
87
+ end
88
+ ```
89
+ or something like that...
90
+ ...
91
+ OR accept a class (and instantiate it the first time)
92
+ ```
93
+ scheduler.every '10m', Class.new do
94
+ def call(job, time)
95
+ # ...
96
+ end
97
+ end
98
+ ```
99
+ the job contains the instance in its @callable
100
+ [x] add spec case for corner case in Job#trigger (overlap vs reschedule) !!!
101
+ [o] rethink job array vs job set for #scheduled?
102
+ [x] introduce common parent class for EveryJob and IntervalJob
103
+ [o] create spec/ at_job_spec.rb, repeat_job_spec.rb, cron_job_spec.rb, ...
104
+ [x] ensure EveryJob do not schedule in the past (it's already like that)
105
+ [o] CronLine#next_time should return a time with subseconds chopped off
106
+ [o] drop min work threads setting?
107
+ [o] thread pool something? Thread upper limit?
108
+ [o] Rufus::Scheduler.singleton, Rufus::Scheduler.s
109
+ [o] EveryJob#first_at= and IntervalJob#first_at= should alter @next_time
110
+ [o] scheduler.schedule duration/time/cron ... for at/in/cron
111
+ (not every, nor interval)
112
+ scheduler.repeat time/cron ... for every/cron
113
+
114
+ [o] :lockfile => x, timestamp, process_id, thread_id...
115
+ warning: have to clean up that file on exit... or does the scheduler
116
+ timestamps it?
117
+ [ ] develop lockfile timestamp thinggy
118
+ ~ if the timestamp is too old (twice the default frequency?) then
119
+ lock [file] take over...
120
+ Is that really what we want all the time?
121
+
122
+ [ ] idea: :mutex => x and :skip_on_mutex => true ?
123
+ would prevent blocking/waiting for the mutex to get available
124
+ :mutex => [ "mutex_name", true ]
125
+ :mutex => [ [ "mutex_name", true ], [ "other_mutex_name", false ] ]
126
+
127
+ [ ] bring back EM (but only EM.defer ?) :defer => true (Job or Scheduler
128
+ or both option?)
129
+
130
+ [ ] prepare a daemon, trust daemon-kit for that
131
+
132
+ [ ] :if => lambda { |job, time| ... } why not?
133
+ :unless => lambda { ...
134
+ :block => lambda { ...
135
+ can help get the block themselves leaner
136
+ #
137
+ investigate guards for schedulables... def if_guard; ...; end
138
+
139
+ [ ] scheduler.every '10', Class.new do
140
+ def call(job, time)
141
+ # might fail...
142
+ end
143
+ def on_error(err, job)
144
+ # catches...
145
+ end
146
+ end
57
147
 
@@ -22,10 +22,8 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- require 'tzinfo'
26
25
 
27
-
28
- module Rufus
26
+ class Rufus::Scheduler
29
27
 
30
28
  #
31
29
  # A 'cron line' is a line in the sense of a crontab
@@ -33,9 +31,6 @@ module Rufus
33
31
  #
34
32
  class CronLine
35
33
 
36
- DAY_S = 24 * 3600
37
- WEEK_S = 7 * DAY_S
38
-
39
34
  # The string used for creating this cronline instance.
40
35
  #
41
36
  attr_reader :original
@@ -51,7 +46,9 @@ module Rufus
51
46
 
52
47
  def initialize(line)
53
48
 
54
- super()
49
+ raise ArgumentError.new(
50
+ "not a string: #{line.inspect}"
51
+ ) unless line.is_a?(String)
55
52
 
56
53
  @original = line
57
54
 
@@ -123,12 +120,15 @@ module Rufus
123
120
  #
124
121
  # (Thanks to K Liu for the note and the examples)
125
122
  #
126
- def next_time(now=Time.now)
123
+ def next_time(from=Time.now)
124
+
125
+ time = @timezone ? @timezone.utc_to_local(from.getutc) : from
127
126
 
128
- time = @timezone ? @timezone.utc_to_local(now.getutc) : now
127
+ time = time.respond_to?(:round) ? time.round : time - time.usec * 1e-6
128
+ # chop off subseconds (and yes, Ruby 1.8 doesn't have #round)
129
129
 
130
- time = time - time.usec * 1e-6 + 1
131
- # small adjustment before starting
130
+ time = time + 1
131
+ # start at the next second
132
132
 
133
133
  loop do
134
134
 
@@ -150,7 +150,7 @@ module Rufus
150
150
 
151
151
  if @timezone
152
152
  time = @timezone.local_to_utc(time)
153
- time = time.getlocal unless now.utc?
153
+ time = time.getlocal unless from.utc?
154
154
  end
155
155
 
156
156
  time
@@ -159,19 +159,19 @@ module Rufus
159
159
  # Returns the previous the cronline matched. It's like next_time, but
160
160
  # for the past.
161
161
  #
162
- def previous_time(now=Time.now)
162
+ def previous_time(from=Time.now)
163
163
 
164
164
  # looks back by slices of two hours,
165
165
  #
166
166
  # finds for '* * * * sun', '* * 13 * *' and '0 12 13 * *'
167
167
  # starting 1970, 1, 1 in 1.8 to 2 seconds (says Rspec)
168
168
 
169
- start = current = now - 2 * 3600
169
+ start = current = from - 2 * 3600
170
170
  result = nil
171
171
 
172
172
  loop do
173
173
  nex = next_time(current)
174
- return (result ? result : previous_time(start)) if nex > now
174
+ return (result ? result : previous_time(start)) if nex > from
175
175
  result = current = nex
176
176
  end
177
177
 
@@ -196,9 +196,38 @@ module Rufus
196
196
  ]
197
197
  end
198
198
 
199
- private
199
+ # Returns the shortest delta between two potential occurences of the
200
+ # schedule described by this cronline.
201
+ #
202
+ def frequency
203
+
204
+ delta = 366 * DAY_S
205
+
206
+ t0 = previous_time(Time.local(2000, 1, 1))
207
+
208
+ loop do
209
+
210
+ break if delta <= 1
211
+ break if delta <= 60 && @seconds && @seconds.size == 1
212
+
213
+ t1 = next_time(t0)
214
+ d = t1 - t0
215
+ delta = d if d < delta
216
+
217
+ break if @months == nil && t1.month == 2
218
+ break if t1.year == 2001
219
+
220
+ t0 = t1
221
+ end
222
+
223
+ delta
224
+ end
225
+
226
+ protected
200
227
 
201
228
  WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
229
+ DAY_S = 24 * 3600
230
+ WEEK_S = 7 * DAY_S
202
231
 
203
232
  def parse_weekdays(item)
204
233
 
@@ -281,7 +310,7 @@ module Rufus
281
310
 
282
311
  raise ArgumentError.new(
283
312
  "#{item.inspect} is not in range #{min}..#{max}"
284
- ) if sta < min or edn > max
313
+ ) if sta < min || edn > max
285
314
 
286
315
  r = []
287
316
  val = sta
@@ -22,11 +22,63 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
-
26
25
  module Rufus
27
- module Scheduler
28
26
 
29
- VERSION = '2.0.23'
30
- end
27
+ class Scheduler
28
+
29
+ #
30
+ # The array rufus-scheduler uses to keep jobs in order (next to trigger
31
+ # first).
32
+ #
33
+ class JobArray
34
+
35
+ def initialize
36
+
37
+ @mutex = Mutex.new
38
+ @array = []
39
+ end
40
+
41
+ def push(job)
42
+
43
+ @mutex.synchronize { @array << job unless @array.index(job) }
44
+
45
+ self
46
+ end
47
+
48
+ def size
49
+
50
+ @array.size
51
+ end
52
+
53
+ def each(now, &block)
54
+
55
+ to_a.sort_by { |j| j.next_time || (now + 1) }.each do |job|
56
+
57
+ break unless job.next_time
58
+ break if job.next_time > now
59
+
60
+ block.call(job)
61
+ end
62
+ end
63
+
64
+ def delete_unscheduled
65
+
66
+ @mutex.synchronize {
67
+
68
+ @array.delete_if { |j| j.next_time.nil? || j.unscheduled_at }
69
+ }
70
+ end
71
+
72
+ def to_a
73
+
74
+ @mutex.synchronize { @array.dup }
75
+ end
76
+
77
+ def [](job_id)
78
+
79
+ @mutex.synchronize { @array.find { |j| j.job_id == job_id } }
80
+ end
81
+ end
82
+ end
31
83
  end
32
84