openwferu 0.9.2 → 0.9.3
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.
- data/examples/mano_tracker.rb +165 -0
- data/examples/scheduler_cron_usage.rb +46 -0
- data/examples/scheduler_usage.rb +54 -0
- data/lib/openwfe/contextual.rb +7 -1
- data/lib/openwfe/engine/engine.rb +58 -15
- data/lib/openwfe/expool/expressionpool.rb +116 -14
- data/lib/openwfe/expool/expstorage.rb +12 -12
- data/lib/openwfe/expool/journalexpstorage.rb +1 -1
- data/lib/openwfe/expool/yamlexpstorage.rb +58 -22
- data/lib/openwfe/expressions/environment.rb +32 -2
- data/lib/openwfe/expressions/expressionmap.rb +17 -0
- data/lib/openwfe/expressions/fe_condition.rb +122 -0
- data/lib/openwfe/expressions/fe_cursor.rb +14 -5
- data/lib/openwfe/expressions/fe_participant.rb +55 -4
- data/lib/openwfe/expressions/fe_raw.rb +43 -12
- data/lib/openwfe/expressions/fe_subprocess.rb +10 -0
- data/lib/openwfe/expressions/fe_time.rb +117 -22
- data/lib/openwfe/expressions/fe_value.rb +27 -8
- data/lib/openwfe/expressions/flowexpression.rb +13 -6
- data/lib/openwfe/expressions/raw_prog.rb +13 -11
- data/lib/openwfe/expressions/timeout.rb +94 -0
- data/lib/openwfe/flowexpressionid.rb +17 -19
- data/lib/openwfe/logging.rb +35 -16
- data/lib/openwfe/participants/atomparticipants.rb +31 -7
- data/lib/openwfe/participants/enoparticipant.rb +43 -3
- data/lib/openwfe/participants/participant.rb +21 -1
- data/lib/openwfe/participants/participantmap.rb +4 -2
- data/lib/openwfe/participants/participants.rb +12 -17
- data/lib/openwfe/participants/soapparticipants.rb +15 -3
- data/lib/openwfe/rudefinitions.rb +3 -0
- data/lib/openwfe/service.rb +8 -0
- data/lib/openwfe/storage/yamlfilestorage.rb +85 -47
- data/lib/openwfe/{otime.rb → util/otime.rb} +0 -0
- data/lib/openwfe/util/scheduler.rb +415 -231
- data/lib/openwfe/util/schedulers.rb +11 -3
- data/lib/openwfe/util/stoppable.rb +69 -0
- data/lib/openwfe/utils.rb +14 -25
- data/lib/openwfe/workitem.rb +12 -6
- data/lib/openwfe/worklist/storeparticipant.rb +145 -0
- data/test/{atomtest.rb → atom_test.rb} +0 -0
- data/test/{crontest.rb → cron_test.rb} +7 -6
- data/test/cronline_test.rb +51 -0
- data/test/{dollartest.rb → dollar_test.rb} +0 -0
- data/test/{feitest.rb → fei_test.rb} +0 -0
- data/test/file_persistence_test.rb +15 -9
- data/test/flowtestbase.rb +11 -5
- data/test/ft_0.rb +8 -0
- data/test/ft_10_loop.rb +72 -10
- data/test/ft_11_ppd.rb +49 -0
- data/test/ft_17_condition.rb +83 -0
- data/test/ft_18_pname.rb +59 -0
- data/test/hparticipant_test.rb +96 -0
- data/test/{misctest.rb → misc_test.rb} +1 -1
- data/test/rake_qtest.rb +10 -4
- data/test/rake_test.rb +12 -1
- data/test/raw_prog_test.rb +1 -1
- data/test/restart_cron_test.rb +78 -0
- data/test/restart_test.rb +79 -0
- data/test/scheduler_test.rb +92 -0
- data/test/{timetest.rb → time_test.rb} +3 -38
- data/test/timeout_test.rb +73 -0
- metadata +26 -11
- data/lib/openwfe/worklist/worklists.rb +0 -175
@@ -41,15 +41,27 @@
|
|
41
41
|
|
42
42
|
require 'soap/rpc/driver'
|
43
43
|
|
44
|
-
require 'openwfe/participants/
|
44
|
+
require 'openwfe/participants/participant'
|
45
45
|
|
46
46
|
|
47
47
|
module OpenWFE
|
48
48
|
|
49
49
|
#
|
50
|
-
#
|
50
|
+
# Wrapping a simple web service call within an OpenWFEru participant.
|
51
51
|
#
|
52
|
-
|
52
|
+
# quote_service = OpenWFE::SoapParticipant.new(
|
53
|
+
# "http://services.xmethods.net/soap", # service URI
|
54
|
+
# "urn:xmethods-delayed-quotes", # namespace
|
55
|
+
# "getQuote", # operation name
|
56
|
+
# [ "symbol" ]) # param arrays (workitem fields)
|
57
|
+
#
|
58
|
+
# engine.register_participant("quote_service", quote_service)
|
59
|
+
#
|
60
|
+
# TODO #8425 : use blocks as hooks for the mapping (pre/post)
|
61
|
+
# of workitems into webservice operation params.
|
62
|
+
#
|
63
|
+
class SoapParticipant
|
64
|
+
include LocalParticipant
|
53
65
|
|
54
66
|
def initialize \
|
55
67
|
(endpoint_url, namespace, method_name, params, param_prefix="")
|
data/lib/openwfe/service.rb
CHANGED
@@ -56,6 +56,14 @@ module OpenWFE
|
|
56
56
|
@service_name = service_name
|
57
57
|
@application_context = application_context
|
58
58
|
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Some services (like the scheduler one for example) need to
|
62
|
+
# free some resources upon stopping. This can be achieved by
|
63
|
+
# overwriting this method.
|
64
|
+
#
|
65
|
+
def stop
|
66
|
+
end
|
59
67
|
end
|
60
68
|
|
61
69
|
class Service
|
@@ -40,6 +40,7 @@
|
|
40
40
|
# John Mettraux at openwfe.org
|
41
41
|
#
|
42
42
|
|
43
|
+
require 'find'
|
43
44
|
require 'yaml'
|
44
45
|
require 'fileutils'
|
45
46
|
|
@@ -59,7 +60,8 @@ module OpenWFE
|
|
59
60
|
|
60
61
|
|
61
62
|
#
|
62
|
-
# yaml
|
63
|
+
# Stores OpenWFEru related objects into yaml encoded files.
|
64
|
+
# This storage is meant to look and feel like a Hash.
|
63
65
|
#
|
64
66
|
class YamlFileStorage
|
65
67
|
include ServiceMixin
|
@@ -72,67 +74,95 @@ module OpenWFE
|
|
72
74
|
FileUtils.makedirs @basepath
|
73
75
|
end
|
74
76
|
|
75
|
-
|
77
|
+
#
|
78
|
+
# Stores an object with its FlowExpressionId instance as its key.
|
79
|
+
#
|
80
|
+
def []= (fei, object)
|
76
81
|
|
77
|
-
fei_path = compute_file_path(
|
82
|
+
fei_path = compute_file_path(fei)
|
78
83
|
fei_parent_path = File.dirname(fei_path)
|
79
84
|
|
80
85
|
FileUtils.makedirs(fei_parent_path) \
|
81
|
-
|
86
|
+
if not File.exist?(fei_parent_path)
|
82
87
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
data = YAML.dump(object)
|
87
|
-
|
88
|
-
io.write(data)
|
89
|
-
io.close
|
90
|
-
end
|
88
|
+
fd = IO.sysopen(fei_path , "w+")
|
89
|
+
io = IO.open(fd , "w+")
|
91
90
|
|
92
|
-
|
93
|
-
# deletes the whole storage directory... beware...
|
94
|
-
#
|
95
|
-
def purge
|
96
|
-
FileUtils.remove_dir @basepath
|
97
|
-
end
|
91
|
+
data = YAML.dump(object)
|
98
92
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
File.exist?(compute_file_path(fei))
|
103
|
-
end
|
93
|
+
io.write(data)
|
94
|
+
io.close
|
95
|
+
end
|
104
96
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
97
|
+
#
|
98
|
+
# Deletes the whole storage directory... beware...
|
99
|
+
#
|
100
|
+
def purge
|
101
|
+
FileUtils.remove_dir @basepath
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Checks whether there is an object (expression, workitem) stored
|
106
|
+
# for the given FlowExpressionId instance.
|
107
|
+
#
|
108
|
+
def has_key? (fei)
|
109
|
+
File.exist?(compute_file_path(fei))
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Removes the object (file) stored for the given FlowExpressionId
|
114
|
+
# instance.
|
115
|
+
#
|
116
|
+
def delete (fei)
|
117
|
+
|
118
|
+
fei_path = compute_file_path(fei)
|
119
|
+
|
120
|
+
if File.exist?(fei_path)
|
121
|
+
File.delete(fei_path)
|
122
|
+
else
|
123
|
+
raise "Object not found at #{fei_path}"
|
114
124
|
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Actually loads and returns the object for the given
|
129
|
+
# FlowExpressionId instance.
|
130
|
+
#
|
131
|
+
def [] (fei)
|
132
|
+
|
133
|
+
fei_path = compute_file_path(fei)
|
115
134
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
135
|
+
return nil if not File.exist?(fei_path)
|
136
|
+
|
137
|
+
load_object(fei_path)
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Returns the count of objects currently stored in this instance.
|
142
|
+
#
|
143
|
+
def length
|
144
|
+
return count_objects(0, @basepath)
|
145
|
+
end
|
146
|
+
|
147
|
+
alias :size :length
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
def load_object (path)
|
152
|
+
|
153
|
+
data = IO.read(path)
|
122
154
|
object = YAML.load(data)
|
123
155
|
|
124
|
-
object.application_context = @application_context
|
156
|
+
object.application_context = @application_context \
|
157
|
+
if object.respond_to? :application_context=
|
125
158
|
|
126
159
|
return object
|
127
160
|
end
|
128
161
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
protected
|
134
|
-
|
135
|
-
def count_expression (count, item)
|
162
|
+
def count_objects (count, item)
|
163
|
+
|
164
|
+
# TODO #8346 : use "find" to do that job
|
165
|
+
# measure perf before and after change !
|
136
166
|
|
137
167
|
return count + 1 if OpenWFE::ends_with(item, ".yaml")
|
138
168
|
|
@@ -142,13 +172,21 @@ module OpenWFE
|
|
142
172
|
d.each do |i|
|
143
173
|
next if i == "." or i == ".."
|
144
174
|
i = item + "/" + i
|
145
|
-
count =
|
175
|
+
count = count_objects(count, i)
|
146
176
|
end
|
147
177
|
d.close()
|
148
178
|
end
|
149
179
|
|
150
180
|
return count
|
151
181
|
end
|
182
|
+
|
183
|
+
def each_object_path (&block)
|
184
|
+
return unless block
|
185
|
+
Find.find(@basepath) do |path|
|
186
|
+
block.call path \
|
187
|
+
unless File.stat(path).directory?
|
188
|
+
end
|
189
|
+
end
|
152
190
|
|
153
191
|
def compute_file_path (fei)
|
154
192
|
raise "this should be implemented in a subclass"
|
File without changes
|
@@ -41,14 +41,54 @@
|
|
41
41
|
|
42
42
|
require 'monitor'
|
43
43
|
|
44
|
-
require 'openwfe/
|
45
|
-
require 'openwfe/
|
44
|
+
#require 'openwfe/utils'
|
45
|
+
require 'openwfe/util/otime'
|
46
|
+
require 'openwfe/util/stoppable'
|
46
47
|
|
47
48
|
|
48
49
|
module OpenWFE
|
49
50
|
|
51
|
+
#
|
52
|
+
# The Scheduler is used by OpenWFEru for registering 'at' and 'cron' jobs.
|
53
|
+
# 'at' jobs to execute once at a given point in time. 'cron' jobs
|
54
|
+
# execute a specified intervals.
|
55
|
+
# The two main methods are thus schedule_at() and schedule().
|
56
|
+
#
|
57
|
+
# schedule_at() and schedule() await either a Schedulable instance and
|
58
|
+
# params (usually an array or nil), either a block, which is more in the
|
59
|
+
# Ruby way.
|
60
|
+
#
|
61
|
+
# Two examples :
|
62
|
+
#
|
63
|
+
# scheduler.schedule_in("3d") do
|
64
|
+
# regenerate_monthly_report()
|
65
|
+
# end
|
66
|
+
# #
|
67
|
+
# # will call the regenerate_monthly_report method
|
68
|
+
# # in 3 days from now
|
69
|
+
#
|
70
|
+
# and
|
71
|
+
#
|
72
|
+
# class Regenerator < Schedulable
|
73
|
+
# def trigger (frequency)
|
74
|
+
# self.send(frequency)
|
75
|
+
# end
|
76
|
+
# def monthly
|
77
|
+
# # ...
|
78
|
+
# end
|
79
|
+
# def yearly
|
80
|
+
# # ...
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# regenerator = Regenerator.new
|
85
|
+
#
|
86
|
+
# scheduler.schedule_in("4d", r, :monthly)
|
87
|
+
# #
|
88
|
+
# # will regenerate the monthly report in four days
|
89
|
+
#
|
50
90
|
class Scheduler
|
51
|
-
include MonitorMixin
|
91
|
+
include MonitorMixin, Stoppable
|
52
92
|
|
53
93
|
attr_accessor \
|
54
94
|
:precision
|
@@ -69,92 +109,46 @@ module OpenWFE
|
|
69
109
|
@last_cron_minute = -1
|
70
110
|
end
|
71
111
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
|
112
|
+
#
|
113
|
+
# Starts this scheduler (or restart it if it was previously stopped)
|
114
|
+
#
|
77
115
|
def start
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
116
|
+
|
117
|
+
#if @scheduler_thread
|
118
|
+
# @scheduler_thread.wakeup
|
119
|
+
# return
|
120
|
+
#end
|
82
121
|
|
83
122
|
@scheduler_thread = Thread.new do
|
84
123
|
while true
|
124
|
+
break if self.is_stopped?
|
125
|
+
#print "."
|
126
|
+
#$stdout.flush
|
85
127
|
step
|
86
128
|
sleep(@precision)
|
87
129
|
end
|
88
130
|
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def step
|
92
|
-
synchronize do
|
93
|
-
now = Time.new
|
94
|
-
minute = now.to_i / 60
|
95
|
-
|
96
|
-
#puts "step() minute is #{minute}"
|
97
|
-
#puts "step() last_cron_minute is #{@last_cron_minute}"
|
98
|
-
|
99
|
-
#
|
100
|
-
# cron entries
|
101
|
-
|
102
|
-
begin
|
103
|
-
if minute > @last_cron_minute
|
104
|
-
@last_cron_minute = minute
|
105
|
-
@cron_entries.each do |cron_id, cron_entry|
|
106
|
-
#puts "step() cron_id : #{cron_id}"
|
107
|
-
cron_entry.trigger \
|
108
|
-
if cron_entry.matches? now
|
109
|
-
end
|
110
|
-
end
|
111
|
-
rescue Exception => e
|
112
|
-
#puts \
|
113
|
-
# "step() caught exception\n" +
|
114
|
-
# OpenWFE::exception_to_s(e)
|
115
|
-
end
|
116
|
-
|
117
|
-
#
|
118
|
-
# pending jobs
|
119
|
-
|
120
|
-
now = now.to_f
|
121
|
-
#
|
122
|
-
# that's what at jobs do understand
|
123
|
-
|
124
|
-
while true
|
125
|
-
|
126
|
-
#puts "step() job.count is #{@pending_jobs.length}"
|
127
|
-
|
128
|
-
break if @pending_jobs.length < 1
|
129
|
-
|
130
|
-
job = @pending_jobs[0]
|
131
|
-
|
132
|
-
#puts "step() job.at is #{job.at}"
|
133
|
-
#puts "step() now is #{now}"
|
134
|
-
|
135
|
-
break if job.at > now
|
136
|
-
|
137
|
-
#if job.at <= now
|
138
|
-
#
|
139
|
-
# obviously
|
140
131
|
|
141
|
-
|
142
|
-
@pending_jobs.delete_at(0)
|
143
|
-
end
|
144
|
-
end
|
132
|
+
do_restart
|
145
133
|
end
|
146
134
|
|
147
135
|
#
|
148
|
-
#
|
136
|
+
# The scheduler is stoppable via stop() or do_stop()
|
137
|
+
#
|
138
|
+
alias :stop :do_stop
|
139
|
+
|
140
|
+
#
|
141
|
+
# Joins on the scheduler thread
|
149
142
|
#
|
150
143
|
def join
|
151
144
|
@scheduler_thread.join
|
152
145
|
end
|
153
146
|
|
154
147
|
#
|
155
|
-
#
|
148
|
+
# Schedules a job by specifying at which time it should trigger.
|
149
|
+
# Returns the a job_id that can be used to unschedule the job.
|
156
150
|
#
|
157
|
-
def schedule_at (at, schedulable, params)
|
151
|
+
def schedule_at (at, schedulable=nil, params=nil, &block)
|
158
152
|
synchronize do
|
159
153
|
|
160
154
|
#puts "0 at is '#{at.to_s}' (#{at.class})"
|
@@ -168,9 +162,10 @@ module OpenWFE
|
|
168
162
|
at = at.to_f \
|
169
163
|
if at.kind_of? Time
|
170
164
|
|
171
|
-
#puts "1 at is '#{at.to_s}' (#{at.class})"
|
165
|
+
#puts "1 at is '#{at.to_s}' (#{at.class})"}"
|
172
166
|
|
173
|
-
|
167
|
+
b = to_block(schedulable, params, &block)
|
168
|
+
job = AtEntry.new(at, &b)
|
174
169
|
|
175
170
|
if at < (Time.new.to_f + @precision)
|
176
171
|
job.trigger()
|
@@ -193,14 +188,14 @@ module OpenWFE
|
|
193
188
|
end
|
194
189
|
|
195
190
|
return push(job)
|
196
|
-
|
197
191
|
end
|
198
192
|
end
|
199
193
|
|
200
194
|
#
|
201
|
-
#
|
195
|
+
# Schedules a job by stating in how much time it should trigger.
|
196
|
+
# Returns the a job_id that can be used to unschedule the job.
|
202
197
|
#
|
203
|
-
def schedule_in (duration, schedulable, params)
|
198
|
+
def schedule_in (duration, schedulable=nil, params=nil, &block)
|
204
199
|
|
205
200
|
if duration.kind_of?(String)
|
206
201
|
duration = OpenWFE::parse_time_string(duration)
|
@@ -208,44 +203,124 @@ module OpenWFE
|
|
208
203
|
duration = Float(duration.to_s)
|
209
204
|
end
|
210
205
|
|
211
|
-
return schedule_at(
|
206
|
+
return schedule_at(
|
207
|
+
Time.new.to_f + duration, schedulable, params, &block)
|
212
208
|
end
|
213
209
|
|
214
210
|
#
|
215
|
-
#
|
211
|
+
# Unschedules an 'at' or a 'cron' job identified by the id
|
212
|
+
# it was given at schedule time.
|
216
213
|
#
|
217
|
-
def unschedule (
|
214
|
+
def unschedule (job_id)
|
218
215
|
synchronize do
|
219
216
|
|
220
217
|
for i in 0...@pending_jobs.length
|
221
|
-
if @pending_jobs[i].eid ==
|
218
|
+
if @pending_jobs[i].eid == job_id
|
222
219
|
@pending_jobs.delete_at(i)
|
223
220
|
return true
|
224
221
|
end
|
225
222
|
end
|
226
223
|
|
227
|
-
if
|
228
|
-
|
224
|
+
return true if unschedule_cron_job(job_id)
|
225
|
+
|
226
|
+
return false
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
#
|
231
|
+
# Unschedules a cron job
|
232
|
+
#
|
233
|
+
def unschedule_cron_job (job_id)
|
234
|
+
synchronize do
|
235
|
+
if @cron_entries.has_key?(job_id)
|
236
|
+
@cron_entries.delete(job_id)
|
229
237
|
return true
|
230
238
|
end
|
231
|
-
|
232
239
|
return false
|
233
240
|
end
|
234
241
|
end
|
235
242
|
|
236
243
|
#
|
237
|
-
#
|
244
|
+
# Schedules a cron job, the 'cron_line' is a string
|
245
|
+
# following the Unix cron standard (see "man 5 crontab" in your command
|
246
|
+
# line).
|
247
|
+
#
|
248
|
+
# For example :
|
249
|
+
#
|
250
|
+
# scheduler.schedule("5 0 * * *", nil, s, p)
|
251
|
+
# # will trigger the schedulable s with params p every day
|
252
|
+
# # five minutes after midnight
|
253
|
+
#
|
254
|
+
# scheduler.schedule("15 14 1 * *", nil, s, p)
|
255
|
+
# # will trigger s at 14:15 on the first of every month
|
256
|
+
#
|
257
|
+
# scheduler.schedule("0 22 * * 1-5") do
|
258
|
+
# puts "it's break time..."
|
259
|
+
# end
|
260
|
+
# # outputs a message every weekday at 10pm
|
238
261
|
#
|
239
|
-
|
262
|
+
# Returns the job id attributed to this 'cron job', this id can
|
263
|
+
# be used to unschedule the job.
|
264
|
+
#
|
265
|
+
def schedule \
|
266
|
+
(cron_line, cron_id=nil, schedulable=nil, params=nil, &block)
|
267
|
+
|
240
268
|
synchronize do
|
241
|
-
|
269
|
+
|
270
|
+
#
|
271
|
+
# is a job with the same id already scheduled ?
|
272
|
+
|
273
|
+
if cron_id
|
274
|
+
if unschedule(cron_id)
|
275
|
+
ldebug do
|
276
|
+
"schedule() unscheduled previous job "+
|
277
|
+
"under same name '#{cron_id}'"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# schedule
|
284
|
+
|
285
|
+
b = to_block(schedulable, params, &block)
|
286
|
+
entry = CronEntry.new(cron_id, cron_line, &b)
|
242
287
|
@cron_entries[entry.eid] = entry
|
288
|
+
|
243
289
|
return entry.eid
|
244
290
|
end
|
245
291
|
end
|
246
292
|
|
293
|
+
#
|
294
|
+
# Returns the job corresponding to job_id, an instance of AtEntry
|
295
|
+
# or CronEntry will be returned.
|
296
|
+
#
|
297
|
+
def get_job (job_id)
|
298
|
+
|
299
|
+
entry = @cron_entries[job_id]
|
300
|
+
return c if c
|
301
|
+
|
302
|
+
@pending_jobs.each do |entry|
|
303
|
+
return entry if entry.eid == job_id
|
304
|
+
end
|
305
|
+
|
306
|
+
return nil
|
307
|
+
end
|
308
|
+
|
247
309
|
protected
|
248
310
|
|
311
|
+
def to_block (schedulable, params, &block)
|
312
|
+
if schedulable
|
313
|
+
lambda do
|
314
|
+
schedulable.trigger(params)
|
315
|
+
end
|
316
|
+
else
|
317
|
+
block
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
#
|
322
|
+
# Pushes an 'at' job into the pending job list
|
323
|
+
#
|
249
324
|
def push (job, index=-1)
|
250
325
|
|
251
326
|
if index == -1
|
@@ -264,6 +339,70 @@ module OpenWFE
|
|
264
339
|
|
265
340
|
return job.eid
|
266
341
|
end
|
342
|
+
|
343
|
+
#
|
344
|
+
# This is the method called each time the scheduler wakes up
|
345
|
+
# (by default 4 times per second). It's meant to quickly
|
346
|
+
# determine if there are jobs to trigger else to get back to sleep.
|
347
|
+
# 'cron' jobs get executed if necessary then 'at' jobs.
|
348
|
+
#
|
349
|
+
def step
|
350
|
+
synchronize do
|
351
|
+
now = Time.new
|
352
|
+
minute = now.min
|
353
|
+
|
354
|
+
#
|
355
|
+
# cron entries
|
356
|
+
|
357
|
+
begin
|
358
|
+
if now.sec == 0 and minute > @last_cron_minute
|
359
|
+
#
|
360
|
+
# only consider cron entries at the second 0 of a
|
361
|
+
# minute
|
362
|
+
|
363
|
+
@last_cron_minute = minute
|
364
|
+
|
365
|
+
@cron_entries.each do |cron_id, cron_entry|
|
366
|
+
#puts "step() cron_id : #{cron_id}"
|
367
|
+
cron_entry.trigger \
|
368
|
+
if cron_entry.matches? now
|
369
|
+
end
|
370
|
+
end
|
371
|
+
rescue Exception => e
|
372
|
+
#puts \
|
373
|
+
# "step() caught exception\n" +
|
374
|
+
# OpenWFE::exception_to_s(e)
|
375
|
+
end
|
376
|
+
|
377
|
+
#
|
378
|
+
# pending jobs
|
379
|
+
|
380
|
+
now = now.to_f
|
381
|
+
#
|
382
|
+
# that's what at jobs do understand
|
383
|
+
|
384
|
+
while true
|
385
|
+
|
386
|
+
#puts "step() job.count is #{@pending_jobs.length}"
|
387
|
+
|
388
|
+
break if @pending_jobs.length < 1
|
389
|
+
|
390
|
+
job = @pending_jobs[0]
|
391
|
+
|
392
|
+
#puts "step() job.at is #{job.at}"
|
393
|
+
#puts "step() now is #{now}"
|
394
|
+
|
395
|
+
break if job.at > now
|
396
|
+
|
397
|
+
#if job.at <= now
|
398
|
+
#
|
399
|
+
# obviously
|
400
|
+
|
401
|
+
job.trigger()
|
402
|
+
@pending_jobs.delete_at(0)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
267
406
|
end
|
268
407
|
|
269
408
|
#
|
@@ -275,213 +414,258 @@ module OpenWFE
|
|
275
414
|
def trigger (params)
|
276
415
|
raise "trigger() implementation is missing"
|
277
416
|
end
|
417
|
+
|
418
|
+
def reschedule (scheduler)
|
419
|
+
raise "reschedule() implentation is missing"
|
420
|
+
end
|
278
421
|
end
|
279
422
|
|
280
|
-
|
281
|
-
# a 'cron line' is a line in the sense of a crontab (man 5 cron) file
|
282
|
-
#
|
283
|
-
class CronLine
|
423
|
+
protected
|
284
424
|
|
285
|
-
|
425
|
+
JOB_ID_LOCK = Monitor.new
|
286
426
|
|
287
|
-
|
427
|
+
class Entry
|
428
|
+
|
429
|
+
@@last_given_id = 0
|
430
|
+
#
|
431
|
+
# as a scheduler is fully transient, no need to
|
432
|
+
# have persistent ids, a simple counter is sufficient
|
288
433
|
|
289
|
-
|
434
|
+
attr_accessor \
|
435
|
+
:eid, :block
|
290
436
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
437
|
+
def initialize (entry_id=nil, &block)
|
438
|
+
@block = block
|
439
|
+
if entry_id
|
440
|
+
@eid = entry_id
|
441
|
+
else
|
442
|
+
JOB_ID_LOCK.synchronize do
|
443
|
+
@eid = @@last_given_id
|
444
|
+
@@last_given_id = @eid + 1
|
445
|
+
end
|
446
|
+
end
|
295
447
|
end
|
296
448
|
|
297
|
-
|
298
|
-
@
|
299
|
-
|
300
|
-
@months = parse_item(items[3], 1, 12)
|
301
|
-
@weekdays = parse_item(items[4], 1, 7)
|
302
|
-
|
303
|
-
adjust_arrays()
|
449
|
+
#def trigger
|
450
|
+
# @block.call @eid
|
451
|
+
#end
|
304
452
|
end
|
305
453
|
|
306
|
-
|
454
|
+
class AtEntry < Entry
|
307
455
|
|
308
|
-
|
309
|
-
|
310
|
-
end
|
456
|
+
attr_accessor \
|
457
|
+
:at
|
311
458
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
return false if no_match?(time.wday, @weekdays)
|
459
|
+
def initialize (at, &block)
|
460
|
+
super(&block)
|
461
|
+
@at = at
|
462
|
+
end
|
317
463
|
|
318
|
-
|
464
|
+
def trigger
|
465
|
+
@block.call @eid, @at
|
466
|
+
end
|
319
467
|
end
|
320
468
|
|
321
|
-
|
469
|
+
class CronEntry < Entry
|
322
470
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
def
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
@
|
334
|
-
|
335
|
-
|
471
|
+
attr_accessor \
|
472
|
+
:cron_line
|
473
|
+
|
474
|
+
def initialize (cron_id, line, &block)
|
475
|
+
|
476
|
+
super(cron_id, &block)
|
477
|
+
|
478
|
+
if line.kind_of? String
|
479
|
+
@cron_line = CronLine.new(line)
|
480
|
+
elsif line.kind_of? CronLine
|
481
|
+
@cron_line = line
|
482
|
+
else
|
483
|
+
raise \
|
484
|
+
"Cannot initialize a CronEntry " +
|
485
|
+
"with a param of class #{line.class}"
|
336
486
|
end
|
337
487
|
end
|
338
488
|
|
339
|
-
def
|
489
|
+
def matches? (time)
|
490
|
+
@cron_line.matches? time
|
491
|
+
end
|
492
|
+
|
493
|
+
def trigger
|
494
|
+
@block.call @eid, @cron_line
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
#
|
499
|
+
# A 'cron line' is a line in the sense of a crontab
|
500
|
+
# (man 5 crontab) file line.
|
501
|
+
#
|
502
|
+
class CronLine
|
340
503
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
504
|
+
attr_reader \
|
505
|
+
:minutes,
|
506
|
+
:hours,
|
507
|
+
:days,
|
508
|
+
:months,
|
509
|
+
:weekdays
|
347
510
|
|
348
|
-
|
511
|
+
def initialize (line)
|
349
512
|
|
350
|
-
|
351
|
-
i = max if i > max
|
513
|
+
super()
|
352
514
|
|
353
|
-
|
354
|
-
end
|
515
|
+
items = line.split
|
355
516
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
i = Integer(i)
|
361
|
-
i = min if i < min
|
362
|
-
i = max if i > max
|
363
|
-
result << i
|
517
|
+
if items.length != 5
|
518
|
+
raise \
|
519
|
+
"cron '#{line}' string should hold 5 items, " +
|
520
|
+
"not #{items.length}" \
|
364
521
|
end
|
365
|
-
|
522
|
+
|
523
|
+
@minutes = parse_item(items[0], 0, 59)
|
524
|
+
@hours = parse_item(items[1], 0, 24)
|
525
|
+
@days = parse_item(items[2], 1, 31)
|
526
|
+
@months = parse_item(items[3], 1, 12)
|
527
|
+
@weekdays = parse_weekdays(items[4])
|
528
|
+
|
529
|
+
adjust_arrays()
|
366
530
|
end
|
367
531
|
|
368
|
-
def
|
369
|
-
i = item.index("-")
|
370
|
-
j = item.index("/")
|
532
|
+
def matches? (time)
|
371
533
|
|
372
|
-
|
534
|
+
if time.kind_of?(Float) or time.kind_of?(Integer)
|
535
|
+
time = Time.at(time)
|
536
|
+
end
|
373
537
|
|
374
|
-
|
538
|
+
return false if no_match?(time.min, @minutes)
|
539
|
+
return false if no_match?(time.hour, @hours)
|
540
|
+
return false if no_match?(time.day, @days)
|
541
|
+
return false if no_match?(time.month, @months)
|
542
|
+
return false if no_match?(time.wday, @weekdays)
|
375
543
|
|
376
|
-
|
377
|
-
|
544
|
+
return true
|
545
|
+
end
|
378
546
|
|
379
|
-
|
547
|
+
#
|
548
|
+
# Returns an array of 5 arrays (minutes, hours, days, months,
|
549
|
+
# weekdays).
|
550
|
+
# This method is used by the cronline unit tests.
|
551
|
+
#
|
552
|
+
def to_array
|
553
|
+
[ @minutes, @hours, @days, @months, @weekdays ]
|
554
|
+
end
|
380
555
|
|
381
|
-
|
556
|
+
private
|
382
557
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
558
|
+
#
|
559
|
+
# adjust values to Ruby
|
560
|
+
#
|
561
|
+
def adjust_arrays()
|
562
|
+
if @hours
|
563
|
+
@hours.each do |h|
|
564
|
+
h = 0 if h == 23
|
565
|
+
end
|
566
|
+
end
|
567
|
+
if @weekdays
|
568
|
+
@weekdays.each do |wd|
|
569
|
+
wd = wd - 1
|
570
|
+
end
|
387
571
|
end
|
388
|
-
|
389
|
-
else # case */x
|
390
|
-
istart = min
|
391
|
-
iend = max
|
392
572
|
end
|
393
573
|
|
394
|
-
|
395
|
-
|
574
|
+
WDS = [ "mon", "tue", "wed", "thu", "fri", "sat", "sun" ]
|
575
|
+
#
|
576
|
+
# used by parse_weekday()
|
577
|
+
|
578
|
+
def parse_weekdays (item)
|
396
579
|
|
397
|
-
|
580
|
+
item = item.downcase
|
398
581
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
582
|
+
WDS.each_with_index do |day, index|
|
583
|
+
item = item.gsub(day, "#{index+1}")
|
584
|
+
end
|
585
|
+
|
586
|
+
return parse_item(item, 1, 7)
|
404
587
|
end
|
405
588
|
|
406
|
-
|
407
|
-
|
589
|
+
def parse_item (item, min, max)
|
590
|
+
|
591
|
+
return nil \
|
592
|
+
if item == "*"
|
593
|
+
return parse_list(item, min, max) \
|
594
|
+
if item.index(",")
|
595
|
+
return parse_range(item, min, max) \
|
596
|
+
if item.index("*") or item.index("-")
|
408
597
|
|
409
|
-
|
598
|
+
i = Integer(item)
|
410
599
|
|
411
|
-
|
600
|
+
i = min if i < min
|
601
|
+
i = max if i > max
|
412
602
|
|
413
|
-
|
414
|
-
return false if value == v
|
603
|
+
return [ i ]
|
415
604
|
end
|
416
605
|
|
417
|
-
|
418
|
-
|
419
|
-
|
606
|
+
def parse_list (item, min, max)
|
607
|
+
items = item.split(",")
|
608
|
+
result = []
|
609
|
+
items.each do |i|
|
610
|
+
i = Integer(i)
|
611
|
+
i = min if i < min
|
612
|
+
i = max if i > max
|
613
|
+
result << i
|
614
|
+
end
|
615
|
+
return result
|
616
|
+
end
|
420
617
|
|
421
|
-
|
618
|
+
def parse_range (item, min, max)
|
619
|
+
i = item.index("-")
|
620
|
+
j = item.index("/")
|
422
621
|
|
423
|
-
|
622
|
+
inc = 1
|
424
623
|
|
425
|
-
|
624
|
+
inc = Integer(item[j+1..-1]) if j
|
426
625
|
|
427
|
-
|
428
|
-
|
429
|
-
# as a scheduler is fully transient, no need to
|
430
|
-
# have persistent ids, a simple counter is sufficient
|
626
|
+
istart = -1
|
627
|
+
iend = -1
|
431
628
|
|
432
|
-
|
433
|
-
:eid, :schedulable, :params
|
434
|
-
|
435
|
-
def initialize (schedulable, params)
|
436
|
-
@schedulable = schedulable
|
437
|
-
@params = params
|
438
|
-
JOB_ID_LOCK.synchronize do
|
439
|
-
@eid = @@last_given_id + 1
|
440
|
-
@@last_given_id = @eid
|
441
|
-
end
|
442
|
-
end
|
629
|
+
if i
|
443
630
|
|
444
|
-
|
445
|
-
@schedulable.trigger(params)
|
446
|
-
end
|
447
|
-
end
|
631
|
+
istart = Integer(item[0..i-1])
|
448
632
|
|
449
|
-
|
633
|
+
if j
|
634
|
+
iend = Integer(item[i+1..j])
|
635
|
+
else
|
636
|
+
iend = Integer(item[i+1..-1])
|
637
|
+
end
|
450
638
|
|
451
|
-
|
452
|
-
|
639
|
+
else # case */x
|
640
|
+
istart = min
|
641
|
+
iend = max
|
642
|
+
end
|
453
643
|
|
454
|
-
|
455
|
-
|
456
|
-
@at = at
|
457
|
-
end
|
458
|
-
end
|
644
|
+
istart = min if istart < min
|
645
|
+
iend = max if iend > max
|
459
646
|
|
460
|
-
|
647
|
+
result = []
|
461
648
|
|
462
|
-
|
463
|
-
|
649
|
+
value = istart
|
650
|
+
while true
|
651
|
+
result << value
|
652
|
+
value = value + inc
|
653
|
+
break if value > iend
|
654
|
+
end
|
464
655
|
|
465
|
-
|
656
|
+
return result
|
657
|
+
end
|
466
658
|
|
467
|
-
|
659
|
+
def no_match? (value, cron_values)
|
468
660
|
|
469
|
-
|
470
|
-
@cronline = CronLine.new(line)
|
471
|
-
elsif line.kind_of?(CronLine)
|
472
|
-
@cronline = line
|
473
|
-
else
|
474
|
-
raise \
|
475
|
-
"Cannot initialize a CronEntry " +
|
476
|
-
"with a param of class #{line.class}"
|
477
|
-
end
|
661
|
+
return false if not cron_values
|
478
662
|
|
479
|
-
|
480
|
-
|
663
|
+
cron_values.each do |v|
|
664
|
+
return false if value == v
|
665
|
+
end
|
481
666
|
|
482
|
-
|
483
|
-
|
484
|
-
end
|
667
|
+
return true
|
668
|
+
end
|
485
669
|
end
|
486
670
|
|
487
671
|
end
|