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.
- data/CHANGELOG.txt +76 -0
- data/CREDITS.txt +23 -0
- data/LICENSE.txt +1 -1
- data/README.md +1439 -0
- data/Rakefile +1 -5
- data/TODO.txt +149 -55
- data/lib/rufus/{sc → scheduler}/cronline.rb +167 -53
- data/lib/rufus/scheduler/job_array.rb +92 -0
- data/lib/rufus/scheduler/jobs.rb +633 -0
- data/lib/rufus/scheduler/locks.rb +95 -0
- data/lib/rufus/scheduler/util.rb +306 -0
- data/lib/rufus/scheduler/zones.rb +174 -0
- data/lib/rufus/scheduler/zotime.rb +154 -0
- data/lib/rufus/scheduler.rb +608 -27
- data/rufus-scheduler.gemspec +6 -4
- data/spec/basics_spec.rb +54 -0
- data/spec/cronline_spec.rb +479 -152
- data/spec/error_spec.rb +139 -0
- data/spec/job_array_spec.rb +39 -0
- data/spec/job_at_spec.rb +58 -0
- data/spec/job_cron_spec.rb +128 -0
- data/spec/job_every_spec.rb +104 -0
- data/spec/job_in_spec.rb +20 -0
- data/spec/job_interval_spec.rb +68 -0
- data/spec/job_repeat_spec.rb +357 -0
- data/spec/job_spec.rb +498 -109
- data/spec/lock_custom_spec.rb +47 -0
- data/spec/lock_flock_spec.rb +47 -0
- data/spec/lock_lockfile_spec.rb +61 -0
- data/spec/lock_spec.rb +59 -0
- data/spec/parse_spec.rb +263 -0
- data/spec/schedule_at_spec.rb +158 -0
- data/spec/schedule_cron_spec.rb +66 -0
- data/spec/schedule_every_spec.rb +109 -0
- data/spec/schedule_in_spec.rb +80 -0
- data/spec/schedule_interval_spec.rb +128 -0
- data/spec/scheduler_spec.rb +928 -124
- data/spec/spec_helper.rb +126 -0
- data/spec/threads_spec.rb +96 -0
- data/spec/zotime_spec.rb +396 -0
- metadata +56 -33
- data/README.rdoc +0 -661
- data/lib/rufus/otime.rb +0 -3
- data/lib/rufus/sc/jobqueues.rb +0 -160
- data/lib/rufus/sc/jobs.rb +0 -471
- data/lib/rufus/sc/rtime.rb +0 -363
- data/lib/rufus/sc/scheduler.rb +0 -636
- data/lib/rufus/sc/version.rb +0 -32
- data/spec/at_in_spec.rb +0 -47
- data/spec/at_spec.rb +0 -125
- data/spec/blocking_spec.rb +0 -64
- data/spec/cron_spec.rb +0 -134
- data/spec/every_spec.rb +0 -304
- data/spec/exception_spec.rb +0 -113
- data/spec/in_spec.rb +0 -150
- data/spec/mutex_spec.rb +0 -159
- data/spec/rtime_spec.rb +0 -137
- data/spec/schedulable_spec.rb +0 -97
- data/spec/spec_base.rb +0 -87
- data/spec/stress_schedule_unschedule_spec.rb +0 -159
- data/spec/timeout_spec.rb +0 -148
- data/test/kjw.rb +0 -113
- 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"
|
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]
|
3
|
-
[
|
4
|
-
|
5
|
-
|
6
|
-
[o]
|
7
|
-
|
8
|
-
[o]
|
9
|
-
|
10
|
-
[o]
|
11
|
-
[o]
|
12
|
-
|
13
|
-
[o]
|
14
|
-
|
15
|
-
[
|
16
|
-
|
17
|
-
[o]
|
18
|
-
[o]
|
19
|
-
|
20
|
-
[
|
21
|
-
|
22
|
-
[o]
|
23
|
-
[o]
|
24
|
-
|
25
|
-
[o]
|
26
|
-
|
27
|
-
|
28
|
-
[o]
|
29
|
-
|
30
|
-
[o]
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
[
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
[
|
40
|
-
|
41
|
-
[o]
|
42
|
-
[o]
|
43
|
-
|
44
|
-
[o]
|
45
|
-
|
46
|
-
[o]
|
47
|
-
|
48
|
-
|
49
|
-
[o]
|
50
|
-
[o]
|
51
|
-
|
52
|
-
[
|
53
|
-
[
|
54
|
-
|
55
|
-
|
56
|
-
|
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-
|
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 '
|
25
|
+
require 'set'
|
26
26
|
|
27
27
|
|
28
|
-
|
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
|
-
|
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 =
|
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 =
|
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(
|
122
|
+
def next_time(from=Time.now)
|
127
123
|
|
128
|
-
time =
|
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
|
-
|
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
|
-
|
136
|
+
zotime.add((60 - time.min) * 60 - time.sec)
|
137
|
+
next
|
140
138
|
end
|
141
139
|
unless sub_match?(time, :min, @minutes)
|
142
|
-
|
140
|
+
zotime.add(60 - time.sec)
|
141
|
+
next
|
143
142
|
end
|
144
143
|
unless sub_match?(time, :sec, @seconds)
|
145
|
-
time
|
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(
|
157
|
+
def previous_time(from=Time.now)
|
163
158
|
|
164
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
+
|