rufus-scheduler 3.6.0 → 3.7.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.
@@ -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
+