tanzeeb-rufus-scheduler 2.0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+