rufus-scheduler 3.6.0 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +13 -0
- data/CREDITS.md +5 -0
- data/LICENSE.txt +1 -1
- data/Makefile +1 -1
- data/README.md +73 -11
- data/lib/rufus/scheduler.rb +528 -435
- data/lib/rufus/scheduler/job_array.rb +37 -47
- data/lib/rufus/scheduler/jobs_core.rb +363 -0
- data/lib/rufus/scheduler/jobs_one_time.rb +53 -0
- data/lib/rufus/scheduler/jobs_repeat.rb +333 -0
- data/lib/rufus/scheduler/locks.rb +41 -44
- data/lib/rufus/scheduler/util.rb +166 -150
- data/rufus-scheduler.gemspec +1 -2
- metadata +11 -10
- data/lib/rufus/scheduler/jobs.rb +0 -701
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c0572c41e8ad285c2de6828cc86ef489122cea65c412514566c08c35713a0523
|
4
|
+
data.tar.gz: 327a0c5e462acc3df68d6c55366a956c9e16b4b4adf208b103568cce4f943dd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3153aa15933ee24cf3a11657fb0b8bd3d58d87e054b20d9d759b52cdd924a418fcf95ef1e14b980b459a501ccce91b285cd85c826e0fa4207fac85f69c77667
|
7
|
+
data.tar.gz: e7af296c9864ffc392ef44eec1bb6ed0331065658b9d9d1225cd2c1f62d1d67302db7075540b0c39a99dcf5f741471f3c38666cb8870165cca23a34f52f1c7df
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,19 @@
|
|
2
2
|
# CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
### rufus-scheduler 3.7.0 - released 2020-12-31
|
6
|
+
|
7
|
+
* Implement Job name:/n: #name and #name=, gh-309
|
8
|
+
* Add Job #has_key?, #value, and #entries
|
9
|
+
* Add #locals access to Job
|
10
|
+
* Implement Scheduler#around_trigger, @jjb, gh-310
|
11
|
+
* Accept max_worker_threads: for max_work_threads:
|
12
|
+
* Clean up Scheduler#shutdwon, thanks @itsaphel and @jjb, gh-304, gh-315
|
13
|
+
|
14
|
+
* f59df40 Bring in discard_past: for every jobs, gh-290
|
15
|
+
* 7613277 Introduce :discard_past = false for cron, gh-305
|
16
|
+
|
17
|
+
|
5
18
|
### rufus-scheduler 3.6.0 - released 2019-04-22
|
6
19
|
|
7
20
|
* Let Scheduler#cron fail if the cron string is invalid, gh-289
|
data/CREDITS.md
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
|
5
5
|
## Contributors
|
6
6
|
|
7
|
+
* John Bachir https://github.com/jjb gh-310
|
8
|
+
* Daniel Berger https://github.com/djberg96 gh-300
|
9
|
+
* Ceyhun Onur https://github.com/ceyonur parse_cron no_error: true
|
7
10
|
* Darwin Wu https://github.com/dwaxe Rails initializer vs tests change
|
8
11
|
* Mike Karolow https://github.com/mike-yesware update Travis target Rubies
|
9
12
|
* Jack-Nie https://github.com/jack-nie gh-285 fix broken comment link
|
@@ -64,6 +67,8 @@
|
|
64
67
|
|
65
68
|
## Feedback
|
66
69
|
|
70
|
+
* aphel (https://github.com/itsaphel) #shutdown vs current thread, gh-304
|
71
|
+
* aesyondu (https://github.com/aesyondu) Rails console 4.2.x, gh-186
|
67
72
|
* Sasha Hoellger (https://github.com/mitnal) parse_cron and readme, gh-270
|
68
73
|
* Gian (https://github.com/snmgian) cron vs :first clarification, gh-266
|
69
74
|
* Vito Laurenza (https://github.com/veetow) help debugging tz issues, gh-240
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
Copyright (c) 2005-
|
2
|
+
Copyright (c) 2005-2020, 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
|
data/Makefile
CHANGED
data/README.md
CHANGED
@@ -24,7 +24,11 @@ scheduler.in '3s' do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
scheduler.join
|
27
|
+
#
|
27
28
|
# let the current thread join the scheduler thread
|
29
|
+
#
|
30
|
+
# (please note that this join should be removed when scheduling
|
31
|
+
# in a web application (Rails and friends) initializer)
|
28
32
|
```
|
29
33
|
(run with `ruby quickstart.rb`)
|
30
34
|
|
@@ -73,6 +77,7 @@ A rufus-scheduler instance will go on scheduling while it is present among the o
|
|
73
77
|
## related and similar gems
|
74
78
|
|
75
79
|
* [Whenever](https://github.com/javan/whenever) - let cron call back your Ruby code, trusted and reliable cron drives your schedule
|
80
|
+
* [ruby-clock](https://github.com/jjb/ruby-clock) - a clock process / job scheduler for Ruby
|
76
81
|
* [Clockwork](https://github.com/Rykian/clockwork) - rufus-scheduler inspired gem
|
77
82
|
* [Crono](https://github.com/plashchynski/crono) - an in-Rails cron scheduler
|
78
83
|
* [PerfectSched](https://github.com/treasure-data/perfectsched) - highly available distributed cron built on [Sequel](https://sequel.jeremyevans.net) and more
|
@@ -100,8 +105,8 @@ I'll drive you right to the [tracks](#so-rails).
|
|
100
105
|
* The error_handler is [#on_error](#rufusscheduleron_errorjob-error) (instead of #on_exception), by default it now prints the details of the error to $stderr (used to be $stdout)
|
101
106
|
* Rufus::Scheduler::TimeOutError renamed to Rufus::Scheduler::TimeoutError
|
102
107
|
* Introduction of "interval" jobs. Whereas "every" jobs are like "every 10 minutes, do this", interval jobs are like "do that, then wait for 10 minutes, then do that again, and so on"
|
103
|
-
* Introduction of a :
|
104
|
-
* "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a
|
108
|
+
* Introduction of a lockfile: true/filename mechanism to prevent multiple schedulers from executing
|
109
|
+
* "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a `every '10m'` job is on, it will trigger once at wakeup, not 6 times (discard_past was false by default in rufus-scheduler 2.x). No intention to re-introduce `discard_past: false` in 3.0 for now.
|
105
110
|
* Introduction of Scheduler #on_pre_trigger and #on_post_trigger callback points
|
106
111
|
|
107
112
|
|
@@ -373,6 +378,23 @@ While paused, the scheduler still accepts schedules, but no schedule will get tr
|
|
373
378
|
|
374
379
|
## job options
|
375
380
|
|
381
|
+
### :name => string
|
382
|
+
|
383
|
+
Sets the name of the job.
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
scheduler.cron '*/15 8 * * *', name: 'Robert' do |job|
|
387
|
+
puts "A, it's #{Time.now} and my name is #{job.name}"
|
388
|
+
end
|
389
|
+
|
390
|
+
job1 =
|
391
|
+
scheduler.schedule_cron '*/30 9 * * *', n: 'temporary' do |job|
|
392
|
+
puts "B, it's #{Time.now} and my name is #{job.name}"
|
393
|
+
end
|
394
|
+
# ...
|
395
|
+
job1.name = 'Beowulf'
|
396
|
+
```
|
397
|
+
|
376
398
|
### :blocking => true
|
377
399
|
|
378
400
|
By default, jobs are triggered in their own, new threads. When `:blocking => true`, the job is triggered in the scheduler thread (a new thread is not created). Yes, while a blocking job is running, the scheduler is not scheduling.
|
@@ -460,7 +482,6 @@ s.every '3s', :first => :now do
|
|
460
482
|
end
|
461
483
|
|
462
484
|
s.join
|
463
|
-
|
464
485
|
```
|
465
486
|
|
466
487
|
that'll output something like:
|
@@ -757,9 +778,9 @@ job.tags
|
|
757
778
|
# => [ 'hello' ]
|
758
779
|
```
|
759
780
|
|
760
|
-
### []=, [], key
|
781
|
+
### []=, [], key?, has_key?, keys, values, and entries
|
761
782
|
|
762
|
-
Threads have thread-local variables
|
783
|
+
Threads have thread-local variables, similarly Rufus-scheduler jobs have job-local variables. Those are more like a dict with thread-safe access.
|
763
784
|
|
764
785
|
```ruby
|
765
786
|
job =
|
@@ -774,13 +795,29 @@ sleep 3.6
|
|
774
795
|
job[:counter]
|
775
796
|
# => 3
|
776
797
|
|
777
|
-
job.key?(:timestamp)
|
778
|
-
|
779
|
-
job.keys
|
780
|
-
|
798
|
+
job.key?(:timestamp) # => true
|
799
|
+
job.has_key?(:timestamp) # => true
|
800
|
+
job.keys # => [ :timestamp, :counter ]
|
801
|
+
```
|
802
|
+
|
803
|
+
Locals can be set at schedule time:
|
804
|
+
```ruby
|
805
|
+
job0 =
|
806
|
+
@scheduler.schedule_cron '*/15 12 * * *', locals: { a: 0 } do
|
807
|
+
# ...
|
808
|
+
end
|
809
|
+
job1 =
|
810
|
+
@scheduler.schedule_cron '*/15 13 * * *', l: { a: 1 } do
|
811
|
+
# ...
|
812
|
+
end
|
781
813
|
```
|
782
814
|
|
783
|
-
Job
|
815
|
+
One can fetch the Hash directly with `Job#locals`. Of course, direct manipulation is not thread-safe.
|
816
|
+
```ruby
|
817
|
+
job.locals.entries do |k, v|
|
818
|
+
p "#{k}: #{v}"
|
819
|
+
end
|
820
|
+
```
|
784
821
|
|
785
822
|
### call
|
786
823
|
|
@@ -963,6 +1000,10 @@ Shuts down the scheduler, ceases any scheduler/triggering activity.
|
|
963
1000
|
|
964
1001
|
Shuts down the scheduler, waits (blocks) until all the jobs cease running.
|
965
1002
|
|
1003
|
+
### Scheduler#shutdown(wait: n)
|
1004
|
+
|
1005
|
+
Shuts down the scheduler, waits (blocks) at most n seconds until all the jobs cease running. (Jobs are killed after n seconds have elapsed).
|
1006
|
+
|
966
1007
|
### Scheduler#shutdown(:kill)
|
967
1008
|
|
968
1009
|
Kills all the job (threads) and then shuts the scheduler down. Radical.
|
@@ -985,6 +1026,8 @@ Returns since the count of seconds for which the scheduler has been running.
|
|
985
1026
|
|
986
1027
|
Lets the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
|
987
1028
|
|
1029
|
+
`#join` is mostly used in standalone scheduling script (or tiny one file examples). Calling `#join` from a web application initializer will probably hijack the main thread and prevent the web application from being served. Do not put a `#join` in such a web application initializer file.
|
1030
|
+
|
988
1031
|
### Scheduler#threads
|
989
1032
|
|
990
1033
|
Returns all the threads associated with the scheduler, including the scheduler thread itself.
|
@@ -1097,7 +1140,9 @@ def scheduler.on_error(job, error)
|
|
1097
1140
|
end
|
1098
1141
|
```
|
1099
1142
|
|
1100
|
-
##
|
1143
|
+
## Callbacks
|
1144
|
+
|
1145
|
+
### Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
|
1101
1146
|
|
1102
1147
|
One can bind callbacks before and after jobs trigger:
|
1103
1148
|
|
@@ -1121,6 +1166,21 @@ The ```trigger_time``` is the time at which the job triggers. It might be a bit
|
|
1121
1166
|
|
1122
1167
|
Warning: these two callbacks are executed in the scheduler thread, not in the work threads (the threads where the job execution really happens).
|
1123
1168
|
|
1169
|
+
### Rufus::Scheduler#around_trigger
|
1170
|
+
|
1171
|
+
One can create an around callback which will wrap a job:
|
1172
|
+
|
1173
|
+
```ruby
|
1174
|
+
def s.around_trigger(job)
|
1175
|
+
t = Time.now
|
1176
|
+
puts "Starting job #{job.id}..."
|
1177
|
+
yield
|
1178
|
+
puts "job #{job.id} finished in #{Time.now-t} seconds."
|
1179
|
+
end
|
1180
|
+
```
|
1181
|
+
|
1182
|
+
The around callback is executed in the thread.
|
1183
|
+
|
1124
1184
|
### Rufus::Scheduler#on_pre_trigger as a guard
|
1125
1185
|
|
1126
1186
|
Returning ```false``` in on_pre_trigger will prevent the job from triggering. Returning anything else (nil, -1, true, ...) will let the job trigger.
|
@@ -1574,6 +1634,8 @@ require 'rufus-scheduler'
|
|
1574
1634
|
s = Rufus::Scheduler.singleton
|
1575
1635
|
|
1576
1636
|
return if defined?(Rails::Console) || Rails.env.test? || File.split($0).last == 'rake'
|
1637
|
+
# return if $PROGRAM_NAME.include?('spring')
|
1638
|
+
# see https://github.com/jmettraux/rufus-scheduler/issues/186
|
1577
1639
|
|
1578
1640
|
# do not schedule when Rails is run from its console, for a test/spec, or
|
1579
1641
|
# from a Rake task
|
data/lib/rufus/scheduler.rb
CHANGED
@@ -1,629 +1,722 @@
|
|
1
1
|
|
2
|
-
require 'set'
|
3
2
|
require 'date' if RUBY_VERSION < '1.9.0'
|
4
|
-
require 'time'
|
5
3
|
require 'thread'
|
6
4
|
|
7
5
|
require 'fugit'
|
8
6
|
|
9
7
|
|
10
|
-
module Rufus
|
8
|
+
module Rufus; end
|
11
9
|
|
12
|
-
|
10
|
+
class Rufus::Scheduler
|
13
11
|
|
14
|
-
|
12
|
+
VERSION = '3.7.0'
|
15
13
|
|
16
|
-
|
14
|
+
EoTime = ::EtOrbi::EoTime
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
require 'rufus/scheduler/util'
|
17
|
+
require 'rufus/scheduler/jobs_core'
|
18
|
+
require 'rufus/scheduler/jobs_one_time'
|
19
|
+
require 'rufus/scheduler/jobs_repeat'
|
20
|
+
require 'rufus/scheduler/job_array'
|
21
|
+
require 'rufus/scheduler/locks'
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
#
|
24
|
+
# A common error class for rufus-scheduler
|
25
|
+
#
|
26
|
+
class Error < StandardError; end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
#
|
29
|
+
# This error is thrown when the :timeout attribute triggers
|
30
|
+
#
|
31
|
+
class TimeoutError < Error; end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
#
|
34
|
+
# For when the scheduler is not running
|
35
|
+
# (it got shut down or didn't start because of a lock)
|
36
|
+
#
|
37
|
+
class NotRunningError < Error; end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
#MIN_WORK_THREADS = 3
|
40
|
+
MAX_WORK_THREADS = 28
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
attr_reader :thread
|
45
|
-
attr_reader :thread_key
|
46
|
-
attr_reader :mutexes
|
42
|
+
attr_accessor :frequency
|
43
|
+
attr_accessor :discard_past
|
47
44
|
|
48
|
-
|
49
|
-
|
45
|
+
attr_reader :started_at
|
46
|
+
attr_reader :paused_at
|
47
|
+
attr_reader :thread
|
48
|
+
attr_reader :thread_key
|
49
|
+
attr_reader :mutexes
|
50
50
|
|
51
|
-
|
51
|
+
#attr_accessor :min_work_threads
|
52
|
+
attr_accessor :max_work_threads
|
52
53
|
|
53
|
-
|
54
|
+
attr_accessor :stderr
|
54
55
|
|
55
|
-
|
56
|
+
attr_reader :work_queue
|
56
57
|
|
57
|
-
|
58
|
+
def initialize(opts={})
|
58
59
|
|
59
|
-
|
60
|
-
@paused = false
|
60
|
+
@opts = opts
|
61
61
|
|
62
|
-
|
62
|
+
@started_at = nil
|
63
|
+
@paused_at = nil
|
63
64
|
|
64
|
-
|
65
|
-
@mutexes = {}
|
65
|
+
@jobs = JobArray.new
|
66
66
|
|
67
|
-
|
67
|
+
@frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
|
68
|
+
@discard_past = opts.has_key?(:discard_past) ? opts[:discard_past] : true
|
68
69
|
|
69
|
-
|
70
|
-
@max_work_threads = opts[:max_work_threads] || MAX_WORK_THREADS
|
70
|
+
@mutexes = {}
|
71
71
|
|
72
|
-
|
72
|
+
@work_queue = Queue.new
|
73
|
+
@join_queue = Queue.new
|
73
74
|
|
74
|
-
|
75
|
+
#@min_work_threads =
|
76
|
+
# opts[:min_work_threads] || opts[:min_worker_threads] ||
|
77
|
+
# MIN_WORK_THREADS
|
78
|
+
@max_work_threads =
|
79
|
+
opts[:max_work_threads] || opts[:max_worker_threads] ||
|
80
|
+
MAX_WORK_THREADS
|
75
81
|
|
76
|
-
|
77
|
-
if lockfile = opts[:lockfile]
|
78
|
-
Rufus::Scheduler::FileLock.new(lockfile)
|
79
|
-
else
|
80
|
-
opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
|
81
|
-
end
|
82
|
+
@stderr = $stderr
|
82
83
|
|
83
|
-
|
84
|
+
@thread_key = "rufus_scheduler_#{self.object_id}"
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
@scheduler_lock =
|
87
|
+
if lockfile = opts[:lockfile]
|
88
|
+
Rufus::Scheduler::FileLock.new(lockfile)
|
89
|
+
else
|
90
|
+
opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
|
91
|
+
end
|
87
92
|
|
88
|
-
|
89
|
-
end
|
93
|
+
@trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new
|
90
94
|
|
91
|
-
#
|
92
|
-
|
93
|
-
def self.singleton(opts={})
|
95
|
+
# If we can't grab the @scheduler_lock, don't run.
|
96
|
+
lock || return
|
94
97
|
|
95
|
-
|
96
|
-
|
98
|
+
start
|
99
|
+
end
|
97
100
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
+
# Returns a singleton Rufus::Scheduler instance
|
102
|
+
#
|
103
|
+
def self.singleton(opts={})
|
101
104
|
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
# For now, let's assume the people pointing at rufus-scheduler/master
|
106
|
-
# on GitHub know what they do...
|
107
|
-
#
|
108
|
-
def self.start_new
|
105
|
+
@singleton ||= Rufus::Scheduler.new(opts)
|
106
|
+
end
|
109
107
|
|
110
|
-
|
111
|
-
|
108
|
+
# Alias for Rufus::Scheduler.singleton
|
109
|
+
#
|
110
|
+
def self.s(opts={}); singleton(opts); end
|
112
111
|
|
113
|
-
|
112
|
+
# Releasing the gem would probably require redirecting .start_new to
|
113
|
+
# .new and emit a simple deprecation message.
|
114
|
+
#
|
115
|
+
# For now, let's assume the people pointing at rufus-scheduler/master
|
116
|
+
# on GitHub know what they do...
|
117
|
+
#
|
118
|
+
def self.start_new
|
114
119
|
|
115
|
-
|
120
|
+
fail 'this is rufus-scheduler 3.x, use .new instead of .start_new'
|
121
|
+
end
|
116
122
|
|
117
|
-
|
118
|
-
#
|
119
|
-
# which provokes https://github.com/jmettraux/rufus-scheduler/issues/98
|
120
|
-
# using the following instead:
|
121
|
-
#
|
122
|
-
@jobs.array.each { |j| j.unschedule }
|
123
|
+
def uptime
|
123
124
|
|
124
|
-
|
125
|
+
@started_at ? EoTime.now - @started_at : nil
|
126
|
+
end
|
125
127
|
|
126
|
-
|
127
|
-
join_all_work_threads
|
128
|
-
elsif opt == :kill
|
129
|
-
kill_all_work_threads
|
130
|
-
end
|
128
|
+
def around_trigger(job)
|
131
129
|
|
132
|
-
|
133
|
-
|
130
|
+
yield
|
131
|
+
end
|
134
132
|
|
135
|
-
|
133
|
+
def uptime_s
|
136
134
|
|
137
|
-
|
135
|
+
uptime ? self.class.to_duration(uptime) : ''
|
136
|
+
end
|
138
137
|
|
139
|
-
|
140
|
-
end
|
138
|
+
def join(time_limit=nil)
|
141
139
|
|
142
|
-
|
140
|
+
fail NotRunningError.new('cannot join scheduler that is not running') \
|
141
|
+
unless @thread
|
142
|
+
fail ThreadError.new('scheduler thread cannot join itself') \
|
143
|
+
if @thread == Thread.current
|
143
144
|
|
144
|
-
|
145
|
+
if time_limit
|
146
|
+
time_limit_join(time_limit)
|
147
|
+
else
|
148
|
+
no_time_limit_join
|
145
149
|
end
|
150
|
+
end
|
146
151
|
|
147
|
-
|
152
|
+
def down?
|
148
153
|
|
149
|
-
|
150
|
-
|
151
|
-
) unless @thread
|
154
|
+
! @started_at
|
155
|
+
end
|
152
156
|
|
153
|
-
|
154
|
-
end
|
157
|
+
def up?
|
155
158
|
|
156
|
-
|
159
|
+
!! @started_at
|
160
|
+
end
|
157
161
|
|
158
|
-
|
159
|
-
end
|
162
|
+
def paused?
|
160
163
|
|
161
|
-
|
164
|
+
!! @paused_at
|
165
|
+
end
|
162
166
|
|
163
|
-
|
164
|
-
end
|
167
|
+
def pause
|
165
168
|
|
166
|
-
|
169
|
+
@paused_at = EoTime.now
|
170
|
+
end
|
167
171
|
|
168
|
-
|
169
|
-
end
|
172
|
+
def resume(opts={})
|
170
173
|
|
171
|
-
|
174
|
+
dp = opts[:discard_past]
|
175
|
+
jobs.each { |job| job.resume_discard_past = dp }
|
172
176
|
|
173
|
-
|
174
|
-
|
177
|
+
@paused_at = nil
|
178
|
+
end
|
175
179
|
|
176
|
-
|
180
|
+
#--
|
181
|
+
# scheduling methods
|
182
|
+
#++
|
177
183
|
|
178
|
-
|
179
|
-
end
|
184
|
+
def at(time, callable=nil, opts={}, &block)
|
180
185
|
|
181
|
-
|
182
|
-
|
183
|
-
#++
|
186
|
+
do_schedule(:once, time, callable, opts, opts[:job], block)
|
187
|
+
end
|
184
188
|
|
185
|
-
|
189
|
+
def schedule_at(time, callable=nil, opts={}, &block)
|
186
190
|
|
187
|
-
|
188
|
-
|
191
|
+
do_schedule(:once, time, callable, opts, true, block)
|
192
|
+
end
|
189
193
|
|
190
|
-
|
194
|
+
def in(duration, callable=nil, opts={}, &block)
|
191
195
|
|
192
|
-
|
193
|
-
|
196
|
+
do_schedule(:once, duration, callable, opts, opts[:job], block)
|
197
|
+
end
|
194
198
|
|
195
|
-
|
199
|
+
def schedule_in(duration, callable=nil, opts={}, &block)
|
196
200
|
|
197
|
-
|
198
|
-
|
201
|
+
do_schedule(:once, duration, callable, opts, true, block)
|
202
|
+
end
|
199
203
|
|
200
|
-
|
204
|
+
def every(duration, callable=nil, opts={}, &block)
|
201
205
|
|
202
|
-
|
203
|
-
|
206
|
+
do_schedule(:every, duration, callable, opts, opts[:job], block)
|
207
|
+
end
|
204
208
|
|
205
|
-
|
209
|
+
def schedule_every(duration, callable=nil, opts={}, &block)
|
206
210
|
|
207
|
-
|
208
|
-
|
211
|
+
do_schedule(:every, duration, callable, opts, true, block)
|
212
|
+
end
|
209
213
|
|
210
|
-
|
214
|
+
def interval(duration, callable=nil, opts={}, &block)
|
211
215
|
|
212
|
-
|
213
|
-
|
216
|
+
do_schedule(:interval, duration, callable, opts, opts[:job], block)
|
217
|
+
end
|
214
218
|
|
215
|
-
|
219
|
+
def schedule_interval(duration, callable=nil, opts={}, &block)
|
216
220
|
|
217
|
-
|
218
|
-
|
221
|
+
do_schedule(:interval, duration, callable, opts, true, block)
|
222
|
+
end
|
219
223
|
|
220
|
-
|
224
|
+
def cron(cronline, callable=nil, opts={}, &block)
|
221
225
|
|
222
|
-
|
223
|
-
|
226
|
+
do_schedule(:cron, cronline, callable, opts, opts[:job], block)
|
227
|
+
end
|
224
228
|
|
225
|
-
|
229
|
+
def schedule_cron(cronline, callable=nil, opts={}, &block)
|
226
230
|
|
227
|
-
|
228
|
-
|
231
|
+
do_schedule(:cron, cronline, callable, opts, true, block)
|
232
|
+
end
|
233
|
+
|
234
|
+
def schedule(arg, callable=nil, opts={}, &block)
|
229
235
|
|
230
|
-
|
236
|
+
callable, opts = nil, callable if callable.is_a?(Hash)
|
237
|
+
opts = opts.dup
|
231
238
|
|
232
|
-
|
239
|
+
opts[:_t] = Rufus::Scheduler.parse(arg, opts)
|
240
|
+
|
241
|
+
case opts[:_t]
|
242
|
+
when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
|
243
|
+
when ::EtOrbi::EoTime, Time then schedule_at(arg, callable, opts, &block)
|
244
|
+
else schedule_in(arg, callable, opts, &block)
|
233
245
|
end
|
246
|
+
end
|
234
247
|
|
235
|
-
|
248
|
+
def repeat(arg, callable=nil, opts={}, &block)
|
236
249
|
|
237
|
-
|
238
|
-
|
250
|
+
callable, opts = nil, callable if callable.is_a?(Hash)
|
251
|
+
opts = opts.dup
|
239
252
|
|
240
|
-
|
253
|
+
opts[:_t] = Rufus::Scheduler.parse(arg, opts)
|
241
254
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
else schedule_in(arg, callable, opts, &block)
|
246
|
-
end
|
255
|
+
case opts[:_t]
|
256
|
+
when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
|
257
|
+
else schedule_every(arg, callable, opts, &block)
|
247
258
|
end
|
259
|
+
end
|
248
260
|
|
249
|
-
|
261
|
+
def unschedule(job_or_job_id)
|
250
262
|
|
251
|
-
|
252
|
-
opts = opts.dup
|
263
|
+
job, job_id = fetch(job_or_job_id)
|
253
264
|
|
254
|
-
|
265
|
+
fail ArgumentError.new("no job found with id '#{job_id}'") unless job
|
255
266
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
267
|
+
job.unschedule if job
|
268
|
+
end
|
269
|
+
|
270
|
+
#--
|
271
|
+
# jobs methods
|
272
|
+
#++
|
261
273
|
|
262
|
-
|
274
|
+
# Returns all the scheduled jobs
|
275
|
+
# (even those right before re-schedule).
|
276
|
+
#
|
277
|
+
def jobs(opts={})
|
263
278
|
|
264
|
-
|
279
|
+
opts = { opts => true } if opts.is_a?(Symbol)
|
265
280
|
|
266
|
-
|
281
|
+
jobs = @jobs.to_a
|
267
282
|
|
268
|
-
|
283
|
+
if opts[:running]
|
284
|
+
jobs = jobs.select { |j| j.running? }
|
285
|
+
elsif ! opts[:all]
|
286
|
+
jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
|
269
287
|
end
|
270
288
|
|
271
|
-
|
272
|
-
|
273
|
-
|
289
|
+
tags = Array(opts[:tag] || opts[:tags]).collect(&:to_s)
|
290
|
+
jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
|
291
|
+
|
292
|
+
jobs
|
293
|
+
end
|
274
294
|
|
275
|
-
|
276
|
-
# (even those right before re-schedule).
|
277
|
-
#
|
278
|
-
def jobs(opts={})
|
295
|
+
def at_jobs(opts={})
|
279
296
|
|
280
|
-
|
297
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
|
298
|
+
end
|
281
299
|
|
282
|
-
|
300
|
+
def in_jobs(opts={})
|
283
301
|
|
284
|
-
|
285
|
-
|
286
|
-
elsif ! opts[:all]
|
287
|
-
jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
|
288
|
-
end
|
302
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
|
303
|
+
end
|
289
304
|
|
290
|
-
|
291
|
-
jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
|
305
|
+
def every_jobs(opts={})
|
292
306
|
|
293
|
-
|
294
|
-
|
307
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
|
308
|
+
end
|
295
309
|
|
296
|
-
|
310
|
+
def interval_jobs(opts={})
|
297
311
|
|
298
|
-
|
299
|
-
|
312
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
|
313
|
+
end
|
300
314
|
|
301
|
-
|
315
|
+
def cron_jobs(opts={})
|
302
316
|
|
303
|
-
|
304
|
-
|
317
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
|
318
|
+
end
|
305
319
|
|
306
|
-
|
320
|
+
def job(job_id)
|
307
321
|
|
308
|
-
|
309
|
-
|
322
|
+
@jobs[job_id]
|
323
|
+
end
|
310
324
|
|
311
|
-
|
325
|
+
# Returns true if the scheduler has acquired the [exclusive] lock and
|
326
|
+
# thus may run.
|
327
|
+
#
|
328
|
+
# Most of the time, a scheduler is run alone and this method should
|
329
|
+
# return true. It is useful in cases where among a group of applications
|
330
|
+
# only one of them should run the scheduler. For schedulers that should
|
331
|
+
# not run, the method should return false.
|
332
|
+
#
|
333
|
+
# Out of the box, rufus-scheduler proposes the
|
334
|
+
# :lockfile => 'path/to/lock/file' scheduler start option. It makes
|
335
|
+
# it easy for schedulers on the same machine to determine which should
|
336
|
+
# run (the first to write the lockfile and lock it). It uses "man 2 flock"
|
337
|
+
# so it probably won't work reliably on distributed file systems.
|
338
|
+
#
|
339
|
+
# If one needs to use a special/different locking mechanism, the scheduler
|
340
|
+
# accepts :scheduler_lock => lock_object. lock_object only needs to respond
|
341
|
+
# to #lock
|
342
|
+
# and #unlock, and both of these methods should be idempotent.
|
343
|
+
#
|
344
|
+
# Look at rufus/scheduler/locks.rb for an example.
|
345
|
+
#
|
346
|
+
def lock
|
347
|
+
|
348
|
+
@scheduler_lock.lock
|
349
|
+
end
|
312
350
|
|
313
|
-
|
314
|
-
|
351
|
+
# Sister method to #lock, is called when the scheduler shuts down.
|
352
|
+
#
|
353
|
+
def unlock
|
315
354
|
|
316
|
-
|
355
|
+
@trigger_lock.unlock
|
356
|
+
@scheduler_lock.unlock
|
357
|
+
end
|
317
358
|
|
318
|
-
|
319
|
-
|
359
|
+
# Callback called when a job is triggered. If the lock cannot be acquired,
|
360
|
+
# the job won't run (though it'll still be scheduled to run again if
|
361
|
+
# necessary).
|
362
|
+
#
|
363
|
+
def confirm_lock
|
320
364
|
|
321
|
-
|
365
|
+
@trigger_lock.lock
|
366
|
+
end
|
322
367
|
|
323
|
-
|
324
|
-
|
368
|
+
# Returns true if this job is currently scheduled.
|
369
|
+
#
|
370
|
+
# Takes extra care to answer true if the job is a repeat job
|
371
|
+
# currently firing.
|
372
|
+
#
|
373
|
+
def scheduled?(job_or_job_id)
|
325
374
|
|
326
|
-
|
327
|
-
# thus may run.
|
328
|
-
#
|
329
|
-
# Most of the time, a scheduler is run alone and this method should
|
330
|
-
# return true. It is useful in cases where among a group of applications
|
331
|
-
# only one of them should run the scheduler. For schedulers that should
|
332
|
-
# not run, the method should return false.
|
333
|
-
#
|
334
|
-
# Out of the box, rufus-scheduler proposes the
|
335
|
-
# :lockfile => 'path/to/lock/file' scheduler start option. It makes
|
336
|
-
# it easy for schedulers on the same machine to determine which should
|
337
|
-
# run (the first to write the lockfile and lock it). It uses "man 2 flock"
|
338
|
-
# so it probably won't work reliably on distributed file systems.
|
339
|
-
#
|
340
|
-
# If one needs to use a special/different locking mechanism, the scheduler
|
341
|
-
# accepts :scheduler_lock => lock_object. lock_object only needs to respond
|
342
|
-
# to #lock
|
343
|
-
# and #unlock, and both of these methods should be idempotent.
|
344
|
-
#
|
345
|
-
# Look at rufus/scheduler/locks.rb for an example.
|
346
|
-
#
|
347
|
-
def lock
|
348
|
-
|
349
|
-
@scheduler_lock.lock
|
350
|
-
end
|
375
|
+
job, _ = fetch(job_or_job_id)
|
351
376
|
|
352
|
-
|
353
|
-
|
354
|
-
def unlock
|
377
|
+
!! (job && job.unscheduled_at.nil? && job.next_time != nil)
|
378
|
+
end
|
355
379
|
|
356
|
-
|
357
|
-
|
358
|
-
|
380
|
+
# Lists all the threads associated with this scheduler.
|
381
|
+
#
|
382
|
+
def threads
|
359
383
|
|
360
|
-
|
361
|
-
|
362
|
-
# necessary).
|
363
|
-
#
|
364
|
-
def confirm_lock
|
384
|
+
Thread.list.select { |t| t[thread_key] }
|
385
|
+
end
|
365
386
|
|
366
|
-
|
387
|
+
# Lists all the work threads (the ones actually running the scheduled
|
388
|
+
# block code)
|
389
|
+
#
|
390
|
+
# Accepts a query option, which can be set to:
|
391
|
+
# * :all (default), returns all the threads that are work threads
|
392
|
+
# or are currently running a job
|
393
|
+
# * :active, returns all threads that are currently running a job
|
394
|
+
# * :vacant, returns the threads that are not running a job
|
395
|
+
#
|
396
|
+
# If, thanks to :blocking => true, a job is scheduled to monopolize the
|
397
|
+
# main scheduler thread, that thread will get returned when :active or
|
398
|
+
# :all.
|
399
|
+
#
|
400
|
+
def work_threads(query=:all)
|
401
|
+
|
402
|
+
ts = threads.select { |t| t[:rufus_scheduler_work_thread] }
|
403
|
+
|
404
|
+
case query
|
405
|
+
when :active then ts.select { |t| t[:rufus_scheduler_job] }
|
406
|
+
when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
|
407
|
+
else ts
|
367
408
|
end
|
409
|
+
end
|
368
410
|
|
369
|
-
|
370
|
-
#
|
371
|
-
# Takes extra care to answer true if the job is a repeat job
|
372
|
-
# currently firing.
|
373
|
-
#
|
374
|
-
def scheduled?(job_or_job_id)
|
411
|
+
def running_jobs(opts={})
|
375
412
|
|
376
|
-
|
413
|
+
jobs(opts.merge(:running => true))
|
414
|
+
end
|
377
415
|
|
378
|
-
|
379
|
-
end
|
416
|
+
def occurrences(time0, time1, format=:per_job)
|
380
417
|
|
381
|
-
|
382
|
-
#
|
383
|
-
def threads
|
418
|
+
h = {}
|
384
419
|
|
385
|
-
|
420
|
+
jobs.each do |j|
|
421
|
+
os = j.occurrences(time0, time1)
|
422
|
+
h[j] = os if os.any?
|
386
423
|
end
|
387
424
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
# * :active, returns all threads that are currently running a job
|
395
|
-
# * :vacant, returns the threads that are not running a job
|
396
|
-
#
|
397
|
-
# If, thanks to :blocking => true, a job is scheduled to monopolize the
|
398
|
-
# main scheduler thread, that thread will get returned when :active or
|
399
|
-
# :all.
|
400
|
-
#
|
401
|
-
def work_threads(query=:all)
|
402
|
-
|
403
|
-
ts = threads.select { |t| t[:rufus_scheduler_work_thread] }
|
404
|
-
|
405
|
-
case query
|
406
|
-
when :active then ts.select { |t| t[:rufus_scheduler_job] }
|
407
|
-
when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
|
408
|
-
else ts
|
409
|
-
end
|
425
|
+
if format == :timeline
|
426
|
+
a = []
|
427
|
+
h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
|
428
|
+
a.sort_by { |(t, _)| t }
|
429
|
+
else
|
430
|
+
h
|
410
431
|
end
|
432
|
+
end
|
411
433
|
|
412
|
-
|
434
|
+
def timeline(time0, time1)
|
413
435
|
|
414
|
-
|
415
|
-
|
436
|
+
occurrences(time0, time1, :timeline)
|
437
|
+
end
|
416
438
|
|
417
|
-
|
439
|
+
def on_error(job, err)
|
440
|
+
|
441
|
+
pre = err.object_id.to_s
|
442
|
+
|
443
|
+
ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }
|
444
|
+
|
445
|
+
stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
|
446
|
+
stderr.puts(" #{pre} job:")
|
447
|
+
stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
|
448
|
+
# TODO: eventually use a Job#detail or something like that
|
449
|
+
stderr.puts(" #{pre} error:")
|
450
|
+
stderr.puts(" #{pre} #{err.object_id}")
|
451
|
+
stderr.puts(" #{pre} #{err.class}")
|
452
|
+
stderr.puts(" #{pre} #{err}")
|
453
|
+
err.backtrace.each do |l|
|
454
|
+
stderr.puts(" #{pre} #{l}")
|
455
|
+
end
|
456
|
+
stderr.puts(" #{pre} tz:")
|
457
|
+
stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
|
458
|
+
stderr.puts(" #{pre} Time.now: #{Time.now}")
|
459
|
+
stderr.puts(" #{pre} local_tzone: #{EoTime.local_tzone.inspect}")
|
460
|
+
stderr.puts(" #{pre} et-orbi:")
|
461
|
+
stderr.puts(" #{pre} #{EoTime.platform_info}")
|
462
|
+
stderr.puts(" #{pre} scheduler:")
|
463
|
+
stderr.puts(" #{pre} object_id: #{object_id}")
|
464
|
+
stderr.puts(" #{pre} opts:")
|
465
|
+
stderr.puts(" #{pre} #{@opts.inspect}")
|
466
|
+
stderr.puts(" #{pre} frequency: #{self.frequency}")
|
467
|
+
stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
|
468
|
+
stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
|
469
|
+
stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
|
470
|
+
stderr.puts(" #{pre} down?: #{down?}")
|
471
|
+
stderr.puts(" #{pre} frequency: #{frequency.inspect}")
|
472
|
+
stderr.puts(" #{pre} discard_past: #{discard_past.inspect}")
|
473
|
+
stderr.puts(" #{pre} started_at: #{started_at.inspect}")
|
474
|
+
stderr.puts(" #{pre} paused_at: #{paused_at.inspect}")
|
475
|
+
stderr.puts(" #{pre} threads: #{self.threads.size}")
|
476
|
+
stderr.puts(" #{pre} thread: #{self.thread}")
|
477
|
+
stderr.puts(" #{pre} thread_key: #{self.thread_key}")
|
478
|
+
stderr.puts(" #{pre} work_threads: #{work_threads.size}")
|
479
|
+
stderr.puts(" #{pre} active: #{work_threads(:active).size}")
|
480
|
+
stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
|
481
|
+
stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
|
482
|
+
stderr.puts(" #{pre} mutexes: #{ms.inspect}")
|
483
|
+
stderr.puts(" #{pre} jobs: #{jobs.size}")
|
484
|
+
stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
|
485
|
+
stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
|
486
|
+
stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
|
487
|
+
stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
|
488
|
+
stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
|
489
|
+
stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
|
490
|
+
stderr.puts(" #{pre} work_queue:")
|
491
|
+
stderr.puts(" #{pre} size: #{@work_queue.size}")
|
492
|
+
stderr.puts(" #{pre} num_waiting: #{@work_queue.num_waiting}")
|
493
|
+
stderr.puts(" #{pre} join_queue:")
|
494
|
+
stderr.puts(" #{pre} size: #{@join_queue.size}")
|
495
|
+
stderr.puts(" #{pre} num_waiting: #{@join_queue.num_waiting}")
|
496
|
+
stderr.puts("} #{pre} .")
|
497
|
+
|
498
|
+
rescue => e
|
499
|
+
|
500
|
+
stderr.puts("failure in #on_error itself:")
|
501
|
+
stderr.puts(e.inspect)
|
502
|
+
stderr.puts(e.backtrace)
|
503
|
+
|
504
|
+
ensure
|
505
|
+
|
506
|
+
stderr.flush
|
507
|
+
end
|
418
508
|
|
419
|
-
|
509
|
+
def shutdown(opt=nil)
|
420
510
|
|
421
|
-
|
422
|
-
|
423
|
-
|
511
|
+
opts =
|
512
|
+
case opt
|
513
|
+
when Symbol then { opt => true }
|
514
|
+
when Hash then opt
|
515
|
+
else {}
|
424
516
|
end
|
425
517
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
518
|
+
@jobs.unschedule_all
|
519
|
+
|
520
|
+
if opts[:wait] || opts[:join]
|
521
|
+
join_shutdown(opts)
|
522
|
+
elsif opts[:kill]
|
523
|
+
kill_shutdown(opts)
|
524
|
+
else
|
525
|
+
regular_shutdown(opts)
|
433
526
|
end
|
434
527
|
|
435
|
-
|
528
|
+
@work_queue.clear
|
436
529
|
|
437
|
-
|
438
|
-
end
|
530
|
+
unlock
|
439
531
|
|
440
|
-
|
532
|
+
@thread.join
|
533
|
+
end
|
534
|
+
alias stop shutdown
|
441
535
|
|
442
|
-
|
536
|
+
protected
|
443
537
|
|
444
|
-
|
538
|
+
def join_shutdown(opts)
|
445
539
|
|
446
|
-
|
447
|
-
|
448
|
-
stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
|
449
|
-
# TODO: eventually use a Job#detail or something like that
|
450
|
-
stderr.puts(" #{pre} error:")
|
451
|
-
stderr.puts(" #{pre} #{err.object_id}")
|
452
|
-
stderr.puts(" #{pre} #{err.class}")
|
453
|
-
stderr.puts(" #{pre} #{err}")
|
454
|
-
err.backtrace.each do |l|
|
455
|
-
stderr.puts(" #{pre} #{l}")
|
456
|
-
end
|
457
|
-
stderr.puts(" #{pre} tz:")
|
458
|
-
stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
|
459
|
-
stderr.puts(" #{pre} Time.now: #{Time.now}")
|
460
|
-
stderr.puts(" #{pre} local_tzone: #{EoTime.local_tzone.inspect}")
|
461
|
-
stderr.puts(" #{pre} et-orbi:")
|
462
|
-
stderr.puts(" #{pre} #{EoTime.platform_info}")
|
463
|
-
stderr.puts(" #{pre} scheduler:")
|
464
|
-
stderr.puts(" #{pre} object_id: #{object_id}")
|
465
|
-
stderr.puts(" #{pre} opts:")
|
466
|
-
stderr.puts(" #{pre} #{@opts.inspect}")
|
467
|
-
stderr.puts(" #{pre} frequency: #{self.frequency}")
|
468
|
-
stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
|
469
|
-
stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
|
470
|
-
stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
|
471
|
-
stderr.puts(" #{pre} down?: #{down?}")
|
472
|
-
stderr.puts(" #{pre} threads: #{self.threads.size}")
|
473
|
-
stderr.puts(" #{pre} thread: #{self.thread}")
|
474
|
-
stderr.puts(" #{pre} thread_key: #{self.thread_key}")
|
475
|
-
stderr.puts(" #{pre} work_threads: #{work_threads.size}")
|
476
|
-
stderr.puts(" #{pre} active: #{work_threads(:active).size}")
|
477
|
-
stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
|
478
|
-
stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
|
479
|
-
stderr.puts(" #{pre} mutexes: #{ms.inspect}")
|
480
|
-
stderr.puts(" #{pre} jobs: #{jobs.size}")
|
481
|
-
stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
|
482
|
-
stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
|
483
|
-
stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
|
484
|
-
stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
|
485
|
-
stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
|
486
|
-
stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
|
487
|
-
stderr.puts(" #{pre} work_queue: #{work_queue.size}")
|
488
|
-
stderr.puts("} #{pre} .")
|
489
|
-
|
490
|
-
rescue => e
|
491
|
-
|
492
|
-
stderr.puts("failure in #on_error itself:")
|
493
|
-
stderr.puts(e.inspect)
|
494
|
-
stderr.puts(e.backtrace)
|
495
|
-
|
496
|
-
ensure
|
497
|
-
|
498
|
-
stderr.flush
|
499
|
-
end
|
540
|
+
limit = opts[:wait] || opts[:join]
|
541
|
+
limit = limit.is_a?(Numeric) ? limit : nil
|
500
542
|
|
501
|
-
|
543
|
+
#@started_at = nil
|
544
|
+
#
|
545
|
+
# when @started_at is nil, the scheduler thread exits, here
|
546
|
+
# we want it to exit when all the work threads have been joined
|
547
|
+
# hence it's set to nil later on
|
548
|
+
#
|
549
|
+
@paused_at = EoTime.now
|
502
550
|
|
503
|
-
|
504
|
-
#
|
505
|
-
def fetch(job_or_job_id)
|
551
|
+
(work_threads.size * 2 + 1).times { @work_queue << :shutdown }
|
506
552
|
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
end
|
553
|
+
work_threads
|
554
|
+
.collect { |wt|
|
555
|
+
wt == Thread.current ? nil : Thread.new { wt.join(limit); wt.kill } }
|
556
|
+
.each { |st|
|
557
|
+
st.join if st }
|
513
558
|
|
514
|
-
|
559
|
+
@started_at = nil
|
560
|
+
end
|
515
561
|
|
516
|
-
|
562
|
+
def kill_shutdown(opts)
|
517
563
|
|
518
|
-
|
519
|
-
|
564
|
+
@started_at = nil
|
565
|
+
work_threads.each(&:kill)
|
566
|
+
end
|
567
|
+
|
568
|
+
def regular_shutdown(opts)
|
569
|
+
|
570
|
+
@started_at = nil
|
571
|
+
end
|
520
572
|
|
521
|
-
|
573
|
+
def time_limit_join(limit)
|
522
574
|
|
523
|
-
|
575
|
+
fail ArgumentError.new("limit #{limit.inspect} should be > 0") \
|
576
|
+
unless limit.is_a?(Numeric) && limit > 0
|
524
577
|
|
525
|
-
|
578
|
+
t0 = monow
|
579
|
+
f = [ limit.to_f / 20, 0.100 ].min
|
526
580
|
|
527
|
-
|
581
|
+
while monow - t0 < limit
|
582
|
+
r =
|
583
|
+
begin
|
584
|
+
@join_queue.pop(true)
|
585
|
+
rescue ThreadError => e
|
586
|
+
# #<ThreadError: queue empty>
|
587
|
+
false
|
588
|
+
end
|
589
|
+
return r if r
|
590
|
+
sleep(f)
|
528
591
|
end
|
529
592
|
|
530
|
-
|
593
|
+
nil
|
594
|
+
end
|
595
|
+
|
596
|
+
def no_time_limit_join
|
597
|
+
|
598
|
+
@join_queue.pop
|
599
|
+
end
|
600
|
+
|
601
|
+
# Returns [ job, job_id ]
|
602
|
+
#
|
603
|
+
def fetch(job_or_job_id)
|
531
604
|
|
532
|
-
|
605
|
+
if job_or_job_id.respond_to?(:job_id)
|
606
|
+
[ job_or_job_id, job_or_job_id.job_id ]
|
607
|
+
else
|
608
|
+
[ job(job_or_job_id), job_or_job_id ]
|
533
609
|
end
|
610
|
+
end
|
611
|
+
|
612
|
+
def terminate_all_jobs
|
534
613
|
|
535
|
-
|
536
|
-
#
|
537
|
-
# work_threads.each { |t| t.raise(KillSignal) }
|
538
|
-
#end
|
614
|
+
jobs.each { |j| j.unschedule }
|
539
615
|
|
540
|
-
|
616
|
+
sleep 0.01 while running_jobs.size > 0
|
617
|
+
end
|
618
|
+
|
619
|
+
#def free_all_work_threads
|
620
|
+
#
|
621
|
+
# work_threads.each { |t| t.raise(KillSignal) }
|
622
|
+
#end
|
541
623
|
|
542
|
-
|
624
|
+
def start
|
543
625
|
|
544
|
-
|
545
|
-
Thread.new do
|
626
|
+
@started_at = EoTime.now
|
546
627
|
|
547
|
-
|
628
|
+
@thread =
|
629
|
+
Thread.new do
|
548
630
|
|
549
|
-
|
550
|
-
trigger_jobs unless @paused
|
551
|
-
timeout_jobs
|
631
|
+
while @started_at do
|
552
632
|
|
553
|
-
|
554
|
-
|
633
|
+
unschedule_jobs
|
634
|
+
trigger_jobs unless @paused_at
|
635
|
+
timeout_jobs
|
636
|
+
|
637
|
+
sleep(@frequency)
|
555
638
|
end
|
556
639
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
640
|
+
rejoin
|
641
|
+
end
|
642
|
+
|
643
|
+
@thread[@thread_key] = true
|
644
|
+
@thread[:rufus_scheduler] = self
|
645
|
+
@thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
|
646
|
+
end
|
561
647
|
|
562
|
-
|
648
|
+
def unschedule_jobs
|
563
649
|
|
564
|
-
|
565
|
-
|
650
|
+
@jobs.delete_unscheduled
|
651
|
+
end
|
566
652
|
|
567
|
-
|
653
|
+
def trigger_jobs
|
568
654
|
|
569
|
-
|
655
|
+
now = EoTime.now
|
570
656
|
|
571
|
-
|
657
|
+
@jobs.each(now) do |job|
|
572
658
|
|
573
|
-
|
574
|
-
end
|
659
|
+
job.trigger(now)
|
575
660
|
end
|
661
|
+
end
|
576
662
|
|
577
|
-
|
663
|
+
def timeout_jobs
|
578
664
|
|
579
|
-
|
665
|
+
work_threads(:active).each do |t|
|
580
666
|
|
581
|
-
|
582
|
-
|
583
|
-
|
667
|
+
job = t[:rufus_scheduler_job]
|
668
|
+
to = t[:rufus_scheduler_timeout]
|
669
|
+
ts = t[:rufus_scheduler_time]
|
584
670
|
|
585
|
-
|
586
|
-
|
671
|
+
next unless job && to && ts
|
672
|
+
# thread might just have become inactive (job -> nil)
|
587
673
|
|
588
|
-
|
674
|
+
to = ts + to unless to.is_a?(EoTime)
|
589
675
|
|
590
|
-
|
676
|
+
next if to > EoTime.now
|
591
677
|
|
592
|
-
|
593
|
-
end
|
678
|
+
t.raise(Rufus::Scheduler::TimeoutError)
|
594
679
|
end
|
680
|
+
end
|
681
|
+
|
682
|
+
def rejoin
|
595
683
|
|
596
|
-
|
684
|
+
(@join_queue.num_waiting * 2 + 1).times { @join_queue << @thread }
|
685
|
+
end
|
597
686
|
|
598
|
-
|
599
|
-
'cannot schedule, scheduler is down or shutting down'
|
600
|
-
) if @started_at.nil?
|
687
|
+
def do_schedule(job_type, t, callable, opts, return_job_instance, block)
|
601
688
|
|
602
|
-
|
603
|
-
|
689
|
+
fail NotRunningError.new(
|
690
|
+
'cannot schedule, scheduler is down or shutting down'
|
691
|
+
) if @started_at.nil?
|
604
692
|
|
605
|
-
|
693
|
+
callable, opts = nil, callable if callable.is_a?(Hash)
|
694
|
+
opts = opts.dup unless opts.has_key?(:_t)
|
606
695
|
|
607
|
-
|
608
|
-
case job_type
|
609
|
-
when :once
|
610
|
-
opts[:_t] ||= Rufus::Scheduler.parse(t, opts)
|
611
|
-
opts[:_t].is_a?(Numeric) ? InJob : AtJob
|
612
|
-
when :every
|
613
|
-
EveryJob
|
614
|
-
when :interval
|
615
|
-
IntervalJob
|
616
|
-
when :cron
|
617
|
-
CronJob
|
618
|
-
end
|
696
|
+
return_job_instance ||= opts[:job]
|
619
697
|
|
620
|
-
|
621
|
-
|
698
|
+
job_class =
|
699
|
+
case job_type
|
700
|
+
when :once
|
701
|
+
opts[:_t] ||= Rufus::Scheduler.parse(t, opts)
|
702
|
+
opts[:_t].is_a?(Numeric) ? InJob : AtJob
|
703
|
+
when :every
|
704
|
+
EveryJob
|
705
|
+
when :interval
|
706
|
+
IntervalJob
|
707
|
+
when :cron
|
708
|
+
CronJob
|
709
|
+
end
|
622
710
|
|
623
|
-
|
711
|
+
job = job_class.new(self, t, opts, block || callable)
|
712
|
+
job.check_frequency
|
624
713
|
|
625
|
-
|
626
|
-
|
714
|
+
@jobs.push(job)
|
715
|
+
|
716
|
+
return_job_instance ? job : job.job_id
|
627
717
|
end
|
718
|
+
|
719
|
+
def monow; self.class.monow; end
|
720
|
+
def ltstamp; self.class.ltstamp; end
|
628
721
|
end
|
629
722
|
|