rufus-scheduler 2.0.24 → 3.1.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 (63) hide show
  1. data/CHANGELOG.txt +76 -0
  2. data/CREDITS.txt +23 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +1439 -0
  5. data/Rakefile +1 -5
  6. data/TODO.txt +149 -55
  7. data/lib/rufus/{sc → scheduler}/cronline.rb +167 -53
  8. data/lib/rufus/scheduler/job_array.rb +92 -0
  9. data/lib/rufus/scheduler/jobs.rb +633 -0
  10. data/lib/rufus/scheduler/locks.rb +95 -0
  11. data/lib/rufus/scheduler/util.rb +306 -0
  12. data/lib/rufus/scheduler/zones.rb +174 -0
  13. data/lib/rufus/scheduler/zotime.rb +154 -0
  14. data/lib/rufus/scheduler.rb +608 -27
  15. data/rufus-scheduler.gemspec +6 -4
  16. data/spec/basics_spec.rb +54 -0
  17. data/spec/cronline_spec.rb +479 -152
  18. data/spec/error_spec.rb +139 -0
  19. data/spec/job_array_spec.rb +39 -0
  20. data/spec/job_at_spec.rb +58 -0
  21. data/spec/job_cron_spec.rb +128 -0
  22. data/spec/job_every_spec.rb +104 -0
  23. data/spec/job_in_spec.rb +20 -0
  24. data/spec/job_interval_spec.rb +68 -0
  25. data/spec/job_repeat_spec.rb +357 -0
  26. data/spec/job_spec.rb +498 -109
  27. data/spec/lock_custom_spec.rb +47 -0
  28. data/spec/lock_flock_spec.rb +47 -0
  29. data/spec/lock_lockfile_spec.rb +61 -0
  30. data/spec/lock_spec.rb +59 -0
  31. data/spec/parse_spec.rb +263 -0
  32. data/spec/schedule_at_spec.rb +158 -0
  33. data/spec/schedule_cron_spec.rb +66 -0
  34. data/spec/schedule_every_spec.rb +109 -0
  35. data/spec/schedule_in_spec.rb +80 -0
  36. data/spec/schedule_interval_spec.rb +128 -0
  37. data/spec/scheduler_spec.rb +928 -124
  38. data/spec/spec_helper.rb +126 -0
  39. data/spec/threads_spec.rb +96 -0
  40. data/spec/zotime_spec.rb +396 -0
  41. metadata +56 -33
  42. data/README.rdoc +0 -661
  43. data/lib/rufus/otime.rb +0 -3
  44. data/lib/rufus/sc/jobqueues.rb +0 -160
  45. data/lib/rufus/sc/jobs.rb +0 -471
  46. data/lib/rufus/sc/rtime.rb +0 -363
  47. data/lib/rufus/sc/scheduler.rb +0 -636
  48. data/lib/rufus/sc/version.rb +0 -32
  49. data/spec/at_in_spec.rb +0 -47
  50. data/spec/at_spec.rb +0 -125
  51. data/spec/blocking_spec.rb +0 -64
  52. data/spec/cron_spec.rb +0 -134
  53. data/spec/every_spec.rb +0 -304
  54. data/spec/exception_spec.rb +0 -113
  55. data/spec/in_spec.rb +0 -150
  56. data/spec/mutex_spec.rb +0 -159
  57. data/spec/rtime_spec.rb +0 -137
  58. data/spec/schedulable_spec.rb +0 -97
  59. data/spec/spec_base.rb +0 -87
  60. data/spec/stress_schedule_unschedule_spec.rb +0 -159
  61. data/spec/timeout_spec.rb +0 -148
  62. data/test/kjw.rb +0 -113
  63. data/test/t.rb +0 -20
data/Rakefile CHANGED
@@ -1,12 +1,8 @@
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'
9
- #require 'rake/rdoctask'
10
6
  require 'rdoc/task'
11
7
 
12
8
 
