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.
@@ -1,200 +1,216 @@
1
1
 
2
- module Rufus
2
+ class Rufus::Scheduler
3
3
 
4
- class Scheduler
4
+ class << self
5
5
 
6
- class << self
6
+ #--
7
+ # time and string methods
8
+ #++
7
9
 
8
- #--
9
- # time and string methods
10
- #++
10
+ def parse(o, opts={})
11
11
 
12
- def parse(o, opts={})
12
+ opts[:no_error] = true
13
13
 
14
- opts[:no_error] = true
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
- parse_cron(o, opts) ||
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
- def parse_cron(o, opts)
22
+ opts[:no_error] ?
23
+ Fugit.parse_cron(o) :
24
+ Fugit.do_parse_cron(o)
25
+ end
23
26
 
24
- Fugit.parse_cron(o)
25
- end
27
+ def parse_in(o, opts={})
26
28
 
27
- def parse_in(o, opts={})
29
+ #o.is_a?(String) ? parse_duration(o, opts) : o
28
30
 
29
- #o.is_a?(String) ? parse_duration(o, opts) : o
31
+ return parse_duration(o, opts) if o.is_a?(String)
32
+ return o if o.is_a?(Numeric)
30
33
 
31
- return parse_duration(o, opts) if o.is_a?(String)
32
- return o if o.is_a?(Numeric)
34
+ fail ArgumentError.new("couldn't parse time point in #{o.inspect}")
33
35
 
34
- fail ArgumentError.new("couldn't parse time point in #{o.inspect}")
36
+ rescue ArgumentError => ae
35
37
 
36
- rescue ArgumentError => ae
38
+ return nil if opts[:no_error]
39
+ fail ae
40
+ end
37
41
 
38
- return nil if opts[:no_error]
39
- fail ae
40
- end
42
+ def parse_at(o, opts={})
41
43
 
42
- def parse_at(o, opts={})
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
- return o if o.is_a?(EoTime)
45
- return EoTime.make(o) if o.is_a?(Time)
46
- EoTime.parse(o, opts)
48
+ rescue StandardError => se
47
49
 
48
- rescue StandardError => se
50
+ return nil if opts[:no_error]
51
+ fail se
52
+ end
49
53
 
50
- return nil if opts[:no_error]
51
- fail se
52
- end
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
- # 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
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
- # 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={})
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
- #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
131
+ to_fugit_duration(seconds, options).to_rufus_s
132
+ end
130
133
 
131
- to_fugit_duration(seconds, options).to_rufus_s
132
- end
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
- # 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={})
154
+ to_fugit_duration(seconds, options).to_rufus_h
155
+ end
153
156
 
154
- to_fugit_duration(seconds, options).to_rufus_h
155
- end
157
+ # Used by both .to_duration and .to_duration_hash
158
+ #
159
+ def to_fugit_duration(seconds, options={})
156
160
 
157
- # Used by both .to_duration and .to_duration_hash
158
- #
159
- def to_fugit_duration(seconds, options={})
161
+ d = Fugit::Duration
162
+ .parse(seconds, options)
163
+ .deflate
160
164
 
161
- d = Fugit::Duration
162
- .parse(seconds, options)
163
- .deflate
165
+ d = d.drop_seconds if options[:drop_seconds]
166
+ d = d.deflate(:month => options[:months]) if options[:months]
164
167
 
165
- d = d.drop_seconds if options[:drop_seconds]
166
- d = d.deflate(:month => options[:months]) if options[:months]
168
+ d
169
+ end
167
170
 
168
- d
169
- end
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
- # Debugging tools...
193
- #
194
- class D
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
 
@@ -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 = 'http://github.com/jmettraux/rufus-scheduler'
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.6.0
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: 2019-04-22 00:00:00.000000000 Z
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/jobs.rb
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: http://github.com/jmettraux/rufus-scheduler
83
+ homepage: https://github.com/jmettraux/rufus-scheduler
82
84
  licenses:
83
85
  - MIT
84
86
  metadata:
85
- changelog_uri: http://github.com/jmettraux/rufus-scheduler/blob/master/CHANGELOG.md
86
- bug_tracker_uri: http://github.com/jmettraux/rufus-scheduler/issues
87
- homepage_uri: http://github.com/jmettraux/rufus-scheduler
88
- source_code_uri: http://github.com/jmettraux/rufus-scheduler
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
- rubyforge_project: rufus
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)
@@ -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
-