rufus-scheduler 3.4.2 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,72 +1,62 @@
1
1
 
2
- module Rufus
2
+ #
3
+ # The array rufus-scheduler uses to keep jobs in order (next to trigger
4
+ # first).
5
+ #
6
+ class Rufus::Scheduler::JobArray
3
7
 
4
- class Scheduler
8
+ def initialize
5
9
 
6
- #
7
- # The array rufus-scheduler uses to keep jobs in order (next to trigger
8
- # first).
9
- #
10
- class JobArray
11
-
12
- def initialize
13
-
14
- @mutex = Mutex.new
15
- @array = []
16
- end
17
-
18
- def push(job)
10
+ @mutex = Mutex.new
11
+ @array = []
12
+ end
19
13
 
20
- @mutex.synchronize { @array << job unless @array.index(job) }
14
+ def push(job)
21
15
 
22
- self
23
- end
16
+ @mutex.synchronize { @array << job unless @array.index(job) }
24
17
 
25
- def size
18
+ self
19
+ end
26
20
 
27
- @array.size
28
- end
21
+ def size
29
22
 
30
- def each(now, &block)
23
+ @array.size
24
+ end
31
25
 
32
- to_a.sort_by do |job|
26
+ def each(now, &block)
33
27
 
34
- job.next_time || (now + 1)
28
+ to_a.sort_by do |job|
35
29
 
36
- end.each do |job|
30
+ job.next_time || (now + 1)
37
31
 
38
- nt = job.next_time
39
- break if ( ! nt) || (nt > now)
32
+ end.each do |job|
40
33
 
41
- block.call(job)
42
- end
43
- end
34
+ nt = job.next_time
35
+ break if ( ! nt) || (nt > now)
44
36
 
45
- def delete_unscheduled
37
+ block.call(job)
38
+ end
39
+ end
46
40
 
47
- @mutex.synchronize {
41
+ def delete_unscheduled
48
42
 
49
- @array.delete_if { |j| j.next_time.nil? || j.unscheduled_at }
50
- }
51
- end
43
+ @mutex.synchronize {
44
+ @array.delete_if { |j| j.next_time.nil? || j.unscheduled_at } }
45
+ end
52
46
 
53
- def to_a
47
+ def to_a
54
48
 
55
- @mutex.synchronize { @array.dup }
56
- end
49
+ @mutex.synchronize { @array.dup }
50
+ end
57
51
 
58
- def [](job_id)
52
+ def [](job_id)
59
53
 
60
- @mutex.synchronize { @array.find { |j| j.job_id == job_id } }
61
- end
54
+ @mutex.synchronize { @array.find { |j| j.job_id == job_id } }
55
+ end
62
56
 
63
- # Only used when shutting down, directly yields the underlying array.
64
- #
65
- def array
57
+ def unschedule_all
66
58
 
67
- @array
68
- end
69
- end
59
+ @array.each(&:unschedule)
70
60
  end
71
61
  end
72
62
 