@@ -42,7 +38,7 @@ desc %{
42
38
  task :build do
43
39
 
44
40
  sh "gem build #{GEMSPEC_FILE}"
45
- sh "mkdir pkg" rescue nil
41
+ sh "mkdir -p pkg"
46
42
  sh "mv #{GEMSPEC.name}-#{GEMSPEC.version}.gem pkg/"
47
43
  end
48
44
 
data/TODO.txt CHANGED
@@ -1,57 +1,151 @@
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
147
+
148
+ ~~~
149
+
150
+ [ ] scheduler.at('chronic string', chronic_options...)
57
151
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2013, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,10 +22,10 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- require 'tzinfo'
25
+ require 'set'
26
26
 
27
27
 
28
- module Rufus
28
+ class Rufus::Scheduler
29
29
 
30
30
  #
31
31
  # A 'cron line' is a line in the sense of a crontab
@@ -33,9 +33,6 @@ module Rufus
33
33
  #
34
34
  class CronLine
35
35
 
36
- DAY_S = 24 * 3600
37
- WEEK_S = 7 * DAY_S
38
-
39
36
  # The string used for creating this cronline instance.
40
37
  #
41
38
  attr_reader :original
@@ -51,14 +48,15 @@ module Rufus
51
48
 
52
49
  def initialize(line)
53
50
 
54
- super()
51
+ raise ArgumentError.new(
52
+ "not a string: #{line.inspect}"
53
+ ) unless line.is_a?(String)
55
54
 
56
55
  @original = line
57
56
 
58
57
  items = line.split
59
58
 
60
- @timezone = (TZInfo::Timezone.get(items.last) rescue nil)
61
- items.pop if @timezone
59
+ @timezone = items.pop if ZoTime.is_timezone?(items.last)
62
60
 
63
61
  raise ArgumentError.new(
64
62
  "not a valid cronline : '#{line}'"
@@ -85,9 +83,7 @@ module Rufus
85
83
  #
86
84
  def matches?(time)
87
85
 
88
- time = Time.at(time) unless time.kind_of?(Time)
89
-
90
- time = @timezone.utc_to_local(time.getutc) if @timezone
86
+ time = ZoTime.new(time.to_f, @timezone || ENV['TZ']).time
91
87
 
92
88
  return false unless sub_match?(time, :sec, @seconds)
93
89
  return false unless sub_match?(time, :min, @minutes)
@@ -109,74 +105,99 @@ module Rufus
109
105
  # be passed if no start time is specified (search start time set to
110
106
  # Time.now))
111
107
  #
112
- # Rufus::CronLine.new('30 7 * * *').next_time(
108
+ # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
113
109
  # Time.mktime(2008, 10, 24, 7, 29))
114
110
  # #=> Fri Oct 24 07:30:00 -0500 2008
115
111
  #
116
- # Rufus::CronLine.new('30 7 * * *').next_time(
112
+ # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
117
113
  # Time.utc(2008, 10, 24, 7, 29))
118
114
  # #=> Fri Oct 24 07:30:00 UTC 2008
119
115
  #
120
- # Rufus::CronLine.new('30 7 * * *').next_time(
116
+ # Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
121
117
  # Time.utc(2008, 10, 24, 7, 29)).localtime
122
118
  # #=> Fri Oct 24 02:30:00 -0500 2008
123
119
  #
124
120
  # (Thanks to K Liu for the note and the examples)
125
121
  #
126
- def next_time(now=Time.now)
122
+ def next_time(from=Time.now)
127
123
 
128
- time = @timezone ? @timezone.utc_to_local(now.getutc) : now
129
-
130
- time = time - time.usec * 1e-6 + 1
131
- # small adjustment before starting
124
+ time = nil
125
+ zotime = ZoTime.new(from.to_i + 1, @timezone || ENV['TZ'])
132
126
 
133
127
  loop do
134
128
 
129
+ time = zotime.time
130
+
135
131
  unless date_match?(time)
136
- time += (24 - time.hour) * 3600 - time.min * 60 - time.sec; next
132
+ zotime.add((24 - time.hour) * 3600 - time.min * 60 - time.sec)
133
+ next
137
134
  end
138
135
  unless sub_match?(time, :hour, @hours)
139
- time += (60 - time.min) * 60 - time.sec; next
136
+ zotime.add((60 - time.min) * 60 - time.sec)
137
+ next
140
138
  end
141
139
  unless sub_match?(time, :min, @minutes)
142
- time += 60 - time.sec; next
140
+ zotime.add(60 - time.sec)
141
+ next
143
142
  end
144
143
  unless sub_match?(time, :sec, @seconds)
145
- time += 1; next
144
+ zotime.add(next_second(time))
145
+ next
146
146
  end
147
147
 
148
148
  break
149
149
  end
150
150
 
151
- if @timezone
152
- time = @timezone.local_to_utc(time)
153
- time = time.getlocal unless now.utc?
154
- end
155
-
156
151
  time
157
152
  end
158
153
 
159
- # Returns the previous the cronline matched. It's like next_time, but
154
+ # Returns the previous time the cronline matched. It's like next_time, but
160
155
  # for the past.
161
156
  #
162
- def previous_time(now=Time.now)
157
+ def previous_time(from=Time.now)
163
158
 
164
- # looks back by slices of two hours,
165
- #
166
- # finds for '* * * * sun', '* * 13 * *' and '0 12 13 * *'
167
- # starting 1970, 1, 1 in 1.8 to 2 seconds (says Rspec)
168
-
169
- start = current = now - 2 * 3600
170
- result = nil
159
+ time = nil
160
+ zotime = ZoTime.new(from.to_i - 1, @timezone || ENV['TZ'])
171
161
 
172
162
  loop do
173
- nex = next_time(current)
174
- return (result ? result : previous_time(start)) if nex > now
175
- result = current = nex
163
+
164
+ time = zotime.time
165
+
166
+ unless date_match?(time)
167
+ zotime.substract(time.hour * 3600 + time.min * 60 + time.sec + 1)
168
+ next
169
+ end
170
+ unless sub_match?(time, :hour, @hours)
171
+ zotime.substract(time.min * 60 + time.sec + 1)
172
+ next
173
+ end
174
+ unless sub_match?(time, :min, @minutes)
175
+ zotime.substract(time.sec + 1)
176
+ next
177
+ end
178
+ unless sub_match?(time, :sec, @seconds)
179
+ zotime.substract(prev_second(time))
180
+ next
181
+ end
182
+
183
+ break
176
184
  end
177
185
 
178
- # never reached
186
+ time
187
+ end
188
+
189
+ if RUBY_VERSION >= '1.9'
190
+ def toa(item)
191
+ item == nil ? nil : item.to_a
192
+ end
193
+ else
194
+ def toi(item); item.is_a?(String) ? item.hash.abs : item.to_i; end
195
+ protected :toi
196
+ def toa(item)
197
+ item.is_a?(Set) ? item.to_a.sort_by { |e| toi(e) } : item
198
+ end
179
199
  end
200
+ protected :toa
180
201
 
181
202
  # Returns an array of 6 arrays (seconds, minutes, hours, days,
182
203
  # months, weekdays).
@@ -185,20 +206,113 @@ module Rufus
185
206
  def to_array
186
207
 
187
208
  [
188
- @seconds,
189
- @minutes,
190
- @hours,
191
- @days,
192
- @months,
193
- @weekdays,
194
- @monthdays,
195
- @timezone ? @timezone.name : nil
209
+ toa(@seconds),
210
+ toa(@minutes),
211
+ toa(@hours),
212
+ toa(@days),
213
+ toa(@months),
214
+ toa(@weekdays),
215
+ toa(@monthdays),
216
+ @timezone
196
217
  ]
197
218
  end
198
219
 
199
- private
220
+ # Returns a quickly computed approximation of the frequency for this
221
+ # cron line.
222
+ #
223
+ # #brute_frequency, on the other hand, will compute the frequency by
224
+ # examining a whole year, that can take more than seconds for a seconds
225
+ # level cron...
226
+ #
227
+ def frequency
228
+
229
+ return brute_frequency unless @seconds && @seconds.length > 1
230
+
231
+ delta = 60
232
+ secs = toa(@seconds)
233
+ prev = secs[0]
234
+
235
+ secs[1..-1].each do |sec|
236
+ d = sec - prev
237
+ delta = d if d < delta
238
+ end
239
+
240
+ delta
241
+ end
242
+
243
+ # Returns the shortest delta between two potential occurences of the
244
+ # schedule described by this cronline.
245
+ #
246
+ # .
247
+ #
248
+ # For a simple cronline like "*/5 * * * *", obviously the frequency is
249
+ # five minutes. Why does this method look at a whole year of #next_time ?
250
+ #
251
+ # Consider "* * * * sun#2,sun#3", the computed frequency is 1 week
252
+ # (the shortest delta is the one between the second sunday and the third
253
+ # sunday). This method takes no chance and runs next_time for the span
254
+ # of a whole year and keeps the shortest.
255
+ #
256
+ # Of course, this method can get VERY slow if you call on it a second-
257
+ # based cronline...
258
+ #
259
+ # Since it's a rarely used method, I haven't taken the time to make it
260
+ # smarter/faster.
261
+ #
262
+ # One obvious improvement would be to cache the result once computed...
263
+ #
264
+ # See https://github.com/jmettraux/rufus-scheduler/issues/89
265
+ # for a discussion about this method.
266
+ #
267
+ def brute_frequency
268
+
269
+ delta = 366 * DAY_S
270
+
271
+ t0 = previous_time(Time.local(2000, 1, 1))
272
+
273
+ loop do
274
+
275
+ break if delta <= 1
276
+ break if delta <= 60 && @seconds && @seconds.size == 1
277
+
278
+ t1 = next_time(t0)
279
+ d = t1 - t0
280
+ delta = d if d < delta
281
+
282
+ break if @months == nil && t1.month == 2
283
+ break if t1.year >= 2001
284
+
285
+ t0 = t1
286
+ end
287
+
288
+ delta
289
+ end
290
+
291
+ protected
292
+
293
+ def next_second(time)
294
+
295
+ secs = @seconds.sort
296
+
297
+ return secs.last + 60 - time.sec if time.sec > secs.last
298
+
299
+ secs.shift while secs.first < time.sec
300
+
301
+ secs.first - time.sec
302
+ end
303
+
304
+ def prev_second(time)
305
+
306
+ secs = @seconds.sort
307
+
308
+ secs.pop while time.sec < secs.last
309
+
310
+ time.sec - secs.last
311
+ end
200
312
 
201
313
  WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
314
+ DAY_S = 24 * 3600
315
+ WEEK_S = 7 * DAY_S
202
316
 
203
317
  def parse_weekdays(item)
204
318
 
@@ -252,7 +366,7 @@ module Rufus
252
366
  "found duplicates in #{item.inspect}"
253
367
  ) if r.uniq.size < r.size
254
368
 
255
- r
369
+ Set.new(r)
256
370
  end
257
371
 
258
372
  RANGE_REGEX = /^(\*|\d{1,2})(?:-(\d{1,2}))?(?:\/(\d{1,2}))?$/
@@ -281,7 +395,7 @@ module Rufus
281
395
 
282
396
  raise ArgumentError.new(
283
397
  "#{item.inspect} is not in range #{min}..#{max}"
284
- ) if sta < min or edn > max
398
+ ) if sta < min || edn > max
285
399
 
