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.
- 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
data/lib/rufus/scheduler/util.rb
CHANGED
@@ -1,200 +1,216 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
class Rufus::Scheduler
|
3
3
|
|
4
|
-
class
|
4
|
+
class << self
|
5
5
|
|
6
|
-
|
6
|
+
#--
|
7
|
+
# time and string methods
|
8
|
+
#++
|
7
9
|
|
8
|
-
|
9
|
-
# time and string methods
|
10
|
-
#++
|
10
|
+
def parse(o, opts={})
|
11
11
|
|
12
|
-
|
12
|
+
opts[:no_error] = true
|
13
13
|
|
14
|
-
|
14
|
+
parse_cron(o, opts) ||
|
15
|
+
parse_in(o, opts) || # covers 'every' schedule strings
|
16
|
+
parse_at(o, opts) ||
|
17
|
+
fail(ArgumentError.new("couldn't parse #{o.inspect} (#{o.class})"))
|
18
|
+
end
|
15
19
|
|
16
|
-
|
17
|
-
parse_in(o, opts) || # covers 'every' schedule strings
|
18
|
-
parse_at(o, opts) ||
|
19
|
-
fail(ArgumentError.new("couldn't parse #{o.inspect} (#{o.class})"))
|
20
|
-
end
|
20
|
+
def parse_cron(o, opts={})
|
21
21
|
|
22
|
-
|
22
|
+
opts[:no_error] ?
|
23
|
+
Fugit.parse_cron(o) :
|
24
|
+
Fugit.do_parse_cron(o)
|
25
|
+
end
|
23
26
|
|
24
|
-
|
25
|
-
end
|
27
|
+
def parse_in(o, opts={})
|
26
28
|
|
27
|
-
|
29
|
+
#o.is_a?(String) ? parse_duration(o, opts) : o
|
28
30
|
|
29
|
-
|
31
|
+
return parse_duration(o, opts) if o.is_a?(String)
|
32
|
+
return o if o.is_a?(Numeric)
|
30
33
|
|
31
|
-
|
32
|
-
return o if o.is_a?(Numeric)
|
34
|
+
fail ArgumentError.new("couldn't parse time point in #{o.inspect}")
|
33
35
|
|
34
|
-
|
36
|
+
rescue ArgumentError => ae
|
35
37
|
|
36
|
-
|
38
|
+
return nil if opts[:no_error]
|
39
|
+
fail ae
|
40
|
+
end
|
37
41
|
|
38
|
-
|
39
|
-
fail ae
|
40
|
-
end
|
42
|
+
def parse_at(o, opts={})
|
41
43
|
|
42
|
-
|
44
|
+
return o if o.is_a?(EoTime)
|
45
|
+
return EoTime.make(o) if o.is_a?(Time)
|
46
|
+
EoTime.parse(o, opts)
|
43
47
|
|
44
|
-
|
45
|
-
return EoTime.make(o) if o.is_a?(Time)
|
46
|
-
EoTime.parse(o, opts)
|
48
|
+
rescue StandardError => se
|
47
49
|
|
48
|
-
|
50
|
+
return nil if opts[:no_error]
|
51
|
+
fail se
|
52
|
+
end
|
49
53
|
|
50
|
-
|
51
|
-
|
52
|
-
|
54
|
+
# Turns a string like '1m10s' into a float like '70.0', more formally,
|
55
|
+
# turns a time duration expressed as a string into a Float instance
|
56
|
+
# (millisecond count).
|
57
|
+
#
|
58
|
+
# w -> week
|
59
|
+
# d -> day
|
60
|
+
# h -> hour
|
61
|
+
# m -> minute
|
62
|
+
# s -> second
|
63
|
+
# M -> month
|
64
|
+
# y -> year
|
65
|
+
# 'nada' -> millisecond
|
66
|
+
#
|
67
|
+
# Some examples:
|
68
|
+
#
|
69
|
+
# Rufus::Scheduler.parse_duration "0.5" # => 0.5
|
70
|
+
# Rufus::Scheduler.parse_duration "500" # => 0.5
|
71
|
+
# Rufus::Scheduler.parse_duration "1000" # => 1.0
|
72
|
+
# Rufus::Scheduler.parse_duration "1h" # => 3600.0
|
73
|
+
# Rufus::Scheduler.parse_duration "1h10s" # => 3610.0
|
74
|
+
# Rufus::Scheduler.parse_duration "1w2d" # => 777600.0
|
75
|
+
#
|
76
|
+
# Negative time strings are OK (Thanks Danny Fullerton):
|
77
|
+
#
|
78
|
+
# Rufus::Scheduler.parse_duration "-0.5" # => -0.5
|
79
|
+
# Rufus::Scheduler.parse_duration "-1h" # => -3600.0
|
80
|
+
#
|
81
|
+
def parse_duration(str, opts={})
|
82
|
+
|
83
|
+
d =
|
84
|
+
opts[:no_error] ?
|
85
|
+
Fugit::Duration.parse(str, opts) :
|
86
|
+
Fugit::Duration.do_parse(str, opts)
|
87
|
+
d ?
|
88
|
+
d.to_sec :
|
89
|
+
nil
|
90
|
+
end
|
53
91
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
d ?
|
88
|
-
d.to_sec :
|
89
|
-
nil
|
90
|
-
end
|
92
|
+
# Turns a number of seconds into a a time string
|
93
|
+
#
|
94
|
+
# Rufus.to_duration 0 # => '0s'
|
95
|
+
# Rufus.to_duration 60 # => '1m'
|
96
|
+
# Rufus.to_duration 3661 # => '1h1m1s'
|
97
|
+
# Rufus.to_duration 7 * 24 * 3600 # => '1w'
|
98
|
+
# Rufus.to_duration 30 * 24 * 3600 + 1 # => "4w2d1s"
|
99
|
+
#
|
100
|
+
# It goes from seconds to the year. Months are not counted (as they
|
101
|
+
# are of variable length). Weeks are counted.
|
102
|
+
#
|
103
|
+
# For 30 days months to be counted, the second parameter of this
|
104
|
+
# method can be set to true.
|
105
|
+
#
|
106
|
+
# Rufus.to_duration 30 * 24 * 3600 + 1, true # => "1M1s"
|
107
|
+
#
|
108
|
+
# If a Float value is passed, milliseconds will be displayed without
|
109
|
+
# 'marker'
|
110
|
+
#
|
111
|
+
# Rufus.to_duration 0.051 # => "51"
|
112
|
+
# Rufus.to_duration 7.051 # => "7s51"
|
113
|
+
# Rufus.to_duration 0.120 + 30 * 24 * 3600 + 1 # => "4w2d1s120"
|
114
|
+
#
|
115
|
+
# (this behaviour mirrors the one found for parse_time_string()).
|
116
|
+
#
|
117
|
+
# Options are :
|
118
|
+
#
|
119
|
+
# * :months, if set to true, months (M) of 30 days will be taken into
|
120
|
+
# account when building up the result
|
121
|
+
# * :drop_seconds, if set to true, seconds and milliseconds will be
|
122
|
+
# trimmed from the result
|
123
|
+
#
|
124
|
+
def to_duration(seconds, options={})
|
91
125
|
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# Rufus.to_duration 3661 # => '1h1m1s'
|
97
|
-
# Rufus.to_duration 7 * 24 * 3600 # => '1w'
|
98
|
-
# Rufus.to_duration 30 * 24 * 3600 + 1 # => "4w2d1s"
|
99
|
-
#
|
100
|
-
# It goes from seconds to the year. Months are not counted (as they
|
101
|
-
# are of variable length). Weeks are counted.
|
102
|
-
#
|
103
|
-
# For 30 days months to be counted, the second parameter of this
|
104
|
-
# method can be set to true.
|
105
|
-
#
|
106
|
-
# Rufus.to_duration 30 * 24 * 3600 + 1, true # => "1M1s"
|
107
|
-
#
|
108
|
-
# If a Float value is passed, milliseconds will be displayed without
|
109
|
-
# 'marker'
|
110
|
-
#
|
111
|
-
# Rufus.to_duration 0.051 # => "51"
|
112
|
-
# Rufus.to_duration 7.051 # => "7s51"
|
113
|
-
# Rufus.to_duration 0.120 + 30 * 24 * 3600 + 1 # => "4w2d1s120"
|
114
|
-
#
|
115
|
-
# (this behaviour mirrors the one found for parse_time_string()).
|
116
|
-
#
|
117
|
-
# Options are :
|
118
|
-
#
|
119
|
-
# * :months, if set to true, months (M) of 30 days will be taken into
|
120
|
-
# account when building up the result
|
121
|
-
# * :drop_seconds, if set to true, seconds and milliseconds will be
|
122
|
-
# trimmed from the result
|
123
|
-
#
|
124
|
-
def to_duration(seconds, options={})
|
126
|
+
#d = Fugit::Duration.parse(seconds, options).deflate
|
127
|
+
#d = d.drop_seconds if options[:drop_seconds]
|
128
|
+
#d = d.deflate(:month => options[:months]) if options[:months]
|
129
|
+
#d.to_rufus_s
|
125
130
|
|
126
|
-
|
127
|
-
|
128
|
-
#d = d.deflate(:month => options[:months]) if options[:months]
|
129
|
-
#d.to_rufus_s
|
131
|
+
to_fugit_duration(seconds, options).to_rufus_s
|
132
|
+
end
|
130
133
|
|
131
|
-
|
132
|
-
|
134
|
+
# Turns a number of seconds (integer or Float) into a hash like in :
|
135
|
+
#
|
136
|
+
# Rufus.to_duration_hash 0.051
|
137
|
+
# # => { :s => 0.051 }
|
138
|
+
# Rufus.to_duration_hash 7.051
|
139
|
+
# # => { :s => 7.051 }
|
140
|
+
# Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
|
141
|
+
# # => { :w => 4, :d => 2, :s => 1.120 }
|
142
|
+
#
|
143
|
+
# This method is used by to_duration behind the scenes.
|
144
|
+
#
|
145
|
+
# Options are :
|
146
|
+
#
|
147
|
+
# * :months, if set to true, months (M) of 30 days will be taken into
|
148
|
+
# account when building up the result
|
149
|
+
# * :drop_seconds, if set to true, seconds and milliseconds will be
|
150
|
+
# trimmed from the result
|
151
|
+
#
|
152
|
+
def to_duration_hash(seconds, options={})
|
133
153
|
|
134
|
-
|
135
|
-
|
136
|
-
# Rufus.to_duration_hash 0.051
|
137
|
-
# # => { :s => 0.051 }
|
138
|
-
# Rufus.to_duration_hash 7.051
|
139
|
-
# # => { :s => 7.051 }
|
140
|
-
# Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
|
141
|
-
# # => { :w => 4, :d => 2, :s => 1.120 }
|
142
|
-
#
|
143
|
-
# This method is used by to_duration behind the scenes.
|
144
|
-
#
|
145
|
-
# Options are :
|
146
|
-
#
|
147
|
-
# * :months, if set to true, months (M) of 30 days will be taken into
|
148
|
-
# account when building up the result
|
149
|
-
# * :drop_seconds, if set to true, seconds and milliseconds will be
|
150
|
-
# trimmed from the result
|
151
|
-
#
|
152
|
-
def to_duration_hash(seconds, options={})
|
154
|
+
to_fugit_duration(seconds, options).to_rufus_h
|
155
|
+
end
|
153
156
|
|
154
|
-
|
155
|
-
|
157
|
+
# Used by both .to_duration and .to_duration_hash
|
158
|
+
#
|
159
|
+
def to_fugit_duration(seconds, options={})
|
156
160
|
|
157
|
-
|
158
|
-
|
159
|
-
|
161
|
+
d = Fugit::Duration
|
162
|
+
.parse(seconds, options)
|
163
|
+
.deflate
|
160
164
|
|
161
|
-
|
162
|
-
|
163
|
-
.deflate
|
165
|
+
d = d.drop_seconds if options[:drop_seconds]
|
166
|
+
d = d.deflate(:month => options[:months]) if options[:months]
|
164
167
|
|
165
|
-
|
166
|
-
|
168
|
+
d
|
169
|
+
end
|
167
170
|
|
168
|
-
|
169
|
-
|
171
|
+
#--
|
172
|
+
# misc
|
173
|
+
#++
|
170
174
|
|
171
|
-
|
172
|
-
# misc
|
173
|
-
#++
|
175
|
+
if RUBY_VERSION > '1.9.9'
|
174
176
|
|
175
177
|
# Produces the UTC string representation of a Time instance
|
176
178
|
#
|
177
179
|
# like "2009/11/23 11:11:50.947109 UTC"
|
178
180
|
#
|
179
181
|
def utc_to_s(t=Time.now)
|
180
|
-
|
181
|
-
"#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
|
182
|
+
"#{t.dup.utc.strftime('%F %T.%6N')} UTC"
|
182
183
|
end
|
183
184
|
|
184
185
|
# Produces a hour/min/sec/milli string representation of Time instance
|
185
186
|
#
|
186
187
|
def h_to_s(t=Time.now)
|
188
|
+
t.strftime('%T.%6N')
|
189
|
+
end
|
190
|
+
else
|
187
191
|
|
192
|
+
def utc_to_s(t=Time.now)
|
193
|
+
"#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
|
194
|
+
end
|
195
|
+
def h_to_s(t=Time.now)
|
188
196
|
"#{t.strftime('%H:%M:%S')}.#{sprintf('%06d', t.usec)}"
|
189
197
|
end
|
190
198
|
end
|
191
199
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
def self.h_to_s(t=Time.now); Rufus::Scheduler.h_to_s(t); end
|
200
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
201
|
+
def monow; Process.clock_gettime(Process::CLOCK_MONOTONIC); end
|
202
|
+
else
|
203
|
+
def monow; Time.now.to_f; end
|
197
204
|
end
|
205
|
+
|
206
|
+
def ltstamp; Time.now.strftime('%FT%T.%3N'); end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Debugging tools...
|
210
|
+
#
|
211
|
+
class D
|
212
|
+
|
213
|
+
def self.h_to_s(t=Time.now); Rufus::Scheduler.h_to_s(t); end
|
198
214
|
end
|
199
215
|
end
|
200
216
|
|
data/rufus-scheduler.gemspec
CHANGED
@@ -10,8 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.authors = [ 'John Mettraux' ]
|
12
12
|
s.email = [ 'jmettraux@gmail.com' ]
|
13
|
-
s.homepage = '
|
14
|
-
s.rubyforge_project = 'rufus'
|
13
|
+
s.homepage = 'https://github.com/jmettraux/rufus-scheduler'
|
15
14
|
s.license = 'MIT'
|
16
15
|
s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
|
17
16
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rufus-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fugit
|
@@ -74,18 +74,20 @@ files:
|
|
74
74
|
- lib/rufus-scheduler.rb
|
75
75
|
- lib/rufus/scheduler.rb
|
76
76
|
- lib/rufus/scheduler/job_array.rb
|
77
|
-
- lib/rufus/scheduler/
|
77
|
+
- lib/rufus/scheduler/jobs_core.rb
|
78
|
+
- lib/rufus/scheduler/jobs_one_time.rb
|
79
|
+
- lib/rufus/scheduler/jobs_repeat.rb
|
78
80
|
- lib/rufus/scheduler/locks.rb
|
79
81
|
- lib/rufus/scheduler/util.rb
|
80
82
|
- rufus-scheduler.gemspec
|
81
|
-
homepage:
|
83
|
+
homepage: https://github.com/jmettraux/rufus-scheduler
|
82
84
|
licenses:
|
83
85
|
- MIT
|
84
86
|
metadata:
|
85
|
-
changelog_uri:
|
86
|
-
bug_tracker_uri:
|
87
|
-
homepage_uri:
|
88
|
-
source_code_uri:
|
87
|
+
changelog_uri: https://github.com/jmettraux/rufus-scheduler/blob/master/CHANGELOG.md
|
88
|
+
bug_tracker_uri: https://github.com/jmettraux/rufus-scheduler/issues
|
89
|
+
homepage_uri: https://github.com/jmettraux/rufus-scheduler
|
90
|
+
source_code_uri: https://github.com/jmettraux/rufus-scheduler
|
89
91
|
post_install_message:
|
90
92
|
rdoc_options: []
|
91
93
|
require_paths:
|
@@ -101,8 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
103
|
- !ruby/object:Gem::Version
|
102
104
|
version: '0'
|
103
105
|
requirements: []
|
104
|
-
|
105
|
-
rubygems_version: 2.6.14.3
|
106
|
+
rubygems_version: 3.0.3
|
106
107
|
signing_key:
|
107
108
|
specification_version: 4
|
108
109
|
summary: job scheduler for Ruby (at, cron, in and every jobs)
|
data/lib/rufus/scheduler/jobs.rb
DELETED
@@ -1,701 +0,0 @@
|
|
1
|
-
|
2
|
-
module Rufus
|
3
|
-
|
4
|
-
class Scheduler
|
5
|
-
|
6
|
-
#--
|
7
|
-
# job classes
|
8
|
-
#++
|
9
|
-
|
10
|
-
class Job
|
11
|
-
|
12
|
-
#
|
13
|
-
# Used by Job#kill
|
14
|
-
#
|
15
|
-
class KillSignal < StandardError; end
|
16
|
-
|
17
|
-
attr_reader :id
|
18
|
-
attr_reader :opts
|
19
|
-
attr_reader :original
|
20
|
-
attr_reader :scheduled_at
|
21
|
-
attr_reader :last_time
|
22
|
-
attr_reader :unscheduled_at
|
23
|
-
attr_reader :tags
|
24
|
-
attr_reader :count
|
25
|
-
attr_reader :last_work_time
|
26
|
-
attr_reader :mean_work_time
|
27
|
-
|
28
|
-
# next trigger time
|
29
|
-
#
|
30
|
-
attr_accessor :next_time
|
31
|
-
|
32
|
-
# previous "next trigger time"
|
33
|
-
#
|
34
|
-
attr_accessor :previous_time
|
35
|
-
|
36
|
-
# anything with a #call(job[, timet]) method,
|
37
|
-
# what gets actually triggered
|
38
|
-
#
|
39
|
-
attr_reader :callable
|
40
|
-
|
41
|
-
# a reference to the instance whose call method is the @callable
|
42
|
-
#
|
43
|
-
attr_reader :handler
|
44
|
-
|
45
|
-
def initialize(scheduler, original, opts, block)
|
46
|
-
|
47
|
-
@scheduler = scheduler
|
48
|
-
@original = original
|
49
|
-
@opts = opts
|
50
|
-
|
51
|
-
@handler = block
|
52
|
-
|
53
|
-
@callable =
|
54
|
-
if block.respond_to?(:arity)
|
55
|
-
block
|
56
|
-
elsif block.respond_to?(:call)
|
57
|
-
block.method(:call)
|
58
|
-
elsif block.is_a?(Class)
|
59
|
-
@handler = block.new
|
60
|
-
@handler.method(:call) rescue nil
|
61
|
-
else
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
|
65
|
-
@scheduled_at = EoTime.now
|
66
|
-
@unscheduled_at = nil
|
67
|
-
@last_time = nil
|
68
|
-
|
69
|
-
@locals = {}
|
70
|
-
@local_mutex = Mutex.new
|
71
|
-
|
72
|
-
@id = determine_id
|
73
|
-
|
74
|
-
fail(
|
75
|
-
ArgumentError,
|
76
|
-
'missing block or callable to schedule',
|
77
|
-
caller[2..-1]
|
78
|
-
) unless @callable
|
79
|
-
|
80
|
-
@tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
81
|
-
|
82
|
-
@count = 0
|
83
|
-
@last_work_time = 0.0
|
84
|
-
@mean_work_time = 0.0
|
85
|
-
|
86
|
-
# tidy up options
|
87
|
-
|
88
|
-
if @opts[:allow_overlap] == false || @opts[:allow_overlapping] == false
|
89
|
-
@opts[:overlap] = false
|
90
|
-
end
|
91
|
-
if m = @opts[:mutex]
|
92
|
-
@opts[:mutex] = Array(m)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
alias job_id id
|
97
|
-
|
98
|
-
# Will fail with an ArgumentError if the job frequency is higher than
|
99
|
-
# the scheduler frequency.
|
100
|
-
#
|
101
|
-
def check_frequency
|
102
|
-
|
103
|
-
# this parent implementation never fails
|
104
|
-
end
|
105
|
-
|
106
|
-
def trigger(time)
|
107
|
-
|
108
|
-
@previous_time = @next_time
|
109
|
-
set_next_time(time)
|
110
|
-
|
111
|
-
do_trigger(time)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Trigger the job right now, off of its schedule.
|
115
|
-
#
|
116
|
-
# Done in collaboration with Piavka in
|
117
|
-
# https://github.com/jmettraux/rufus-scheduler/issues/214
|
118
|
-
#
|
119
|
-
def trigger_off_schedule(time=EoTime.now)
|
120
|
-
|
121
|
-
do_trigger(time)
|
122
|
-
end
|
123
|
-
|
124
|
-
def unschedule
|
125
|
-
|
126
|
-
@unscheduled_at = EoTime.now
|
127
|
-
end
|
128
|
-
|
129
|
-
def threads
|
130
|
-
|
131
|
-
Thread.list.select { |t| t[:rufus_scheduler_job] == self }
|
132
|
-
end
|
133
|
-
|
134
|
-
# Kills all the threads this Job currently has going on.
|
135
|
-
#
|
136
|
-
def kill
|
137
|
-
|
138
|
-
threads.each { |t| t.raise(KillSignal) }
|
139
|
-
end
|
140
|
-
|
141
|
-
def running?
|
142
|
-
|
143
|
-
threads.any?
|
144
|
-
end
|
145
|
-
|
146
|
-
def scheduled?
|
147
|
-
|
148
|
-
@scheduler.scheduled?(self)
|
149
|
-
end
|
150
|
-
|
151
|
-
def []=(key, value)
|
152
|
-
|
153
|
-
@local_mutex.synchronize { @locals[key] = value }
|
154
|
-
end
|
155
|
-
|
156
|
-
def [](key)
|
157
|
-
|
158
|
-
@local_mutex.synchronize { @locals[key] }
|
159
|
-
end
|
160
|
-
|
161
|
-
def key?(key)
|
162
|
-
|
163
|
-
@local_mutex.synchronize { @locals.key?(key) }
|
164
|
-
end
|
165
|
-
|
166
|
-
def keys
|
167
|
-
|
168
|
-
@local_mutex.synchronize { @locals.keys }
|
169
|
-
end
|
170
|
-
|
171
|
-
#def hash
|
172
|
-
# self.object_id
|
173
|
-
#end
|
174
|
-
#def eql?(o)
|
175
|
-
# o.class == self.class && o.hash == self.hash
|
176
|
-
#end
|
177
|
-
#
|
178
|
-
# might be necessary at some point
|
179
|
-
|
180
|
-
def next_times(count)
|
181
|
-
|
182
|
-
next_time ? [ next_time ] : []
|
183
|
-
end
|
184
|
-
|
185
|
-
# Calls the callable (usually a block) wrapped in this Job instance.
|
186
|
-
#
|
187
|
-
# Warning: error rescueing is the responsibity of the caller.
|
188
|
-
#
|
189
|
-
def call(do_rescue=false)
|
190
|
-
|
191
|
-
do_call(EoTime.now, do_rescue)
|
192
|
-
end
|
193
|
-
|
194
|
-
protected
|
195
|
-
|
196
|
-
def callback(meth, time)
|
197
|
-
|
198
|
-
return true unless @scheduler.respond_to?(meth)
|
199
|
-
|
200
|
-
arity = @scheduler.method(meth).arity
|
201
|
-
args = [ self, time ][0, (arity < 0 ? 2 : arity)]
|
202
|
-
|
203
|
-
@scheduler.send(meth, *args)
|
204
|
-
end
|
205
|
-
|
206
|
-
def compute_timeout
|
207
|
-
|
208
|
-
if to = @opts[:timeout]
|
209
|
-
Rufus::Scheduler.parse(to)
|
210
|
-
else
|
211
|
-
nil
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def mutex(m)
|
216
|
-
|
217
|
-
m.is_a?(Mutex) ? m : (@scheduler.mutexes[m.to_s] ||= Mutex.new)
|
218
|
-
end
|
219
|
-
|
220
|
-
def do_call(time, do_rescue)
|
221
|
-
|
222
|
-
args = [ self, time ][0, @callable.arity]
|
223
|
-
@callable.call(*args)
|
224
|
-
|
225
|
-
rescue StandardError => se
|
226
|
-
|
227
|
-
fail se unless do_rescue
|
228
|
-
|
229
|
-
return if se.is_a?(KillSignal) # discard
|
230
|
-
|
231
|
-
@scheduler.on_error(self, se)
|
232
|
-
|
233
|
-
# exceptions above StandardError do pass through
|
234
|
-
end
|
235
|
-
|
236
|
-
def do_trigger(time)
|
237
|
-
|
238
|
-
return if (
|
239
|
-
opts[:overlap] == false &&
|
240
|
-
running?
|
241
|
-
)
|
242
|
-
return if (
|
243
|
-
callback(:confirm_lock, time) &&
|
244
|
-
callback(:on_pre_trigger, time)
|
245
|
-
) == false
|
246
|
-
|
247
|
-
@count += 1
|
248
|
-
|
249
|
-
if opts[:blocking]
|
250
|
-
trigger_now(time)
|
251
|
-
else
|
252
|
-
trigger_queue(time)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
def trigger_now(time)
|
257
|
-
|
258
|
-
t = EoTime.now
|
259
|
-
# if there are mutexes, t might be really bigger than time
|
260
|
-
|
261
|
-
Thread.current[:rufus_scheduler_job] = self
|
262
|
-
Thread.current[:rufus_scheduler_time] = t
|
263
|
-
Thread.current[:rufus_scheduler_timeout] = compute_timeout
|
264
|
-
|
265
|
-
@last_time = t
|
266
|
-
|
267
|
-
do_call(time, true)
|
268
|
-
|
269
|
-
ensure
|
270
|
-
|
271
|
-
@last_work_time =
|
272
|
-
EoTime.now - Thread.current[:rufus_scheduler_time]
|
273
|
-
@mean_work_time =
|
274
|
-
((@count - 1) * @mean_work_time + @last_work_time) / @count
|
275
|
-
|
276
|
-
post_trigger(time)
|
277
|
-
|
278
|
-
Thread.current[:rufus_scheduler_job] = nil
|
279
|
-
Thread.current[:rufus_scheduler_time] = nil
|
280
|
-
Thread.current[:rufus_scheduler_timeout] = nil
|
281
|
-
end
|
282
|
-
|
283
|
-
def post_trigger(time)
|
284
|
-
|
285
|
-
set_next_time(time, true)
|
286
|
-
|
287
|
-
callback(:on_post_trigger, time)
|
288
|
-
end
|
289
|
-
|
290
|
-
def start_work_thread
|
291
|
-
|
292
|
-
thread =
|
293
|
-
Thread.new do
|
294
|
-
|
295
|
-
Thread.current[:rufus_scheduler_job] = true
|
296
|
-
# indicates that the thread is going to be assigned immediately
|
297
|
-
|
298
|
-
Thread.current[@scheduler.thread_key] = true
|
299
|
-
Thread.current[:rufus_scheduler_work_thread] = true
|
300
|
-
|
301
|
-
loop do
|
302
|
-
|
303
|
-
job, time = @scheduler.work_queue.pop
|
304
|
-
|
305
|
-
break if @scheduler.started_at == nil
|
306
|
-
|
307
|
-
next if job.unscheduled_at
|
308
|
-
|
309
|
-
begin
|
310
|
-
|
311
|
-
(job.opts[:mutex] || []).reduce(
|
312
|
-
lambda { job.trigger_now(time) }
|
313
|
-
) do |b, m|
|
314
|
-
lambda { mutex(m).synchronize { b.call } }
|
315
|
-
end.call
|
316
|
-
|
317
|
-
rescue KillSignal
|
318
|
-
|
319
|
-
# simply go on looping
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
thread[@scheduler.thread_key] = true
|
325
|
-
thread[:rufus_scheduler_work_thread] = true
|
326
|
-
#
|
327
|
-
# same as above (in the thead block),
|
328
|
-
# but since it has to be done as quickly as possible.
|
329
|
-
# So, whoever is running first (scheduler thread vs job thread)
|
330
|
-
# sets this information
|
331
|
-
|
332
|
-
thread
|
333
|
-
end
|
334
|
-
|
335
|
-
def trigger_queue(time)
|
336
|
-
|
337
|
-
threads = @scheduler.work_threads
|
338
|
-
|
339
|
-
vac = threads.select { |t| t[:rufus_scheduler_job] == nil }.size
|
340
|
-
que = @scheduler.work_queue.size
|
341
|
-
|
342
|
-
cur = threads.size
|
343
|
-
max = @scheduler.max_work_threads
|
344
|
-
|
345
|
-
start_work_thread if vac - que < 1 && cur < max
|
346
|
-
|
347
|
-
@scheduler.work_queue << [ self, time ]
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
class OneTimeJob < Job
|
352
|
-
|
353
|
-
alias time next_time
|
354
|
-
|
355
|
-
def occurrences(time0, time1)
|
356
|
-
|
357
|
-
(time >= time0 && time <= time1) ? [ time ] : []
|
358
|
-
end
|
359
|
-
|
360
|
-
protected
|
361
|
-
|
362
|
-
def determine_id
|
363
|
-
|
364
|
-
[
|
365
|
-
self.class.name.split(':').last.downcase[0..-4],
|
366
|
-
@scheduled_at.to_f,
|
367
|
-
@next_time.to_f,
|
368
|
-
(self.object_id < 0 ? 'm' : '') + self.object_id.to_s
|
369
|
-
].map(&:to_s).join('_')
|
370
|
-
end
|
371
|
-
|
372
|
-
# There is no next_time for one time jobs, hence the false.
|
373
|
-
#
|
374
|
-
def set_next_time(trigger_time, is_post=false)
|
375
|
-
|
376
|
-
@next_time = is_post ? nil : false
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
class AtJob < OneTimeJob
|
381
|
-
|
382
|
-
def initialize(scheduler, time, opts, block)
|
383
|
-
|
384
|
-
super(scheduler, time, opts, block)
|
385
|
-
|
386
|
-
@next_time =
|
387
|
-
opts[:_t] || Rufus::Scheduler.parse_at(time, opts)
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
class InJob < OneTimeJob
|
392
|
-
|
393
|
-
def initialize(scheduler, duration, opts, block)
|
394
|
-
|
395
|
-
super(scheduler, duration, opts, block)
|
396
|
-
|
397
|
-
@next_time =
|
398
|
-
@scheduled_at +
|
399
|
-
opts[:_t] || Rufus::Scheduler.parse_in(duration, opts)
|
400
|
-
end
|
401
|
-
end
|
402
|
-
|
403
|
-
class RepeatJob < Job
|
404
|
-
|
405
|
-
attr_reader :paused_at
|
406
|
-
|
407
|
-
attr_reader :first_at
|
408
|
-
attr_reader :last_at
|
409
|
-
attr_accessor :times
|
410
|
-
|
411
|
-
def initialize(scheduler, duration, opts, block)
|
412
|
-
|
413
|
-
super
|
414
|
-
|
415
|
-
@paused_at = nil
|
416
|
-
|
417
|
-
@times = opts[:times]
|
418
|
-
|
419
|
-
fail ArgumentError.new(
|
420
|
-
"cannot accept :times => #{@times.inspect}, not nil or an int"
|
421
|
-
) unless @times == nil || @times.is_a?(Integer)
|
422
|
-
|
423
|
-
self.first_at =
|
424
|
-
opts[:first] || opts[:first_time] ||
|
425
|
-
opts[:first_at] || opts[:first_in] ||
|
426
|
-
nil
|
427
|
-
self.last_at =
|
428
|
-
opts[:last] || opts[:last_at] || opts[:last_in]
|
429
|
-
end
|
430
|
-
|
431
|
-
def first_at=(first)
|
432
|
-
|
433
|
-
return (@first_at = nil) if first == nil
|
434
|
-
|
435
|
-
n0 = EoTime.now
|
436
|
-
n1 = n0 + 0.003
|
437
|
-
|
438
|
-
first = n0 if first == :now || first == :immediately || first == 0
|
439
|
-
fdur = Rufus::Scheduler.parse_duration(first, no_error: true)
|
440
|
-
|
441
|
-
@first_at = (fdur && (EoTime.now + fdur)) || EoTime.make(first)
|
442
|
-
@first_at = n1 if @first_at >= n0 && @first_at < n1
|
443
|
-
|
444
|
-
fail ArgumentError.new(
|
445
|
-
"cannot set first[_at|_in] in the past: " +
|
446
|
-
"#{first.inspect} -> #{@first_at.inspect}"
|
447
|
-
) if @first_at < n0
|
448
|
-
|
449
|
-
@first_at
|
450
|
-
end
|
451
|
-
|
452
|
-
def last_at=(last)
|
453
|
-
|
454
|
-
@last_at =
|
455
|
-
if last
|
456
|
-
ldur = Rufus::Scheduler.parse_duration(last, no_error: true)
|
457
|
-
(ldur && (EoTime.now + ldur)) || EoTime.make(last)
|
458
|
-
else
|
459
|
-
nil
|
460
|
-
end
|
461
|
-
|
462
|
-
fail ArgumentError.new(
|
463
|
-
"cannot set last[_at|_in] in the past: " +
|
464
|
-
"#{last.inspect} -> #{@last_at.inspect}"
|
465
|
-
) if last && @last_at < EoTime.now
|
466
|
-
|
467
|
-
@last_at
|
468
|
-
end
|
469
|
-
|
470
|
-
def trigger(time)
|
471
|
-
|
472
|
-
return if @paused_at
|
473
|
-
|
474
|
-
return (@next_time = nil) if @times && @times < 1
|
475
|
-
return (@next_time = nil) if @last_at && time >= @last_at
|
476
|
-
#
|
477
|
-
# It keeps jobs one step too much in @jobs, but it's OK
|
478
|
-
|
479
|
-
super
|
480
|
-
|
481
|
-
@times -= 1 if @times
|
482
|
-
end
|
483
|
-
|
484
|
-
def pause
|
485
|
-
|
486
|
-
@paused_at = EoTime.now
|
487
|
-
end
|
488
|
-
|
489
|
-
def resume
|
490
|
-
|
491
|
-
@paused_at = nil
|
492
|
-
end
|
493
|
-
|
494
|
-
def paused?
|
495
|
-
|
496
|
-
@paused_at != nil
|
497
|
-
end
|
498
|
-
|
499
|
-
def determine_id
|
500
|
-
|
501
|
-
[
|
502
|
-
self.class.name.split(':').last.downcase[0..-4],
|
503
|
-
@scheduled_at.to_f,
|
504
|
-
(self.object_id < 0 ? 'm' : '') + self.object_id.to_s
|
505
|
-
].map(&:to_s).join('_')
|
506
|
-
end
|
507
|
-
|
508
|
-
def occurrences(time0, time1)
|
509
|
-
|
510
|
-
a = []
|
511
|
-
|
512
|
-
nt = @next_time
|
513
|
-
ts = @times
|
514
|
-
|
515
|
-
loop do
|
516
|
-
|
517
|
-
break if nt > time1
|
518
|
-
break if ts && ts <= 0
|
519
|
-
|
520
|
-
a << nt if nt >= time0
|
521
|
-
|
522
|
-
nt = next_time_from(nt)
|
523
|
-
ts = ts - 1 if ts
|
524
|
-
end
|
525
|
-
|
526
|
-
a
|
527
|
-
end
|
528
|
-
|
529
|
-
# Starting from now, returns the {count} next occurences
|
530
|
-
# (EtOrbi::EoTime instances) for this job.
|
531
|
-
#
|
532
|
-
# Warning, for IntervalJob, the @mean_work_time is used since
|
533
|
-
# "interval" works from the end of a job to its next trigger
|
534
|
-
# (not from one trigger to the next, as for "cron" and "every").
|
535
|
-
#
|
536
|
-
def next_times(count)
|
537
|
-
|
538
|
-
(count - 1).times.inject([ next_time ]) { |a|
|
539
|
-
a << next_time_from(a.last)
|
540
|
-
a }
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
#
|
545
|
-
# A parent class of EveryJob and IntervalJob
|
546
|
-
#
|
547
|
-
class EvInJob < RepeatJob
|
548
|
-
|
549
|
-
def first_at=(first)
|
550
|
-
|
551
|
-
@next_time = super
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
class EveryJob < EvInJob
|
556
|
-
|
557
|
-
attr_reader :frequency
|
558
|
-
|
559
|
-
def initialize(scheduler, duration, opts, block)
|
560
|
-
|
561
|
-
super(scheduler, duration, opts, block)
|
562
|
-
|
563
|
-
@frequency = Rufus::Scheduler.parse_in(@original)
|
564
|
-
|
565
|
-
fail ArgumentError.new(
|
566
|
-
"cannot schedule #{self.class} with a frequency " +
|
567
|
-
"of #{@frequency.inspect} (#{@original.inspect})"
|
568
|
-
) if @frequency <= 0
|
569
|
-
|
570
|
-
set_next_time(nil)
|
571
|
-
end
|
572
|
-
|
573
|
-
def check_frequency
|
574
|
-
|
575
|
-
fail ArgumentError.new(
|
576
|
-
"job frequency (#{@frequency}s) is higher than " +
|
577
|
-
"scheduler frequency (#{@scheduler.frequency}s)"
|
578
|
-
) if @frequency < @scheduler.frequency
|
579
|
-
end
|
580
|
-
|
581
|
-
def next_time_from(time)
|
582
|
-
|
583
|
-
time + @frequency
|
584
|
-
end
|
585
|
-
|
586
|
-
protected
|
587
|
-
|
588
|
-
def set_next_time(trigger_time, is_post=false)
|
589
|
-
|
590
|
-
return if is_post
|
591
|
-
|
592
|
-
n = EoTime.now
|
593
|
-
|
594
|
-
@next_time =
|
595
|
-
if @first_at && (trigger_time == nil || @first_at > n)
|
596
|
-
@first_at
|
597
|
-
else
|
598
|
-
(@next_time || n) + @frequency
|
599
|
-
end
|
600
|
-
end
|
601
|
-
end
|
602
|
-
|
603
|
-
class IntervalJob < EvInJob
|
604
|
-
|
605
|
-
attr_reader :interval
|
606
|
-
|
607
|
-
def initialize(scheduler, interval, opts, block)
|
608
|
-
|
609
|
-
super(scheduler, interval, opts, block)
|
610
|
-
|
611
|
-
@interval = Rufus::Scheduler.parse_in(@original)
|
612
|
-
|
613
|
-
fail ArgumentError.new(
|
614
|
-
"cannot schedule #{self.class} with an interval " +
|
615
|
-
"of #{@interval.inspect} (#{@original.inspect})"
|
616
|
-
) if @interval <= 0
|
617
|
-
|
618
|
-
set_next_time(nil)
|
619
|
-
end
|
620
|
-
|
621
|
-
def next_time_from(time)
|
622
|
-
|
623
|
-
time + @mean_work_time + @interval
|
624
|
-
end
|
625
|
-
|
626
|
-
protected
|
627
|
-
|
628
|
-
def set_next_time(trigger_time, is_post=false)
|
629
|
-
|
630
|
-
@next_time =
|
631
|
-
if is_post
|
632
|
-
EoTime.now + @interval
|
633
|
-
elsif trigger_time.nil?
|
634
|
-
if @first_at == nil || @first_at < Time.now
|
635
|
-
EoTime.now + @interval
|
636
|
-
else
|
637
|
-
@first_at
|
638
|
-
end
|
639
|
-
else
|
640
|
-
false
|
641
|
-
end
|
642
|
-
end
|
643
|
-
end
|
644
|
-
|
645
|
-
class CronJob < RepeatJob
|
646
|
-
|
647
|
-
attr_reader :cron_line
|
648
|
-
|
649
|
-
def initialize(scheduler, cronline, opts, block)
|
650
|
-
|
651
|
-
super(scheduler, cronline, opts, block)
|
652
|
-
|
653
|
-
@cron_line = opts[:_t] || ::Fugit::Cron.do_parse(cronline)
|
654
|
-
|
655
|
-
set_next_time(nil)
|
656
|
-
end
|
657
|
-
|
658
|
-
def check_frequency
|
659
|
-
|
660
|
-
return if @scheduler.frequency <= 1
|
661
|
-
#
|
662
|
-
# The minimum time delta in a cron job is 1 second, so if the
|
663
|
-
# scheduler frequency is less than that, no worries.
|
664
|
-
|
665
|
-
f = @cron_line.rough_frequency
|
666
|
-
|
667
|
-
fail ArgumentError.new(
|
668
|
-
"job frequency (min ~#{f}s) is higher than " +
|
669
|
-
"scheduler frequency (#{@scheduler.frequency}s)"
|
670
|
-
) if f < @scheduler.frequency
|
671
|
-
end
|
672
|
-
|
673
|
-
def brute_frequency
|
674
|
-
|
675
|
-
@cron_line.brute_frequency
|
676
|
-
end
|
677
|
-
|
678
|
-
def rough_frequency
|
679
|
-
|
680
|
-
@cron_line.rough_frequency
|
681
|
-
end
|
682
|
-
|
683
|
-
def next_time_from(time)
|
684
|
-
|
685
|
-
if @first_at == nil || @first_at <= time
|
686
|
-
@cron_line.next_time(time)
|
687
|
-
else
|
688
|
-
@first_at
|
689
|
-
end
|
690
|
-
end
|
691
|
-
|
692
|
-
protected
|
693
|
-
|
694
|
-
def set_next_time(trigger_time, is_post=false)
|
695
|
-
|
696
|
-
@next_time = next_time_from(trigger_time || Time.now)
|
697
|
-
end
|
698
|
-
end
|
699
|
-
end
|
700
|
-
end
|
701
|
-
|