rufus-scheduler 2.0.24 → 3.1.0

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