tanzeeb-rufus-scheduler 2.0.7.2

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.
@@ -0,0 +1,481 @@
1
+ #--
2
+ # Copyright (c) 2006-2010, 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
+ require 'rufus/sc/version'
27
+ require 'rufus/sc/rtime'
28
+ require 'rufus/sc/cronline'
29
+ require 'rufus/sc/jobs'
30
+ require 'rufus/sc/jobqueues'
31
+
32
+
33
+ module Rufus::Scheduler
34
+
35
+ #
36
+ # It's OK to pass an object responding to :trigger when scheduling a job
37
+ # (instead of passing a block).
38
+ #
39
+ # This is simply a helper module. The rufus-scheduler will check if scheduled
40
+ # object quack (respond to :trigger anyway).
41
+ #
42
+ module Schedulable
43
+ def call (job)
44
+ trigger(job.params)
45
+ end
46
+ def trigger (params)
47
+ raise NotImplementedError.new('implementation is missing')
48
+ end
49
+ end
50
+
51
+ #
52
+ # For backward compatibility
53
+ #
54
+ module ::Rufus::Schedulable
55
+ extend ::Rufus::Scheduler::Schedulable
56
+ end
57
+
58
+ # Legacy from the previous version of Rufus-Scheduler.
59
+ #
60
+ # Consider all methods here as 'deprecated'.
61
+ #
62
+ module LegacyMethods
63
+
64
+ def find_jobs (tag=nil)
65
+ tag ? find_by_tag(tag) : all_jobs.values
66
+ end
67
+ def at_job_count
68
+ @jobs.select(:at).size +
69
+ @jobs.select(:in).size
70
+ end
71
+ def every_job_count
72
+ @jobs.select(:every).size
73
+ end
74
+ def cron_job_count
75
+ @cron_jobs.size
76
+ end
77
+ def pending_job_count
78
+ @jobs.size
79
+ end
80
+ def precision
81
+ @frequency
82
+ end
83
+ end
84
+
85
+ #
86
+ # The core of a rufus-scheduler. See implementations like
87
+ # Rufus::Scheduler::PlainScheduler and Rufus::Scheduler::EmScheduler for
88
+ # directly usable stuff.
89
+ #
90
+ class SchedulerCore
91
+
92
+ include LegacyMethods
93
+
94
+ # classical options hash
95
+ #
96
+ attr_reader :options
97
+
98
+ # Instantiates a Rufus::Scheduler.
99
+ #
100
+ def initialize (opts={})
101
+
102
+ @options = opts
103
+
104
+ @jobs = get_queue(:at, opts)
105
+ @cron_jobs = get_queue(:cron, opts)
106
+
107
+ @frequency = @options[:frequency] || 0.330
108
+ end
109
+
110
+ # Instantiates and starts a new Rufus::Scheduler.
111
+ #
112
+ def self.start_new (opts={})
113
+
114
+ s = self.new(opts)
115
+ s.start
116
+ s
117
+ end
118
+
119
+ #--
120
+ # SCHEDULE METHODS
121
+ #++
122
+
123
+ # Schedules a job in a given amount of time.
124
+ #
125
+ # scheduler.in '20m' do
126
+ # puts "order ristretto"
127
+ # end
128
+ #
129
+ # will order an espresso (well sort of) in 20 minutes.
130
+ #
131
+ def in (t, s=nil, opts={}, &block)
132
+
133
+ add_job(InJob.new(self, t, combine_opts(s, opts), &block))
134
+ end
135
+ alias :schedule_in :in
136
+
137
+ # Schedules a job at a given point in time.
138
+ #
139
+ # scheduler.at 'Thu Mar 26 19:30:00 2009' do
140
+ # puts 'order pizza'
141
+ # end
142
+ #
143
+ # pizza is for Thursday at 2000 (if the shop brochure is right).
144
+ #
145
+ def at (t, s=nil, opts={}, &block)
146
+
147
+ add_job(AtJob.new(self, t, combine_opts(s, opts), &block))
148
+ end
149
+ alias :schedule_at :at
150
+
151
+ # Schedules a recurring job every t.
152
+ #
153
+ # scheduler.every '5m1w' do
154
+ # puts 'check blood pressure'
155
+ # end
156
+ #
157
+ # checking blood pressure every 5 months and 1 week.
158
+ #
159
+ def every (t, s=nil, opts={}, &block)
160
+
161
+ add_job(EveryJob.new(self, t, combine_opts(s, opts), &block))
162
+ end
163
+ alias :schedule_every :every
164
+
165
+ # Schedules a job given a cron string.
166
+ #
167
+ # scheduler.cron '0 22 * * 1-5' do
168
+ # # every day of the week at 00:22
169
+ # puts 'activate security system'
170
+ # end
171
+ #
172
+ def cron (cronstring, s=nil, opts={}, &block)
173
+
174
+ add_cron_job(CronJob.new(self, cronstring, combine_opts(s, opts), &block))
175
+ end
176
+ alias :schedule :cron
177
+
178
+ # Unschedules a job (cron or at/every/in job) given its id.
179
+ #
180
+ # Returns the job that got unscheduled.
181
+ #
182
+ def unschedule (job_id)
183
+
184
+ @jobs.unschedule(job_id) || @cron_jobs.unschedule(job_id)
185
+ end
186
+
187
+ #--
188
+ # MISC
189
+ #++
190
+
191
+ # Feel free to override this method. The default implementation simply
192
+ # outputs the error message to STDOUT
193
+ #
194
+ def handle_exception (job, exception)
195
+
196
+ if self.respond_to?(:log_exception)
197
+ #
198
+ # some kind of backward compatibility
199
+
200
+ log_exception(exception)
201
+
202
+ else
203
+
204
+ puts '=' * 80
205
+ puts "scheduler caught exception :"
206
+ puts exception
207
+ exception.backtrace.each { |l| puts l }
208
+ puts '=' * 80
209
+ end
210
+ end
211
+
212
+ #--
213
+ # JOB LOOKUP
214
+ #++
215
+
216
+ # Returns a map job_id => job for at/in/every jobs
217
+ #
218
+ def jobs
219
+
220
+ @jobs.to_h
221
+ end
222
+
223
+ # Returns a map job_id => job for cron jobs
224
+ #
225
+ def cron_jobs
226
+
227
+ @cron_jobs.to_h
228
+ end
229
+
230
+ # Returns a map job_id => job of all the jobs currently in the scheduler
231
+ #
232
+ def all_jobs
233
+
234
+ jobs.merge(cron_jobs)
235
+ end
236
+
237
+ # Returns a list of jobs with the given tag
238
+ #
239
+ def find_by_tag (tag)
240
+
241
+ all_jobs.values.select { |j| j.tags.include?(tag) }
242
+ end
243
+
244
+ # Returns the current list of trigger threads (threads) dedicated to
245
+ # the execution of jobs.
246
+ #
247
+ def trigger_threads
248
+
249
+ Thread.list.select { |t|
250
+ t["rufus_scheduler__trigger_thread__#{self.object_id}"] == true
251
+ }
252
+ end
253
+
254
+ protected
255
+
256
+ # Returns a job queue instance.
257
+ #
258
+ # (made it into a method for easy override)
259
+ #
260
+ def get_queue (type, opts)
261
+
262
+ q = if type == :cron
263
+ opts[:cron_job_queue] || Rufus::Scheduler::CronJobQueue.new
264
+ else
265
+ opts[:job_queue] || Rufus::Scheduler::JobQueue.new
266
+ end
267
+
268
+ q.scheduler = self if q.respond_to?(:scheduler=)
269
+
270
+ q
271
+ end
272
+
273
+ def combine_opts (schedulable, opts)
274
+
275
+ if schedulable.respond_to?(:trigger) || schedulable.respond_to?(:call)
276
+
277
+ opts[:schedulable] = schedulable
278
+
279
+ elsif schedulable != nil
280
+
281
+ opts = schedulable.merge(opts)
282
+ end
283
+
284
+ opts
285
+ end
286
+
287
+ # The method that does the "wake up and trigger any job that should get
288
+ # triggered.
289
+ #
290
+ def step
291
+
292
+ @cron_jobs.trigger_matching_jobs
293
+ @jobs.trigger_matching_jobs
294
+ end
295
+
296
+ def add_job (job)
297
+
298
+ complain_if_blocking_and_timeout(job)
299
+
300
+ return if job.params[:discard_past] && Time.now.to_f >= job.at
301
+
302
+ @jobs << job
303
+
304
+ job
305
+ end
306
+
307
+ def add_cron_job (job)
308
+
309
+ complain_if_blocking_and_timeout(job)
310
+
311
+ @cron_jobs << job
312
+
313
+ job
314
+ end
315
+
316
+ # Raises an error if the job has the params :blocking and :timeout set
317
+ #
318
+ def complain_if_blocking_and_timeout (job)
319
+
320
+ raise(
321
+ ArgumentError.new('cannot set a :timeout on a :blocking job')
322
+ ) if job.params[:blocking] and job.params[:timeout]
323
+ end
324
+
325
+ # The default, plain, implementation. If 'blocking' is true, will simply
326
+ # call the block and return when the block is done.
327
+ # Else, it will call the block in a dedicated thread.
328
+ #
329
+ # TODO : clarify, the blocking here blocks the whole scheduler, while
330
+ # EmScheduler blocking triggers for the next tick. Not the same thing ...
331
+ #
332
+ def trigger_job (blocking, &block)
333
+
334
+ if blocking
335
+ block.call
336
+ else
337
+ Thread.new { block.call }
338
+ end
339
+ end
340
+ end
341
+
342
+ #--
343
+ # SCHEDULER 'IMPLEMENTATIONS'
344
+ #++
345
+
346
+ #
347
+ # A classical implementation, uses a sleep/step loop in a thread (like the
348
+ # original rufus-scheduler).
349
+ #
350
+ class PlainScheduler < SchedulerCore
351
+
352
+ def start
353
+
354
+ @thread = Thread.new do
355
+ loop do
356
+ sleep(@frequency)
357
+ self.step
358
+ end
359
+ end
360
+
361
+ @thread[:name] =
362
+ @options[:thread_name] ||
363
+ "#{self.class} - #{Rufus::Scheduler::VERSION}"
364
+ end
365
+
366
+ def stop (opts={})
367
+
368
+ @thread.exit
369
+ end
370
+
371
+ def join
372
+
373
+ @thread.join
374
+ end
375
+ end
376
+
377
+ # TODO : investigate idea
378
+ #
379
+ #class BlockingScheduler < PlainScheduler
380
+ # # use a Queue and a worker thread for the 'blocking' jobs
381
+ #end
382
+
383
+ #
384
+ # A rufus-scheduler that steps only when the ruby process receives the
385
+ # 10 / USR1 signal.
386
+ #
387
+ class SignalScheduler < SchedulerCore
388
+
389
+ def initialize (opts={})
390
+
391
+ super(opts)
392
+
393
+ trap(@options[:signal] || 10) do
394
+ step
395
+ end
396
+ end
397
+
398
+ def stop
399
+
400
+ trap(@options[:signal] || 10)
401
+ end
402
+ end
403
+
404
+ #
405
+ # A rufus-scheduler that uses an EventMachine periodic timer instead of a
406
+ # loop.
407
+ #
408
+ class EmScheduler < SchedulerCore
409
+
410
+ def initialize (opts={})
411
+
412
+ raise LoadError.new(
413
+ 'EventMachine missing, "require \'eventmachine\'" might help'
414
+ ) unless defined?(EM)
415
+
416
+ super(opts)
417
+ end
418
+
419
+ def start
420
+
421
+ @em_thread = nil
422
+
423
+ unless EM.reactor_running?
424
+ @em_thread = Thread.new { EM.run }
425
+ while (not EM.reactor_running?)
426
+ Thread.pass
427
+ end
428
+ end
429
+
430
+ #unless EM.reactor_running?
431
+ # t = Thread.current
432
+ # @em_thread = Thread.new { EM.run { t.wakeup } }
433
+ # Thread.stop # EM will wake us up when it's ready
434
+ #end
435
+
436
+ @timer = EM::PeriodicTimer.new(@frequency) { step }
437
+ end
438
+
439
+ # Stops the scheduler.
440
+ #
441
+ # If the :stop_em option is passed and set to true, it will stop the
442
+ # EventMachine (but only if it started the EM by itself !).
443
+ #
444
+ def stop (opts={})
445
+
446
+ @timer.cancel
447
+
448
+ EM.stop if opts[:stop_em] and @em_thread
449
+ end
450
+
451
+ # Joins this scheduler. Will actually join it only if it started the
452
+ # underlying EventMachine.
453
+ #
454
+ def join
455
+
456
+ @em_thread.join if @em_thread
457
+ end
458
+
459
+ protected
460
+
461
+ # If 'blocking' is set to true, the block will get called at the
462
+ # 'next_tick'. Else the block will get called via 'defer' (own thread).
463
+ #
464
+ def trigger_job (blocking, &block)
465
+
466
+ m = blocking ? :next_tick : :defer
467
+ #
468
+ # :next_tick monopolizes the EM
469
+ # :defer executes its block in another thread
470
+
471
+ EM.send(m) { block.call }
472
+ end
473
+ end
474
+
475
+ #
476
+ # This error is thrown when the :timeout attribute triggers
477
+ #
478
+ class TimeOutError < RuntimeError
479
+ end
480
+ end
481
+