@@ -0,0 +1,363 @@
1
+
2
+ class Rufus::Scheduler::Job
3
+
4
+ EoTime = ::EtOrbi::EoTime
5
+
6
+ #
7
+ # Used by Job#kill
8
+ #
9
+ class KillSignal < StandardError; end
10
+
11
+ attr_reader :id
12
+ attr_reader :opts
13
+ attr_reader :original
14
+ attr_reader :scheduled_at
15
+ attr_reader :last_time
16
+ attr_reader :unscheduled_at
17
+ attr_reader :tags
18
+ attr_reader :locals
19
+ attr_reader :count
20
+ attr_reader :last_work_time
21
+ attr_reader :mean_work_time
22
+
23
+ attr_accessor :name
24
+
25
+ # next trigger time
26
+ #
27
+ attr_accessor :next_time
28
+
29
+ # previous "next trigger time"
30
+ #
31
+ attr_accessor :previous_time
32
+
33
+ # anything with a #call(job[, timet]) method,
34
+ # what gets actually triggered
35
+ #
36
+ attr_reader :callable
37
+
38
+ # a reference to the instance whose call method is the @callable
39
+ #
40
+ attr_reader :handler
41
+
42
+ # Default, core, implementation has no effect. Repeat jobs do override it.
43
+ #
44
+ def resume_discard_past=(v); end
45
+
46
+ def initialize(scheduler, original, opts, block)
47
+
48
+ @scheduler = scheduler
49
+ @original = original
50
+ @opts = opts
51
+
52
+ @handler = block
53
+
54
+ @callable =
55
+ if block.respond_to?(:arity)
56
+ block
57
+ elsif block.respond_to?(:call)
58
+ block.method(:call)
59
+ elsif block.is_a?(Class)
60
+ @handler = block.new
61
+ @handler.method(:call) rescue nil
62
+ else
63
+ nil
64
+ end
65
+
66
+ @scheduled_at = EoTime.now
67
+ @unscheduled_at = nil
68
+ @last_time = nil
69
+
70
+ @locals = opts[:locals] || opts[:l] || {}
71
+ @local_mutex = Mutex.new
72
+
73
+ @id = determine_id
74
+ @name = opts[:name] || opts[:n]
75
+
76
+ fail(
77
+ ArgumentError,
78
+ 'missing block or callable to schedule',
79
+ caller[2..-1]
80
+ ) unless @callable
81
+
82
+ @tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
83
+
84
+ @count = 0
85
+ @last_work_time = 0.0
86
+ @mean_work_time = 0.0
87
+
88
+ # tidy up options
89
+
90
+ if @opts[:allow_overlap] == false || @opts[:allow_overlapping] == false
91
+ @opts[:overlap] = false
92
+ end
93
+ if m = @opts[:mutex]
94
+ @opts[:mutex] = Array(m)
95
+ end
96
+ end
97
+
98
+ alias job_id id
99
+
100
+ # Will fail with an ArgumentError if the job frequency is higher than
101
+ # the scheduler frequency.
102
+ #
103
+ def check_frequency
104
+
105
+ # this parent implementation never fails
106
+ end
107
+
108
+ def trigger(time)
109
+
110
+ @previous_time = @next_time
111
+ set_next_time(time)
112
+
113
+ do_trigger(time)
114
+ end
115
+
116
+ # Trigger the job right now, off of its schedule.
117
+ #
118
+ # Done in collaboration with Piavka in
119
+ # https://github.com/jmettraux/rufus-scheduler/issues/214
120
+ #
121
+ def trigger_off_schedule(time=EoTime.now)
122
+
123
+ do_trigger(time)
124
+ end
125
+
126
+ def unschedule
127
+
128
+ @unscheduled_at = EoTime.now
129
+ end
130
+
131
+ def threads
132
+
133
+ Thread.list.select { |t| t[:rufus_scheduler_job] == self }
134
+ end
135
+
136
+ # Kills all the threads this Job currently has going on.
137
+ #
138
+ def kill
139
+
140
+ threads.each { |t| t.raise(KillSignal) }
141
+ end
142
+
143
+ def running?
144
+
145
+ threads.any?
146
+ end
147
+
148
+ def scheduled?
149
+
150
+ @scheduler.scheduled?(self)
151
+ end
152
+
153
+ def []=(key, value)
154
+
155
+ @local_mutex.synchronize { @locals[key] = value }
156
+ end
157
+
158
+ def [](key)
159
+
160
+ @local_mutex.synchronize { @locals[key] }
161
+ end
162
+
163
+ def has_key?(key)
164
+
165
+ @local_mutex.synchronize { @locals.has_key?(key) }
166
+ end
167
+ alias key? has_key?
168
+
169
+ def keys; @local_mutex.synchronize { @locals.keys }; end
170
+ def values; @local_mutex.synchronize { @locals.values }; end
171
+ def entries; @local_mutex.synchronize { @locals.entries }; end
172
+
173
+ #def hash
174
+ # self.object_id
175
+ #end
176
+ #def eql?(o)
177
+ # o.class == self.class && o.hash == self.hash
178
+ #end
179
+ #
180
+ # might be necessary at some point
181
+
182
+ def next_times(count)
183
+
184
+ next_time ? [ next_time ] : []
185
+ end
186
+
187
+ # Calls the callable (usually a block) wrapped in this Job instance.
188
+ #
189
+ # Warning: error rescueing is the responsibity of the caller.
190
+ #
191
+ def call(do_rescue=false)
192
+
193
+ do_call(EoTime.now, do_rescue)
194
+ end
195
+
196
+ protected
197
+
198
+ def callback(meth, time)
199
+
200
+ return true unless @scheduler.respond_to?(meth)
201
+
202
+ arity = @scheduler.method(meth).arity
203
+ args = [ self, time ][0, (arity < 0 ? 2 : arity)]
204
+
205
+ @scheduler.send(meth, *args)
206
+ end
207
+
208
+ def compute_timeout
209
+
210
+ if to = @opts[:timeout]
211
+ Rufus::Scheduler.parse(to)
212
+ else
213
+ nil
214
+ end
215
+ end
216
+
217
+ def mutex(m)
218
+
219
+ m.is_a?(Mutex) ? m : (@scheduler.mutexes[m.to_s] ||= Mutex.new)
220
+ end
221
+
222
+ def do_call(time, do_rescue)
223
+
224
+ args = [ self, time ][0, @callable.arity]
225
+
226
+ @scheduler.around_trigger(self) do
227
+ @callable.call(*args)
228
+ end
229
+
230
+ rescue StandardError => se
231
+
232
+ fail se unless do_rescue
233
+
234
+ return if se.is_a?(KillSignal) # discard
235
+
236
+ @scheduler.on_error(self, se)
237
+
238
+ # exceptions above StandardError do pass through
239
+ end
240
+
241
+ def do_trigger(time)
242
+
243
+ return if (
244
+ opts[:overlap] == false &&
245
+ running?
246
+ )
247
+ return if (
248
+ callback(:confirm_lock, time) &&
249
+ callback(:on_pre_trigger, time)
250
+ ) == false
251
+
252
+ @count += 1
253
+
254
+ if opts[:blocking]
255
+ trigger_now(time)
256
+ else
257
+ trigger_queue(time)
258
+ end
259
+ end
260
+
261
+ def trigger_now(time)
262
+
263
+ ct = Thread.current
264
+
265
+ t = EoTime.now
266
+ # if there are mutexes, t might be really bigger than time
267
+
268
+ ct[:rufus_scheduler_job] = self
269
+ ct[:rufus_scheduler_time] = t
270
+ ct[:rufus_scheduler_timeout] = compute_timeout
271
+
272
+ @last_time = t
273
+
274
+ do_call(time, true)
275
+
276
+ ensure
277
+
278
+ @last_work_time =
279
+ EoTime.now - ct[:rufus_scheduler_time]
280
+ @mean_work_time =
281
+ ((@count - 1) * @mean_work_time + @last_work_time) / @count
282
+
283
+ post_trigger(time)
284
+
285
+ ct[:rufus_scheduler_job] = nil
286
+ ct[:rufus_scheduler_time] = nil
287
+ ct[:rufus_scheduler_timeout] = nil
288
+ end
289
+
290
+ def post_trigger(time)
291
+
292
+ set_next_time(time, true)
293
+ # except IntervalJob instances, jobs will ignore this call
294
+
295
+ callback(:on_post_trigger, time)
296
+ end
297
+
298
+ def start_work_thread
299
+
300
+ thread =
301
+ Thread.new do
302
+
303
+ ct = Thread.current
304
+
305
+ ct[:rufus_scheduler_job] = true
306
+ # indicates that the thread is going to be assigned immediately
307
+
308
+ ct[@scheduler.thread_key] = true
309
+ ct[:rufus_scheduler_work_thread] = true
310
+
311
+ loop do
312
+
313
+ break if @scheduler.started_at == nil
314
+
315
+ job, time = @scheduler.work_queue.pop
316
+
317
+ break if job == :shutdown
318
+ break if @scheduler.started_at == nil
319
+
320
+ next if job.unscheduled_at
321
+
322
+ begin
323
+
324
+ (job.opts[:mutex] || []).reduce(
325
+ lambda { job.trigger_now(time) }
326
+ ) do |b, m|
327
+ lambda { mutex(m).synchronize { b.call } }
328
+ end.call
329
+
330
+ rescue KillSignal
331
+
332
+ # simply go on looping
333
+ end
334
+ end
335
+ end
336
+
337
+ thread[@scheduler.thread_key] = true
338
+ thread[:rufus_scheduler_work_thread] = true
339
+ #
340
+ # same as above (in the thead block),
341
+ # but since it has to be done as quickly as possible.
342
+ # So, whoever is running first (scheduler thread vs job thread)
343
+ # sets this information
344
+
345
+ thread
346
+ end
347
+
348
+ def trigger_queue(time)
349
+
350
+ threads = @scheduler.work_threads
351
+
352
+ vac = threads.select { |t| t[:rufus_scheduler_job] == nil }.size
353
+ que = @scheduler.work_queue.size
354
+
355
+ cur = threads.size
356
+ max = @scheduler.max_work_threads
357
+
358
+ start_work_thread if vac - que < 1 && cur < max
359
+
360
+ @scheduler.work_queue << [ self, time ]
361
+ end
362
+ end
363
+