286
400
  r = []
287
401
  val = sta
@@ -0,0 +1,92 @@
1
+ #--
2
+ # Copyright (c) 2006-2015, 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
+
28
+ class Scheduler
29
+
30
+ #
31
+ # The array rufus-scheduler uses to keep jobs in order (next to trigger
32
+ # first).
33
+ #
34
+ class JobArray
35
+
36
+ def initialize
37
+
38
+ @mutex = Mutex.new
39
+ @array = []
40
+ end
41
+
42
+ def push(job)
43
+
44
+ @mutex.synchronize { @array << job unless @array.index(job) }
45
+
46
+ self
47
+ end
48
+
49
+ def size
50
+
51
+ @array.size
52
+ end
53
+
54
+ def each(now, &block)
55
+
56
+ to_a.sort_by { |j| j.next_time || (now + 1) }.each do |job|
57
+
58
+ break unless job.next_time
59
+ break if job.next_time > now
60
+
61
+ block.call(job)
62
+ end
63
+ end
64
+
65
+ def delete_unscheduled
66
+
67
+ @mutex.synchronize {
68
+
69
+ @array.delete_if { |j| j.next_time.nil? || j.unscheduled_at }
70
+ }
71
+ end
72
+
73
+ def to_a
74
+
75
+ @mutex.synchronize { @array.dup }
76
+ end
77
+
78
+ def [](job_id)
79
+
80
+ @mutex.synchronize { @array.find { |j| j.job_id == job_id } }
81
+ end
82
+
83
+ # Only used when shutting down, directly yields the underlying array.
84
+ #
85
+ def array
86
+
87
+ @array
88
+ end
89
+ end
90
+ end
91
+ end
92
+