rufus-scheduler 3.0.0 → 3.0.9
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 +63 -0
- data/CREDITS.txt +16 -0
- data/LICENSE.txt +1 -1
- data/README.md +389 -7
- data/Rakefile +0 -1
- data/TODO.txt +4 -0
- data/lib/rufus/scheduler/cronline.rb +109 -28
- data/lib/rufus/scheduler/job_array.rb +8 -1
- data/lib/rufus/scheduler/jobs.rb +127 -43
- data/lib/rufus/scheduler/locks.rb +95 -0
- data/lib/rufus/scheduler/util.rb +39 -28
- data/lib/rufus/scheduler.rb +150 -46
- data/rufus-scheduler.gemspec +2 -1
- data/spec/basics_spec.rb +54 -0
- data/spec/cronline_spec.rb +349 -92
- data/spec/error_spec.rb +30 -7
- data/spec/job_array_spec.rb +2 -2
- data/spec/job_at_spec.rb +4 -4
- data/spec/job_cron_spec.rb +42 -3
- data/spec/job_every_spec.rb +24 -5
- data/spec/job_interval_spec.rb +5 -5
- data/spec/job_repeat_spec.rb +86 -38
- data/spec/job_spec.rb +172 -55
- data/spec/lock_custom_spec.rb +47 -0
- data/spec/lock_flock_spec.rb +47 -0
- data/spec/{lockfile_spec.rb → lock_lockfile_spec.rb} +5 -5
- data/spec/lock_spec.rb +59 -0
- data/spec/parse_spec.rb +138 -76
- data/spec/schedule_at_spec.rb +46 -17
- data/spec/schedule_cron_spec.rb +6 -6
- data/spec/schedule_every_spec.rb +12 -12
- data/spec/schedule_in_spec.rb +8 -8
- data/spec/schedule_interval_spec.rb +13 -13
- data/spec/scheduler_spec.rb +213 -119
- data/spec/spec_helper.rb +62 -1
- data/spec/threads_spec.rb +24 -3
- metadata +58 -37
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#--
|
|
2
|
-
# Copyright (c) 2006-
|
|
2
|
+
# Copyright (c) 2006-2014, 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
|
|
@@ -106,15 +106,15 @@ class Rufus::Scheduler
|
|
|
106
106
|
# be passed if no start time is specified (search start time set to
|
|
107
107
|
# Time.now))
|
|
108
108
|
#
|
|
109
|
-
# Rufus::CronLine.new('30 7 * * *').next_time(
|
|
109
|
+
# Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
|
|
110
110
|
# Time.mktime(2008, 10, 24, 7, 29))
|
|
111
111
|
# #=> Fri Oct 24 07:30:00 -0500 2008
|
|
112
112
|
#
|
|
113
|
-
# Rufus::CronLine.new('30 7 * * *').next_time(
|
|
113
|
+
# Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
|
|
114
114
|
# Time.utc(2008, 10, 24, 7, 29))
|
|
115
115
|
# #=> Fri Oct 24 07:30:00 UTC 2008
|
|
116
116
|
#
|
|
117
|
-
# Rufus::CronLine.new('30 7 * * *').next_time(
|
|
117
|
+
# Rufus::Scheduler::CronLine.new('30 7 * * *').next_time(
|
|
118
118
|
# Time.utc(2008, 10, 24, 7, 29)).localtime
|
|
119
119
|
# #=> Fri Oct 24 02:30:00 -0500 2008
|
|
120
120
|
#
|
|
@@ -122,18 +122,18 @@ class Rufus::Scheduler
|
|
|
122
122
|
#
|
|
123
123
|
def next_time(from=Time.now)
|
|
124
124
|
|
|
125
|
-
time =
|
|
126
|
-
|
|
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)
|
|
125
|
+
time = local_time(from)
|
|
126
|
+
time = round_to_seconds(time)
|
|
129
127
|
|
|
128
|
+
# start at the next second
|
|
130
129
|
time = time + 1
|
|
131
|
-
# start at the next second
|
|
132
130
|
|
|
133
131
|
loop do
|
|
134
|
-
|
|
135
132
|
unless date_match?(time)
|
|
136
|
-
|
|
133
|
+
dst = time.isdst
|
|
134
|
+
time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
|
|
135
|
+
time -= 3600 if time.isdst != dst # not necessary for winter, but...
|
|
136
|
+
next
|
|
137
137
|
end
|
|
138
138
|
unless sub_match?(time, :hour, @hours)
|
|
139
139
|
time += (60 - time.min) * 60 - time.sec; next
|
|
@@ -148,34 +148,46 @@ class Rufus::Scheduler
|
|
|
148
148
|
break
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
time = @timezone.local_to_utc(time)
|
|
153
|
-
time = time.getlocal unless from.utc?
|
|
154
|
-
end
|
|
151
|
+
global_time(time, from.utc?)
|
|
155
152
|
|
|
156
|
-
|
|
153
|
+
rescue TZInfo::PeriodNotFound
|
|
154
|
+
|
|
155
|
+
next_time(from + 3600)
|
|
157
156
|
end
|
|
158
157
|
|
|
159
|
-
# Returns the previous the cronline matched. It's like next_time, but
|
|
158
|
+
# Returns the previous time the cronline matched. It's like next_time, but
|
|
160
159
|
# for the past.
|
|
161
160
|
#
|
|
162
161
|
def previous_time(from=Time.now)
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# finds for '* * * * sun', '* * 13 * *' and '0 12 13 * *'
|
|
167
|
-
# starting 1970, 1, 1 in 1.8 to 2 seconds (says Rspec)
|
|
163
|
+
time = local_time(from)
|
|
164
|
+
time = round_to_seconds(time)
|
|
168
165
|
|
|
169
|
-
start
|
|
170
|
-
|
|
166
|
+
# start at the previous second
|
|
167
|
+
time = time - 1
|
|
171
168
|
|
|
172
169
|
loop do
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
unless date_match?(time)
|
|
171
|
+
time -= time.hour * 3600 + time.min * 60 + time.sec + 1; next
|
|
172
|
+
end
|
|
173
|
+
unless sub_match?(time, :hour, @hours)
|
|
174
|
+
time -= time.min * 60 + time.sec + 1; next
|
|
175
|
+
end
|
|
176
|
+
unless sub_match?(time, :min, @minutes)
|
|
177
|
+
time -= time.sec + 1; next
|
|
178
|
+
end
|
|
179
|
+
unless sub_match?(time, :sec, @seconds)
|
|
180
|
+
time -= 1; next
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
break
|
|
176
184
|
end
|
|
177
185
|
|
|
178
|
-
|
|
186
|
+
global_time(time, from.utc?)
|
|
187
|
+
|
|
188
|
+
rescue TZInfo::PeriodNotFound
|
|
189
|
+
|
|
190
|
+
previous_time(time)
|
|
179
191
|
end
|
|
180
192
|
|
|
181
193
|
# Returns an array of 6 arrays (seconds, minutes, hours, days,
|
|
@@ -196,10 +208,53 @@ class Rufus::Scheduler
|
|
|
196
208
|
]
|
|
197
209
|
end
|
|
198
210
|
|
|
211
|
+
# Returns a quickly computed approximation of the frequency for this
|
|
212
|
+
# cron line.
|
|
213
|
+
#
|
|
214
|
+
# #brute_frequency, on the other hand, will compute the frequency by
|
|
215
|
+
# examining a whole, that can take more than seconds for a seconds
|
|
216
|
+
# level cron...
|
|
217
|
+
#
|
|
218
|
+
def frequency
|
|
219
|
+
|
|
220
|
+
return brute_frequency unless @seconds && @seconds.length > 1
|
|
221
|
+
|
|
222
|
+
delta = 60
|
|
223
|
+
prev = @seconds[0]
|
|
224
|
+
|
|
225
|
+
@seconds[1..-1].each do |sec|
|
|
226
|
+
d = sec - prev
|
|
227
|
+
delta = d if d < delta
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
delta
|
|
231
|
+
end
|
|
232
|
+
|
|
199
233
|
# Returns the shortest delta between two potential occurences of the
|
|
200
234
|
# schedule described by this cronline.
|
|
201
235
|
#
|
|
202
|
-
|
|
236
|
+
# .
|
|
237
|
+
#
|
|
238
|
+
# For a simple cronline like "*/5 * * * *", obviously the frequency is
|
|
239
|
+
# five minutes. Why does this method look at a whole year of #next_time ?
|
|
240
|
+
#
|
|
241
|
+
# Consider "* * * * sun#2,sun#3", the computed frequency is 1 week
|
|
242
|
+
# (the shortest delta is the one between the second sunday and the third
|
|
243
|
+
# sunday). This method takes no chance and runs next_time for the span
|
|
244
|
+
# of a whole year and keeps the shortest.
|
|
245
|
+
#
|
|
246
|
+
# Of course, this method can get VERY slow if you call on it a second-
|
|
247
|
+
# based cronline...
|
|
248
|
+
#
|
|
249
|
+
# Since it's a rarely used method, I haven't taken the time to make it
|
|
250
|
+
# smarter/faster.
|
|
251
|
+
#
|
|
252
|
+
# One obvious improvement would be to cache the result once computed...
|
|
253
|
+
#
|
|
254
|
+
# See https://github.com/jmettraux/rufus-scheduler/issues/89
|
|
255
|
+
# for a discussion about this method.
|
|
256
|
+
#
|
|
257
|
+
def brute_frequency
|
|
203
258
|
|
|
204
259
|
delta = 366 * DAY_S
|
|
205
260
|
|
|
@@ -380,6 +435,32 @@ class Rufus::Scheduler
|
|
|
380
435
|
|
|
381
436
|
[ "#{WEEKDAYS[date.wday]}##{pos}", "#{WEEKDAYS[date.wday]}##{neg}" ]
|
|
382
437
|
end
|
|
438
|
+
|
|
439
|
+
def local_time(time)
|
|
440
|
+
|
|
441
|
+
@timezone ? @timezone.utc_to_local(time.getutc) : time
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def global_time(time, from_in_utc)
|
|
445
|
+
|
|
446
|
+
if @timezone
|
|
447
|
+
time =
|
|
448
|
+
begin
|
|
449
|
+
@timezone.local_to_utc(time)
|
|
450
|
+
rescue TZInfo::AmbiguousTime
|
|
451
|
+
@timezone.local_to_utc(time, time.isdst)
|
|
452
|
+
end
|
|
453
|
+
time = time.getlocal unless from_in_utc
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
time
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def round_to_seconds(time)
|
|
460
|
+
|
|
461
|
+
# Ruby 1.8 doesn't have #round
|
|
462
|
+
time.respond_to?(:round) ? time.round : time - time.usec * 1e-6
|
|
463
|
+
end
|
|
383
464
|
end
|
|
384
465
|
end
|
|
385
466
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#--
|
|
2
|
-
# Copyright (c) 2006-
|
|
2
|
+
# Copyright (c) 2006-2014, 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
|
|
@@ -78,6 +78,13 @@ module Rufus
|
|
|
78
78
|
|
|
79
79
|
@mutex.synchronize { @array.find { |j| j.job_id == job_id } }
|
|
80
80
|
end
|
|
81
|
+
|
|
82
|
+
# Only used when shutting down, directly yields the underlying array.
|
|
83
|
+
#
|
|
84
|
+
def array
|
|
85
|
+
|
|
86
|
+
@array
|
|
87
|
+
end
|
|
81
88
|
end
|
|
82
89
|
end
|
|
83
90
|
end
|
data/lib/rufus/scheduler/jobs.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#--
|
|
2
|
-
# Copyright (c) 2006-
|
|
2
|
+
# Copyright (c) 2006-2014, 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
|
|
@@ -45,6 +45,9 @@ module Rufus
|
|
|
45
45
|
attr_reader :last_time
|
|
46
46
|
attr_reader :unscheduled_at
|
|
47
47
|
attr_reader :tags
|
|
48
|
+
attr_reader :count
|
|
49
|
+
attr_reader :last_work_time
|
|
50
|
+
attr_reader :mean_work_time
|
|
48
51
|
|
|
49
52
|
# next trigger time
|
|
50
53
|
#
|
|
@@ -82,8 +85,6 @@ module Rufus
|
|
|
82
85
|
@scheduled_at = Time.now
|
|
83
86
|
@unscheduled_at = nil
|
|
84
87
|
@last_time = nil
|
|
85
|
-
#@mutexes = {}
|
|
86
|
-
#@pool_mutex = Mutex.new
|
|
87
88
|
|
|
88
89
|
@locals = {}
|
|
89
90
|
@local_mutex = Mutex.new
|
|
@@ -98,6 +99,10 @@ module Rufus
|
|
|
98
99
|
|
|
99
100
|
@tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
|
100
101
|
|
|
102
|
+
@count = 0
|
|
103
|
+
@last_work_time = 0.0
|
|
104
|
+
@mean_work_time = 0.0
|
|
105
|
+
|
|
101
106
|
# tidy up options
|
|
102
107
|
|
|
103
108
|
if @opts[:allow_overlap] == false || @opts[:allow_overlapping] == false
|
|
@@ -112,13 +117,18 @@ module Rufus
|
|
|
112
117
|
|
|
113
118
|
def trigger(time)
|
|
114
119
|
|
|
115
|
-
set_next_time(
|
|
116
|
-
|
|
117
|
-
return if opts[:overlap] == false && running?
|
|
120
|
+
set_next_time(time)
|
|
118
121
|
|
|
119
|
-
|
|
122
|
+
return if (
|
|
123
|
+
opts[:overlap] == false &&
|
|
124
|
+
running?
|
|
125
|
+
)
|
|
126
|
+
return if (
|
|
127
|
+
callback(:confirm_lock, time) &&
|
|
128
|
+
callback(:on_pre_trigger, time)
|
|
129
|
+
) == false
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
@count += 1
|
|
122
132
|
|
|
123
133
|
if opts[:blocking]
|
|
124
134
|
do_trigger(time)
|
|
@@ -183,17 +193,25 @@ module Rufus
|
|
|
183
193
|
#
|
|
184
194
|
# might be necessary at some point
|
|
185
195
|
|
|
186
|
-
|
|
196
|
+
# Calls the callable (usually a block) wrapped in this Job instance.
|
|
197
|
+
#
|
|
198
|
+
# Warning: error rescueing is the responsibity of the caller.
|
|
199
|
+
#
|
|
200
|
+
def call(do_rescue=false)
|
|
201
|
+
|
|
202
|
+
do_call(Time.now, do_rescue)
|
|
203
|
+
end
|
|
187
204
|
|
|
188
|
-
|
|
205
|
+
protected
|
|
189
206
|
|
|
190
|
-
|
|
207
|
+
def callback(meth, time)
|
|
191
208
|
|
|
192
|
-
return unless @scheduler.respond_to?(
|
|
209
|
+
return true unless @scheduler.respond_to?(meth)
|
|
193
210
|
|
|
194
|
-
|
|
211
|
+
arity = @scheduler.method(meth).arity
|
|
212
|
+
args = [ self, time ][0, (arity < 0 ? 2 : arity)]
|
|
195
213
|
|
|
196
|
-
@scheduler.send(
|
|
214
|
+
@scheduler.send(meth, *args)
|
|
197
215
|
end
|
|
198
216
|
|
|
199
217
|
def compute_timeout
|
|
@@ -210,6 +228,22 @@ module Rufus
|
|
|
210
228
|
m.is_a?(Mutex) ? m : (@scheduler.mutexes[m.to_s] ||= Mutex.new)
|
|
211
229
|
end
|
|
212
230
|
|
|
231
|
+
def do_call(time, do_rescue)
|
|
232
|
+
|
|
233
|
+
args = [ self, time ][0, @callable.arity]
|
|
234
|
+
@callable.call(*args)
|
|
235
|
+
|
|
236
|
+
rescue StandardError => se
|
|
237
|
+
|
|
238
|
+
raise se unless do_rescue
|
|
239
|
+
|
|
240
|
+
return if se.is_a?(KillSignal) # discard
|
|
241
|
+
|
|
242
|
+
@scheduler.on_error(self, se)
|
|
243
|
+
|
|
244
|
+
# exceptions above StandardError do pass through
|
|
245
|
+
end
|
|
246
|
+
|
|
213
247
|
def do_trigger(time)
|
|
214
248
|
|
|
215
249
|
t = Time.now
|
|
@@ -221,19 +255,15 @@ module Rufus
|
|
|
221
255
|
|
|
222
256
|
@last_time = t
|
|
223
257
|
|
|
224
|
-
|
|
225
|
-
@callable.call(*args)
|
|
226
|
-
|
|
227
|
-
rescue KillSignal
|
|
228
|
-
|
|
229
|
-
# discard
|
|
230
|
-
|
|
231
|
-
rescue StandardError => se
|
|
232
|
-
|
|
233
|
-
@scheduler.on_error(self, se)
|
|
258
|
+
do_call(time, true)
|
|
234
259
|
|
|
235
260
|
ensure
|
|
236
261
|
|
|
262
|
+
@last_work_time =
|
|
263
|
+
Time.now - Thread.current[:rufus_scheduler_time]
|
|
264
|
+
@mean_work_time =
|
|
265
|
+
((@count - 1) * @mean_work_time + @last_work_time) / @count
|
|
266
|
+
|
|
237
267
|
post_trigger(time)
|
|
238
268
|
|
|
239
269
|
Thread.current[:rufus_scheduler_job] = nil
|
|
@@ -243,9 +273,9 @@ module Rufus
|
|
|
243
273
|
|
|
244
274
|
def post_trigger(time)
|
|
245
275
|
|
|
246
|
-
set_next_time(
|
|
276
|
+
set_next_time(time, true)
|
|
247
277
|
|
|
248
|
-
callback(:
|
|
278
|
+
callback(:on_post_trigger, time)
|
|
249
279
|
end
|
|
250
280
|
|
|
251
281
|
def start_work_thread
|
|
@@ -290,15 +320,15 @@ module Rufus
|
|
|
290
320
|
|
|
291
321
|
def do_trigger_in_thread(time)
|
|
292
322
|
|
|
293
|
-
|
|
323
|
+
threads = @scheduler.work_threads
|
|
294
324
|
|
|
295
|
-
|
|
296
|
-
|
|
325
|
+
cur = threads.size
|
|
326
|
+
vac = threads.select { |t| t[:rufus_scheduler_job] == nil }.size
|
|
297
327
|
#min = @scheduler.min_work_threads
|
|
298
328
|
max = @scheduler.max_work_threads
|
|
329
|
+
que = @scheduler.work_queue.size
|
|
299
330
|
|
|
300
|
-
start_work_thread if
|
|
301
|
-
#end
|
|
331
|
+
start_work_thread if vac - que < 1 && cur < max
|
|
302
332
|
|
|
303
333
|
@scheduler.work_queue << [ self, time ]
|
|
304
334
|
end
|
|
@@ -308,6 +338,11 @@ module Rufus
|
|
|
308
338
|
|
|
309
339
|
alias time next_time
|
|
310
340
|
|
|
341
|
+
def occurrences(time0, time1)
|
|
342
|
+
|
|
343
|
+
time >= time0 && time <= time1 ? [ time ] : []
|
|
344
|
+
end
|
|
345
|
+
|
|
311
346
|
protected
|
|
312
347
|
|
|
313
348
|
def determine_id
|
|
@@ -322,7 +357,7 @@ module Rufus
|
|
|
322
357
|
|
|
323
358
|
# There is no next_time for one time jobs, hence the false.
|
|
324
359
|
#
|
|
325
|
-
def set_next_time(
|
|
360
|
+
def set_next_time(trigger_time, is_post=false)
|
|
326
361
|
|
|
327
362
|
@next_time = is_post ? nil : false
|
|
328
363
|
end
|
|
@@ -334,7 +369,8 @@ module Rufus
|
|
|
334
369
|
|
|
335
370
|
super(scheduler, time, opts, block)
|
|
336
371
|
|
|
337
|
-
@next_time =
|
|
372
|
+
@next_time =
|
|
373
|
+
opts[:_t] || Rufus::Scheduler.parse_at(time, opts)
|
|
338
374
|
end
|
|
339
375
|
end
|
|
340
376
|
|
|
@@ -344,7 +380,9 @@ module Rufus
|
|
|
344
380
|
|
|
345
381
|
super(scheduler, duration, opts, block)
|
|
346
382
|
|
|
347
|
-
@next_time =
|
|
383
|
+
@next_time =
|
|
384
|
+
@scheduled_at +
|
|
385
|
+
opts[:_t] || Rufus::Scheduler.parse_in(duration, opts)
|
|
348
386
|
end
|
|
349
387
|
end
|
|
350
388
|
|
|
@@ -369,19 +407,24 @@ module Rufus
|
|
|
369
407
|
) unless @times == nil || @times.is_a?(Fixnum)
|
|
370
408
|
|
|
371
409
|
self.first_at =
|
|
372
|
-
opts[:first] || opts[:
|
|
410
|
+
opts[:first] || opts[:first_time] ||
|
|
411
|
+
opts[:first_at] || opts[:first_in] ||
|
|
412
|
+
0
|
|
373
413
|
self.last_at =
|
|
374
414
|
opts[:last] || opts[:last_at] || opts[:last_in]
|
|
375
415
|
end
|
|
376
416
|
|
|
377
417
|
def first_at=(first)
|
|
378
418
|
|
|
419
|
+
n = Time.now
|
|
420
|
+
first = n + 0.001 if first == :now || first == :immediately
|
|
421
|
+
|
|
379
422
|
@first_at = Rufus::Scheduler.parse_to_time(first)
|
|
380
423
|
|
|
381
424
|
raise ArgumentError.new(
|
|
382
425
|
"cannot set first[_at|_in] in the past: " +
|
|
383
426
|
"#{first.inspect} -> #{@first_at.inspect}"
|
|
384
|
-
) if first != 0 && @first_at <
|
|
427
|
+
) if first != 0 && @first_at < n
|
|
385
428
|
end
|
|
386
429
|
|
|
387
430
|
def last_at=(last)
|
|
@@ -408,7 +451,7 @@ module Rufus
|
|
|
408
451
|
|
|
409
452
|
super
|
|
410
453
|
|
|
411
|
-
@times
|
|
454
|
+
@times -= 1 if @times
|
|
412
455
|
end
|
|
413
456
|
|
|
414
457
|
def pause
|
|
@@ -434,6 +477,27 @@ module Rufus
|
|
|
434
477
|
opts.hash.abs
|
|
435
478
|
].map(&:to_s).join('_')
|
|
436
479
|
end
|
|
480
|
+
|
|
481
|
+
def occurrences(time0, time1)
|
|
482
|
+
|
|
483
|
+
a = []
|
|
484
|
+
|
|
485
|
+
nt = @next_time
|
|
486
|
+
ts = @times
|
|
487
|
+
|
|
488
|
+
loop do
|
|
489
|
+
|
|
490
|
+
break if nt > time1
|
|
491
|
+
break if ts && ts <= 0
|
|
492
|
+
|
|
493
|
+
a << nt if nt >= time0
|
|
494
|
+
|
|
495
|
+
nt = next_time_from(nt)
|
|
496
|
+
ts = ts - 1 if ts
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
a
|
|
500
|
+
end
|
|
437
501
|
end
|
|
438
502
|
|
|
439
503
|
#
|
|
@@ -464,12 +528,12 @@ module Rufus
|
|
|
464
528
|
"of #{@frequency.inspect} (#{@original.inspect})"
|
|
465
529
|
) if @frequency <= 0
|
|
466
530
|
|
|
467
|
-
set_next_time(
|
|
531
|
+
set_next_time(nil)
|
|
468
532
|
end
|
|
469
533
|
|
|
470
534
|
protected
|
|
471
535
|
|
|
472
|
-
def set_next_time(
|
|
536
|
+
def set_next_time(trigger_time, is_post=false)
|
|
473
537
|
|
|
474
538
|
return if is_post
|
|
475
539
|
|
|
@@ -482,6 +546,11 @@ module Rufus
|
|
|
482
546
|
@first_at
|
|
483
547
|
end
|
|
484
548
|
end
|
|
549
|
+
|
|
550
|
+
def next_time_from(time)
|
|
551
|
+
|
|
552
|
+
time + @frequency
|
|
553
|
+
end
|
|
485
554
|
end
|
|
486
555
|
|
|
487
556
|
class IntervalJob < EvInJob
|
|
@@ -499,12 +568,12 @@ module Rufus
|
|
|
499
568
|
"of #{@interval.inspect} (#{@original.inspect})"
|
|
500
569
|
) if @interval <= 0
|
|
501
570
|
|
|
502
|
-
set_next_time(
|
|
571
|
+
set_next_time(nil)
|
|
503
572
|
end
|
|
504
573
|
|
|
505
574
|
protected
|
|
506
575
|
|
|
507
|
-
def set_next_time(
|
|
576
|
+
def set_next_time(trigger_time, is_post=false)
|
|
508
577
|
|
|
509
578
|
@next_time =
|
|
510
579
|
if is_post
|
|
@@ -519,6 +588,11 @@ module Rufus
|
|
|
519
588
|
false
|
|
520
589
|
end
|
|
521
590
|
end
|
|
591
|
+
|
|
592
|
+
def next_time_from(time)
|
|
593
|
+
|
|
594
|
+
time + @mean_work_time + @interval
|
|
595
|
+
end
|
|
522
596
|
end
|
|
523
597
|
|
|
524
598
|
class CronJob < RepeatJob
|
|
@@ -527,7 +601,7 @@ module Rufus
|
|
|
527
601
|
|
|
528
602
|
super(scheduler, cronline, opts, block)
|
|
529
603
|
|
|
530
|
-
@cron_line = CronLine.new(cronline)
|
|
604
|
+
@cron_line = opts[:_t] || CronLine.new(cronline)
|
|
531
605
|
@next_time = @cron_line.next_time
|
|
532
606
|
end
|
|
533
607
|
|
|
@@ -536,12 +610,22 @@ module Rufus
|
|
|
536
610
|
@cron_line.frequency
|
|
537
611
|
end
|
|
538
612
|
|
|
613
|
+
def brute_frequency
|
|
614
|
+
|
|
615
|
+
@cron_line.brute_frequency
|
|
616
|
+
end
|
|
617
|
+
|
|
539
618
|
protected
|
|
540
619
|
|
|
541
|
-
def set_next_time(
|
|
620
|
+
def set_next_time(trigger_time, is_post=false)
|
|
542
621
|
|
|
543
622
|
@next_time = @cron_line.next_time
|
|
544
623
|
end
|
|
624
|
+
|
|
625
|
+
def next_time_from(time)
|
|
626
|
+
|
|
627
|
+
@cron_line.next_time(time)
|
|
628
|
+
end
|
|
545
629
|
end
|
|
546
630
|
end
|
|
547
631
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2006-2014, 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
|
+
require 'fileutils'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Rufus::Scheduler
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# A lock that can always be acquired
|
|
32
|
+
#
|
|
33
|
+
class NullLock
|
|
34
|
+
|
|
35
|
+
# Locking is always successful.
|
|
36
|
+
#
|
|
37
|
+
def lock; true; end
|
|
38
|
+
|
|
39
|
+
def locked?; true; end
|
|
40
|
+
def unlock; true; end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# The standard flock mecha, with its own class thanks to @ecin
|
|
45
|
+
#
|
|
46
|
+
class FileLock
|
|
47
|
+
|
|
48
|
+
attr_reader :path
|
|
49
|
+
|
|
50
|
+
def initialize(path)
|
|
51
|
+
|
|
52
|
+
@path = path.to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Locking is successful if this Ruby process can create and lock
|
|
56
|
+
# its lockfile (at the given path).
|
|
57
|
+
#
|
|
58
|
+
def lock
|
|
59
|
+
|
|
60
|
+
return true if locked?
|
|
61
|
+
|
|
62
|
+
@lockfile = nil
|
|
63
|
+
|
|
64
|
+
FileUtils.mkdir_p(::File.dirname(@path))
|
|
65
|
+
|
|
66
|
+
file = File.new(@path, File::RDWR | File::CREAT)
|
|
67
|
+
locked = file.flock(File::LOCK_NB | File::LOCK_EX)
|
|
68
|
+
|
|
69
|
+
return false unless locked
|
|
70
|
+
|
|
71
|
+
now = Time.now
|
|
72
|
+
|
|
73
|
+
file.print("pid: #{$$}, ")
|
|
74
|
+
file.print("scheduler.object_id: #{self.object_id}, ")
|
|
75
|
+
file.print("time: #{now}, ")
|
|
76
|
+
file.print("timestamp: #{now.to_f}")
|
|
77
|
+
file.flush
|
|
78
|
+
|
|
79
|
+
@lockfile = file
|
|
80
|
+
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def unlock
|
|
85
|
+
|
|
86
|
+
!! (@lockfile && @lockfile.flock(File::LOCK_UN))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def locked?
|
|
90
|
+
|
|
91
|
+
!! (@lockfile && @lockfile.flock(File::LOCK_NB | File::LOCK_EX))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|