openwferu 0.9.16 → 0.9.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. data/examples/about_state.rb +81 -0
  2. data/examples/engine_template.rb +7 -0
  3. data/lib/openwfe/contextual.rb +2 -2
  4. data/lib/openwfe/def.rb +2 -3
  5. data/lib/openwfe/{util/schedulers.rb → engine.rb} +3 -39
  6. data/lib/openwfe/engine/engine.rb +202 -251
  7. data/lib/openwfe/engine/process_status.rb +359 -0
  8. data/lib/openwfe/expool/errorjournal.rb +6 -6
  9. data/lib/openwfe/expool/expressionpool.rb +161 -239
  10. data/lib/openwfe/expool/expstorage.rb +185 -55
  11. data/lib/openwfe/expool/journal.rb +1 -2
  12. data/lib/openwfe/expool/parser.rb +233 -0
  13. data/lib/openwfe/expool/threadedexpstorage.rb +6 -18
  14. data/lib/openwfe/expool/wfidgen.rb +25 -7
  15. data/lib/openwfe/expool/yamlexpstorage.rb +60 -37
  16. data/lib/openwfe/expressions/condition.rb +49 -12
  17. data/lib/openwfe/expressions/environment.rb +45 -15
  18. data/lib/openwfe/expressions/expressionmap.rb +39 -19
  19. data/lib/openwfe/expressions/fe_concurrence.rb +24 -13
  20. data/lib/openwfe/expressions/fe_cron.rb +19 -18
  21. data/lib/openwfe/expressions/fe_cursor.rb +69 -28
  22. data/lib/openwfe/expressions/fe_define.rb +4 -1
  23. data/lib/openwfe/expressions/fe_do.rb +1 -3
  24. data/lib/openwfe/expressions/fe_equals.rb +131 -20
  25. data/lib/openwfe/expressions/fe_fqv.rb +27 -3
  26. data/lib/openwfe/expressions/fe_iterator.rb +14 -7
  27. data/lib/openwfe/expressions/fe_listen.rb +7 -2
  28. data/lib/openwfe/expressions/fe_misc.rb +187 -20
  29. data/lib/openwfe/expressions/fe_participant.rb +8 -7
  30. data/lib/openwfe/expressions/fe_reserve.rb +105 -33
  31. data/lib/openwfe/expressions/fe_save.rb +55 -5
  32. data/lib/openwfe/expressions/{fe_value.rb → fe_set.rb} +6 -82
  33. data/lib/openwfe/expressions/fe_sleep.rb +25 -15
  34. data/lib/openwfe/expressions/fe_subprocess.rb +2 -2
  35. data/lib/openwfe/expressions/fe_wait.rb +3 -2
  36. data/lib/openwfe/expressions/fe_when.rb +7 -15
  37. data/lib/openwfe/expressions/flowexpression.rb +90 -49
  38. data/lib/openwfe/expressions/merge.rb +7 -1
  39. data/lib/openwfe/expressions/raw.rb +261 -63
  40. data/lib/openwfe/expressions/{raw_prog.rb → rprocdef.rb} +94 -179
  41. data/lib/openwfe/expressions/time.rb +36 -12
  42. data/lib/openwfe/expressions/timeout.rb +9 -7
  43. data/lib/openwfe/expressions/value.rb +126 -0
  44. data/lib/openwfe/flowexpressionid.rb +52 -22
  45. data/lib/openwfe/listeners/listeners.rb +3 -3
  46. data/lib/openwfe/listeners/socketlisteners.rb +8 -5
  47. data/lib/openwfe/logging.rb +6 -3
  48. data/lib/openwfe/omixins.rb +8 -6
  49. data/lib/openwfe/orest/xmlcodec.rb +16 -12
  50. data/lib/openwfe/participants.rb +38 -0
  51. data/lib/openwfe/participants/participant.rb +1 -1
  52. data/lib/openwfe/participants/participantmap.rb +24 -10
  53. data/lib/openwfe/participants/participants.rb +4 -3
  54. data/lib/openwfe/participants/soapparticipants.rb +1 -1
  55. data/lib/openwfe/participants/socketparticipants.rb +1 -1
  56. data/lib/openwfe/rudefinitions.rb +7 -5
  57. data/lib/openwfe/storage/yamlcustom.rb +10 -10
  58. data/lib/openwfe/storage/yamlfilestorage.rb +12 -12
  59. data/lib/openwfe/tools/flowtracer.rb +6 -5
  60. data/lib/openwfe/util/dollar.rb +42 -85
  61. data/lib/openwfe/util/ometa.rb +1 -3
  62. data/lib/openwfe/util/workqueue.rb +1 -1
  63. data/lib/openwfe/utils.rb +33 -11
  64. data/lib/openwfe/version.rb +2 -2
  65. data/lib/openwfe/workitem.rb +76 -14
  66. data/lib/openwfe/worklist/storelocks.rb +9 -4
  67. data/lib/openwfe/worklist/storeparticipant.rb +1 -1
  68. data/test/back_0916_test.rb +101 -0
  69. data/test/bm/ft_26_load.rb +1 -1
  70. data/test/bm/ft_26b_load.rb +1 -1
  71. data/test/bm/ft_26c_load.rb +3 -2
  72. data/test/bm/ft_26d_load.rb +97 -0
  73. data/test/bm/ft_recu.rb +71 -0
  74. data/test/concurrence_test.rb +1 -1
  75. data/test/condition_test.rb +152 -0
  76. data/test/description_test.rb +12 -7
  77. data/test/eno_test.rb +1 -1
  78. data/test/expool_20031219_0916.tgz +0 -0
  79. data/test/fe_lookup_att_test.rb +1 -1
  80. data/test/fei_test.rb +16 -0
  81. data/test/file_persistence_test.rb +8 -12
  82. data/test/filep_cancel_test.rb +116 -0
  83. data/test/flowtestbase.rb +47 -25
  84. data/test/ft_0.rb +1 -1
  85. data/test/ft_10_loop.rb +29 -14
  86. data/test/{ft_10b_loop2.rb → ft_10b_loop.rb} +2 -11
  87. data/test/ft_11_ppd.rb +6 -17
  88. data/test/ft_11b_ppd.rb +1 -4
  89. data/test/ft_12_blockparticipant.rb +1 -1
  90. data/test/ft_13_eno.rb +1 -1
  91. data/test/ft_15_iterator.rb +1 -1
  92. data/test/ft_15b_iterator.rb +1 -1
  93. data/test/ft_17_condition.rb +6 -6
  94. data/test/ft_18_pname.rb +1 -1
  95. data/test/ft_20_cron.rb +1 -1
  96. data/test/ft_21_cron.rb +6 -4
  97. data/test/ft_22_history.rb +1 -1
  98. data/test/ft_23_when.rb +1 -1
  99. data/test/ft_23b_when.rb +18 -6
  100. data/test/ft_23c_wait.rb +8 -6
  101. data/test/ft_25_cancel.rb +7 -5
  102. data/test/ft_27_getflowpos.rb +22 -17
  103. data/test/ft_28_fileparticipant.rb +1 -2
  104. data/test/ft_2_concurrence.rb +1 -1
  105. data/test/ft_2b_concurrence.rb +25 -20
  106. data/test/ft_30_socketlistener.rb +0 -3
  107. data/test/ft_34_cancelwfid.rb +9 -9
  108. data/test/ft_35_localdefs.rb +0 -1
  109. data/test/ft_36_subprocids.rb +6 -6
  110. data/test/ft_38_tag.rb +3 -2
  111. data/test/ft_38b_tag.rb +229 -0
  112. data/test/ft_39_reserve.rb +3 -18
  113. data/test/ft_39b_reserve.rb +34 -5
  114. data/test/ft_3b_lookup_vf.rb +83 -0
  115. data/test/ft_40_defined.rb +2 -11
  116. data/test/ft_42_environments.rb +4 -6
  117. data/test/ft_44b_restore.rb +88 -22
  118. data/test/ft_45_citerator.rb +57 -11
  119. data/test/ft_49_condition.rb +4 -2
  120. data/test/ft_4_misc.rb +24 -3
  121. data/test/ft_50_xml_attribute.rb +17 -20
  122. data/test/ft_54_listen.rb +1 -1
  123. data/test/ft_54b_listen.rb +2 -2
  124. data/test/ft_56_timeout.rb +8 -1
  125. data/test/ft_57_a.rb +10 -10
  126. data/test/ft_59_ps.rb +49 -16
  127. data/test/ft_60_ecancel.rb +52 -10
  128. data/test/ft_63_pause.rb +8 -8
  129. data/test/ft_65_stringlaunch.rb +4 -6
  130. data/test/ft_67_schedlaunch.rb +4 -4
  131. data/test/ft_69_cancelmissing.rb +4 -2
  132. data/test/ft_70_lookupvar.rb +2 -2
  133. data/test/ft_72_lookup_processes.rb +2 -2
  134. data/test/ft_73_cancel_sub.rb +8 -8
  135. data/test/ft_77_segments.rb +38 -0
  136. data/test/ft_78_eval.rb +154 -0
  137. data/test/ft_79_tticket.rb +185 -0
  138. data/test/ft_80_spname.rb +95 -0
  139. data/test/ft_81_exp.rb +64 -0
  140. data/test/ft_82_trecu.rb +48 -0
  141. data/test/ft_83_badpause.rb +62 -0
  142. data/test/ft_84_updateexp.rb +125 -0
  143. data/test/ft_9b_cursor.rb +105 -0
  144. data/test/ft_tests.rb +14 -1
  145. data/test/hash_test.rb +7 -7
  146. data/test/hparticipant_test.rb +4 -4
  147. data/test/lookup_vf_test.rb +94 -0
  148. data/test/misc_test.rb +5 -3
  149. data/test/orest_test.rb +4 -3
  150. data/test/param_test.rb +12 -16
  151. data/test/participant_test.rb +36 -0
  152. data/test/pending.rb +10 -10
  153. data/test/rake_ltest.rb +1 -10
  154. data/test/rake_qtest.rb +7 -6
  155. data/test/raw_prog_test.rb +89 -121
  156. data/test/restart_cron_test.rb +84 -36
  157. data/test/restart_paused_test.rb +100 -0
  158. data/test/restart_sleep_test.rb +1 -1
  159. data/test/restart_tests.rb +1 -0
  160. data/test/restart_when_test.rb +33 -22
  161. data/test/ruby_procdef_test.rb +19 -18
  162. data/test/sec_test.rb +74 -35
  163. data/test/storage_test.rb +44 -0
  164. data/test/test.rb +3 -0
  165. data/test/timeout_test.rb +7 -18
  166. data/test/wfid_test.rb +2 -1
  167. data/test/wi_test.rb +29 -18
  168. metadata +121 -57
  169. data/lib/openwfe/expressions/raw_xml.rb +0 -176
  170. data/lib/openwfe/expressions/simplerep.rb +0 -266
  171. data/lib/openwfe/util/kotoba.rb +0 -236
  172. data/lib/openwfe/util/lru.rb +0 -171
  173. data/lib/openwfe/util/otime.rb +0 -246
  174. data/lib/openwfe/util/safe.rb +0 -160
  175. data/lib/openwfe/util/scheduler.rb +0 -1158
  176. data/test/cron_test.rb +0 -113
  177. data/test/cronline_test.rb +0 -60
  178. data/test/dollar_test.rb +0 -90
  179. data/test/kotoba_test.rb +0 -72
  180. data/test/lru_test.rb +0 -79
  181. data/test/safely_test.rb +0 -84
  182. data/test/scheduler_1_test.rb +0 -88
  183. data/test/scheduler_test.rb +0 -363
  184. data/test/time_test.rb +0 -84
@@ -1,1158 +0,0 @@
1
-
2
- #--
3
- # Copyright (c) 2006-2007, John Mettraux, OpenWFE.org
4
- # All rights reserved.
5
- #
6
- # Redistribution and use in source and binary forms, with or without
7
- # modification, are permitted provided that the following conditions are met:
8
- #
9
- # . Redistributions of source code must retain the above copyright notice, this
10
- # list of conditions and the following disclaimer.
11
- #
12
- # . Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- #
16
- # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
- # used to endorse or promote products derived from this software without
18
- # specific prior written permission.
19
- #
20
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
- # POSSIBILITY OF SUCH DAMAGE.
31
- #++
32
- #
33
-
34
- #
35
- # "made in Japan"
36
- #
37
- # John Mettraux at openwfe.org
38
- #
39
-
40
- require 'monitor'
41
- require 'openwfe/util/otime'
42
-
43
-
44
- module OpenWFE
45
-
46
- #
47
- # The Scheduler is used by OpenWFEru for registering 'at' and 'cron' jobs.
48
- # 'at' jobs to execute once at a given point in time. 'cron' jobs
49
- # execute a specified intervals.
50
- # The two main methods are thus schedule_at() and schedule().
51
- #
52
- # schedule_at() and schedule() await either a Schedulable instance and
53
- # params (usually an array or nil), either a block, which is more in the
54
- # Ruby way.
55
- #
56
- # Some examples :
57
- #
58
- # scheduler.schedule_in("3d") do
59
- # regenerate_monthly_report()
60
- # end
61
- # #
62
- # # will call the regenerate_monthly_report method
63
- # # in 3 days from now
64
- #
65
- # scheduler.schedule "0 22 * * 1-5" do
66
- # log.info "activating security system..."
67
- # activate_security_system()
68
- # end
69
- #
70
- # job_id = scheduler.schedule_at "Sun Oct 07 14:24:01 +0900 2009" do
71
- # init_self_destruction_sequence()
72
- # end
73
- #
74
- # an example that uses a Schedulable class :
75
- #
76
- # class Regenerator < Schedulable
77
- # def trigger (frequency)
78
- # self.send(frequency)
79
- # end
80
- # def monthly
81
- # # ...
82
- # end
83
- # def yearly
84
- # # ...
85
- # end
86
- # end
87
- #
88
- # regenerator = Regenerator.new
89
- #
90
- # scheduler.schedule_in("4d", regenerator)
91
- # #
92
- # # will regenerate the report in four days
93
- #
94
- # scheduler.schedule_in(
95
- # "5d",
96
- # { :schedulable => regenerator, :scope => :month })
97
- # #
98
- # # will regenerate the monthly report in 5 days
99
- #
100
- # There is also schedule_every() :
101
- #
102
- # scheduler.schedule_every("1h20m") do
103
- # regenerate_latest_report()
104
- # end
105
- #
106
- # The scheduler has a "exit_when_no_more_jobs" attribute. When set to
107
- # 'true', the scheduler will exit as soon as there are no more jobs to
108
- # run.
109
- # Use with care though, if you create a scheduler, set this attribute
110
- # to true and start the scheduler, the scheduler will immediately exit.
111
- # This attribute is best used indirectly : the method
112
- # join_until_no_more_jobs() wraps it.
113
- #
114
- # The :scheduler_precision can be set when instantiating the scheduler.
115
- #
116
- # scheduler = OpenWFE::Scheduler.new(:scheduler_precision => 0.500)
117
- # scheduler.start
118
- # #
119
- # # instatiates a scheduler that checks its jobs twice per second
120
- # # (the default is 4 times per second (0.250))
121
- #
122
- #
123
- # Since OpenWFEru 0.9.16, tags can be attached to jobs scheduled :
124
- #
125
- # scheduler.schedule_in "2h", :tags => "backup" do
126
- # init_backup_sequence()
127
- # end
128
- #
129
- # scheduler.schedule "0 24 * * *", :tags => "new_day" do
130
- # do_this_or_that()
131
- # end
132
- #
133
- # jobs = find_jobs 'backup'
134
- # jobs.each { |job| job.unschedule }
135
- #
136
- # Multiple tags may be attached to a single job :
137
- #
138
- # scheduler.schedule_in "2h", :tags => [ "backup", "important" ] do
139
- # init_backup_sequence()
140
- # end
141
- #
142
- # The vanilla case for tags assume they are String instances, but nothing
143
- # prevents you from using anything else. The scheduler has no persistence
144
- # by itself, so no serialization issue.
145
- #
146
- #
147
- # Since OpenWFEru 0.9.16, a cron schedule can be set at the second level :
148
- #
149
- # scheduler.schedule "7 * * * * *" do
150
- # puts "it's now the seventh second of the minute"
151
- # end
152
- #
153
- # The OpenWFEru scheduler recognizes an optional first column for second
154
- # scheduling. This column can, like for the other columns, specify a
155
- # value ("7"), a list of values ("7,8,9,27") or a range ("7-12").
156
- #
157
- class Scheduler
158
- include MonitorMixin
159
-
160
- #
161
- # By default, the precision is 0.250, with means the scheduler
162
- # will check for jobs to execute 4 times per second.
163
- #
164
- attr_accessor :precision
165
-
166
- #
167
- # As its name implies.
168
- #
169
- attr_accessor :stopped
170
-
171
-
172
- def initialize (params={})
173
-
174
- super()
175
-
176
- @pending_jobs = []
177
- @cron_jobs = {}
178
-
179
- @scheduler_thread = nil
180
-
181
- @precision = 0.250
182
- # every 250ms, the scheduler wakes up (default value)
183
- begin
184
- @precision = Float(params[:scheduler_precision])
185
- rescue Exception => e
186
- # let precision at its default value
187
- end
188
-
189
- @exit_when_no_more_jobs = false
190
- @dont_reschedule_every = false
191
-
192
- @last_cron_second = -1
193
-
194
- @stopped = true
195
- end
196
-
197
- #
198
- # Starts this scheduler (or restart it if it was previously stopped)
199
- #
200
- def sstart
201
-
202
- @stopped = false
203
-
204
- @scheduler_thread = Thread.new do
205
-
206
- if defined?(JRUBY_VERSION)
207
-
208
- require 'java'
209
-
210
- java.lang.Thread.current_thread.name = \
211
- "openwferu scheduler (Ruby Thread)"
212
- end
213
-
214
- while true
215
- break if @stopped
216
- step
217
- sleep @precision
218
- end
219
- end
220
- end
221
-
222
- #
223
- # The scheduler is stoppable via sstop()
224
- #
225
- def sstop
226
-
227
- @stopped = true
228
- end
229
-
230
- alias :start :sstart
231
- alias :stop :sstop
232
-
233
- #
234
- # Joins on the scheduler thread
235
- #
236
- def join
237
-
238
- @scheduler_thread.join
239
- end
240
-
241
- #
242
- # Like join() but takes care of setting the 'exit_when_no_more_jobs'
243
- # attribute of this scheduler to true before joining.
244
- # Thus the scheduler will exit (and the join terminates) as soon as
245
- # there aren't no more 'at' (or 'every') jobs in the scheduler.
246
- #
247
- # Currently used only in unit tests.
248
- #
249
- def join_until_no_more_jobs
250
-
251
- @exit_when_no_more_jobs = true
252
- join
253
- end
254
-
255
- #
256
- # Schedules a job by specifying at which time it should trigger.
257
- # Returns the a job_id that can be used to unschedule the job.
258
- #
259
- # This method returns a job identifier which can be used to unschedule()
260
- # the job.
261
- #
262
- # If the job is specified in the past, it will be triggered immediately
263
- # but not scheduled.
264
- # To avoid the triggering, the parameter :discard_past may be set to
265
- # true :
266
- #
267
- # jobid = scheduler.schedule_at(yesterday, :discard_past => true) do
268
- # puts "you'll never read this message"
269
- # end
270
- #
271
- # And 'jobid' will hold a nil (not scheduled).
272
- #
273
- #
274
- def schedule_at (at, params={}, &block)
275
-
276
- params = prepare_params(params)
277
-
278
- sschedule_at(at, params, &block)
279
- end
280
-
281
-
282
- #
283
- # Schedules a job by stating in how much time it should trigger.
284
- # Returns the a job_id that can be used to unschedule the job.
285
- #
286
- # This method returns a job identifier which can be used to unschedule()
287
- # the job.
288
- #
289
- def schedule_in (duration, params={}, &block)
290
-
291
- duration = duration_to_f(duration)
292
- params = prepare_params(params)
293
-
294
- schedule_at(Time.new.to_f + duration, params, &block)
295
- end
296
-
297
- #
298
- # Schedules a job in a loop. After an execution, it will not execute
299
- # before the time specified in 'freq'.
300
- #
301
- # This method returns a job identifier which can be used to unschedule()
302
- # the job.
303
- #
304
- def schedule_every (freq, params={}, &block)
305
-
306
- f = duration_to_f freq
307
-
308
- params = prepare_params params
309
- schedulable = params[:schedulable]
310
- params[:every] = freq
311
-
312
- last_at = params[:last_at]
313
- next_at = if last_at
314
- last_at + f
315
- else
316
- Time.now.to_f + f
317
- end
318
-
319
- sschedule_at next_at, params do |job_id, at|
320
-
321
- if schedulable
322
- schedulable.trigger(params)
323
- else
324
- block.call job_id, at
325
- end
326
-
327
- params[:job_id] = job_id
328
- params[:last_at] = at
329
-
330
- schedule_every(freq, params, &block) \
331
- unless @dont_reschedule_every
332
- #
333
- # yes, this is a kind of recursion
334
-
335
- job_id
336
- end
337
- end
338
-
339
- #
340
- # Unschedules an 'at' or a 'cron' job identified by the id
341
- # it was given at schedule time.
342
- #
343
- def unschedule (job_id)
344
- synchronize do
345
-
346
- for i in 0...@pending_jobs.length
347
- if @pending_jobs[i].job_id == job_id
348
- @pending_jobs.delete_at i
349
- return true
350
- end
351
- end
352
-
353
- unschedule_cron_job job_id
354
- end
355
- end
356
-
357
- #
358
- # Unschedules a cron job
359
- #
360
- def unschedule_cron_job (job_id)
361
- synchronize do
362
- if @cron_jobs.has_key?(job_id)
363
- @cron_jobs.delete job_id
364
- return true
365
- end
366
- false
367
- end
368
- end
369
-
370
- #
371
- # Schedules a cron job, the 'cron_line' is a string
372
- # following the Unix cron standard (see "man 5 crontab" in your command
373
- # line, or http://www.google.com/search?q=man%205%20crontab).
374
- #
375
- # For example :
376
- #
377
- # scheduler.schedule("5 0 * * *", s)
378
- # # will trigger the schedulable s every day
379
- # # five minutes after midnight
380
- #
381
- # scheduler.schedule("15 14 1 * *", s)
382
- # # will trigger s at 14:15 on the first of every month
383
- #
384
- # scheduler.schedule("0 22 * * 1-5") do
385
- # puts "it's break time..."
386
- # end
387
- # # outputs a message every weekday at 10pm
388
- #
389
- # Returns the job id attributed to this 'cron job', this id can
390
- # be used to unschedule the job.
391
- #
392
- # This method returns a job identifier which can be used to unschedule()
393
- # the job.
394
- #
395
- def schedule (cron_line, params={}, &block)
396
- synchronize do
397
-
398
- params = prepare_params(params)
399
-
400
- #
401
- # is a job with the same id already scheduled ?
402
-
403
- cron_id = params[:cron_id]
404
- cron_id = params[:job_id] unless cron_id
405
-
406
- unschedule(cron_id) if cron_id
407
-
408
- #
409
- # schedule
410
-
411
- b = to_block(params, &block)
412
- job = CronJob.new(self, cron_id, cron_line, params, &b)
413
- @cron_jobs[job.job_id] = job
414
-
415
- job.job_id
416
- end
417
- end
418
-
419
- #
420
- # Returns the job corresponding to job_id, an instance of AtJob
421
- # or CronJob will be returned.
422
- #
423
- def get_job (job_id)
424
-
425
- job = @cron_jobs[job_id]
426
- return job if job
427
-
428
- synchronize do
429
- @pending_jobs.find do |job|
430
- job.job_id == job_id
431
- end
432
- end
433
- end
434
-
435
- #
436
- # Finds a job (via get_job()) and then returns the wrapped
437
- # schedulable if any.
438
- #
439
- def get_schedulable (job_id)
440
-
441
- return nil unless job_id
442
-
443
- j = get_job(job_id)
444
-
445
- return j.schedulable if j.respond_to?(:schedulable)
446
-
447
- nil
448
- end
449
-
450
- #
451
- # Returns an array of jobs that have the given tag.
452
- #
453
- def find_jobs (tag)
454
-
455
- result = @cron_jobs.values.find_all do |job|
456
- job.has_tag?(tag)
457
- end
458
-
459
- synchronize do
460
- result + @pending_jobs.find_all do |job|
461
- job.has_tag?(tag)
462
- end
463
- end
464
- end
465
-
466
- #
467
- # Finds the jobs with the given tag and then returns an array of
468
- # the wrapped Schedulable objects.
469
- # Jobs that haven't a wrapped Schedulable won't be included in the
470
- # result.
471
- #
472
- def find_schedulables (tag)
473
-
474
- jobs = find_jobs(tag)
475
-
476
- result = []
477
-
478
- jobs.each do |job|
479
- result.push(job.schedulable) if job.respond_to?(:schedulable)
480
- end
481
-
482
- result
483
- end
484
-
485
- #
486
- # Returns the number of currently pending jobs in this scheduler
487
- # ('at' jobs and 'every' jobs).
488
- #
489
- def pending_job_count
490
- @pending_jobs.size
491
- end
492
-
493
- #
494
- # Returns the number of cron jobs currently active in this scheduler.
495
- #
496
- def cron_job_count
497
- @cron_jobs.size
498
- end
499
-
500
- #
501
- # Returns the current count of 'every' jobs scheduled.
502
- #
503
- def every_job_count
504
- @pending_jobs.select { |j| j.is_a?(EveryJob) }.size
505
- end
506
-
507
- #
508
- # Returns the current count of 'at' jobs scheduled (not 'every').
509
- #
510
- def at_job_count
511
- @pending_jobs.select { |j| j.instance_of?(AtJob) }.size
512
- end
513
-
514
- #
515
- # Returns true if the given string seems to be a cron string.
516
- #
517
- def Scheduler.is_cron_string (s)
518
- s.match(".+ .+ .+ .+ .+")
519
- end
520
-
521
- protected
522
-
523
- #
524
- # Making sure that params is a Hash.
525
- #
526
- def prepare_params (params)
527
- params = { :schedulable => params } \
528
- if params.is_a?(Schedulable)
529
- params
530
- end
531
-
532
- #
533
- # The core method behind schedule_at and schedule_in (and also
534
- # schedule_every). It's protected, don't use it directly.
535
- #
536
- def sschedule_at (at, params={}, &block)
537
- synchronize do
538
-
539
- #puts "0 at is '#{at.to_s}' (#{at.class})"
540
-
541
- at = OpenWFE::to_ruby_time(at) \
542
- if at.kind_of?(String)
543
-
544
- at = OpenWFE::to_gm_time(at) \
545
- if at.kind_of?(DateTime)
546
-
547
- at = at.to_f \
548
- if at.kind_of?(Time)
549
-
550
- #puts "1 at is '#{at.to_s}' (#{at.class})"}"
551
-
552
- jobClass = if params[:every]
553
- EveryJob
554
- else
555
- AtJob
556
- end
557
-
558
- job_id = params[:job_id]
559
-
560
- b = to_block(params, &block)
561
-
562
- job = jobClass.new(self, at, job_id, params, &b)
563
-
564
- unschedule(job_id) if job_id
565
-
566
- if at < (Time.new.to_f + @precision)
567
- job.trigger() unless params[:discard_past]
568
- return nil
569
- end
570
-
571
- return push(job) \
572
- if @pending_jobs.length < 1
573
-
574
- # shortcut : check if the new job is posterior to
575
- # the last job pending
576
-
577
- return push(job) \
578
- if at >= @pending_jobs.last.at
579
-
580
- for i in 0...@pending_jobs.length
581
- if at <= @pending_jobs[i].at
582
- return push(job, i)
583
- end
584
- end
585
-
586
- push(job)
587
- end
588
- end
589
-
590
- #
591
- # Ensures that a duration is a expressed as a Float instance.
592
- #
593
- # duration_to_f("10s")
594
- #
595
- # will yields 10.0
596
- #
597
- def duration_to_f (s)
598
- return s if s.kind_of? Float
599
- return OpenWFE::parse_time_string(s) if s.kind_of? String
600
- Float(s.to_s)
601
- end
602
-
603
- #
604
- # Returns a block. If a block is passed, will return it, else,
605
- # if a :schedulable is set in the params, will return a block
606
- # wrapping a call to it.
607
- #
608
- def to_block (params, &block)
609
-
610
- return block if block
611
-
612
- schedulable = params[:schedulable]
613
-
614
- return nil unless schedulable
615
-
616
- params.delete :schedulable
617
-
618
- l = lambda do
619
- schedulable.trigger(params)
620
- end
621
- class << l
622
- attr_accessor :schedulable
623
- end
624
- l.schedulable = schedulable
625
-
626
- l
627
- end
628
-
629
- #
630
- # Pushes an 'at' job into the pending job list
631
- #
632
- def push (job, index=-1)
633
-
634
- if index == -1
635
- #
636
- # push job at the end
637
- #
638
- @pending_jobs << job
639
- else
640
- #
641
- # insert job at given index
642
- #
643
- @pending_jobs[index, 0] = job
644
- end
645
-
646
- #puts "push() at '#{Time.at(job.at)}'"
647
-
648
- job.job_id
649
- end
650
-
651
- #
652
- # This is the method called each time the scheduler wakes up
653
- # (by default 4 times per second). It's meant to quickly
654
- # determine if there are jobs to trigger else to get back to sleep.
655
- # 'cron' jobs get executed if necessary then 'at' jobs.
656
- #
657
- def step
658
- synchronize do
659
-
660
- now = Time.new
661
-
662
- if @exit_when_no_more_jobs
663
-
664
- if @pending_jobs.size < 1
665
-
666
- @stopped = true
667
- return
668
- end
669
-
670
- @dont_reschedule_every = true if at_job_count < 1
671
- end
672
-
673
- # TODO : eventually consider running cron / pending
674
- # job triggering in two different threads
675
- #
676
- # but well... there's the synchronization issue...
677
-
678
- #
679
- # cron jobs
680
-
681
- if now.sec != @last_cron_second
682
-
683
- @last_cron_second = now.sec
684
-
685
- #puts "step() @cron_jobs.size #{@cron_jobs.size}"
686
-
687
- @cron_jobs.each do |cron_id, cron_job|
688
- #puts "step() cron_id : #{cron_id}"
689
- trigger(cron_job) if cron_job.matches?(now)
690
- end
691
- end
692
-
693
- #
694
- # pending jobs
695
-
696
- now = now.to_f
697
- #
698
- # that's what at jobs do understand
699
-
700
- while true
701
-
702
- #puts "step() job.count is #{@pending_jobs.length}"
703
-
704
- break if @pending_jobs.length < 1
705
-
706
- job = @pending_jobs[0]
707
-
708
- #puts "step() job.at is #{job.at}"
709
- #puts "step() now is #{now}"
710
-
711
- break if job.at > now
712
-
713
- #if job.at <= now
714
- #
715
- # obviously
716
-
717
- trigger job
718
-
719
- @pending_jobs.delete_at(0)
720
- end
721
- end
722
- end
723
-
724
- #
725
- # Triggers the job (in a dedicated thread).
726
- #
727
- # If an error occurs in the job, it well get caught and an error
728
- # message will be displayed to STDOUT.
729
- # If this scheduler provides a lwarn(message) method, it will
730
- # be used insted.
731
- #
732
- def trigger (job)
733
-
734
- Thread.new do
735
- begin
736
- job.trigger
737
- rescue Exception => e
738
- message =
739
- "trigger() caught exception\n" +
740
- OpenWFE::exception_to_s(e)
741
- if self.respond_to? :lwarn
742
- lwarn { message }
743
- else
744
- puts message
745
- end
746
- end
747
- end
748
- end
749
- end
750
-
751
- #
752
- # This module adds a trigger method to any class that includes it.
753
- # The default implementation feature here triggers an exception.
754
- #
755
- module Schedulable
756
-
757
- def trigger (params)
758
- raise "trigger() implementation is missing"
759
- end
760
-
761
- def reschedule (scheduler)
762
- raise "reschedule() implentation is missing"
763
- end
764
- end
765
-
766
- protected
767
-
768
- JOB_ID_LOCK = Monitor.new
769
- #
770
- # would it be better to use a Mutex instead of a full-blown
771
- # Monitor ?
772
-
773
- #
774
- # The parent class for scheduled jobs.
775
- #
776
- class Job
777
-
778
- @@last_given_id = 0
779
- #
780
- # as a scheduler is fully transient, no need to
781
- # have persistent ids, a simple counter is sufficient
782
-
783
- #
784
- # The identifier for the job
785
- #
786
- attr_accessor :job_id
787
-
788
- #
789
- # An array of tags
790
- #
791
- attr_accessor :tags
792
-
793
- #
794
- # The block to execute at trigger time
795
- #
796
- attr_accessor :block
797
-
798
- #
799
- # A reference to the scheduler
800
- #
801
- attr_reader :scheduler
802
-
803
- #
804
- # Keeping a copy of the initialization params of the job.
805
- #
806
- attr_reader :params
807
-
808
-
809
- def initialize (scheduler, job_id, params, &block)
810
-
811
- @scheduler = scheduler
812
- @block = block
813
-
814
- if job_id
815
- @job_id = job_id
816
- else
817
- JOB_ID_LOCK.synchronize do
818
- @job_id = @@last_given_id
819
- @@last_given_id = @job_id + 1
820
- end
821
- end
822
-
823
- @params = params
824
-
825
- #@tags = Array(tags).collect { |tag| tag.to_s }
826
- # making sure we have an array of String tags
827
-
828
- @tags = Array(params[:tags])
829
- # any tag is OK
830
- end
831
-
832
- #
833
- # Returns true if this job sports the given tag
834
- #
835
- def has_tag? (tag)
836
-
837
- @tags.include?(tag)
838
- end
839
-
840
- #
841
- # Removes (cancels) this job from its scheduler.
842
- #
843
- def unschedule
844
-
845
- @scheduler.unschedule(@job_id)
846
- end
847
- end
848
-
849
- #
850
- # An 'at' job.
851
- class AtJob < Job
852
-
853
- #
854
- # The float representation (Time.to_f) of the time at which
855
- # the job should be triggered.
856
- #
857
- attr_accessor :at
858
-
859
- #
860
- # The constructor.
861
- #
862
- def initialize (scheduler, at, at_id, params, &block)
863
-
864
- super(scheduler, at_id, params, &block)
865
- @at = at
866
- end
867
-
868
- #
869
- # Triggers the job (calls the block)
870
- #
871
- def trigger
872
-
873
- @block.call @job_id, @at
874
- end
875
-
876
- #
877
- # Returns the Time instance at which this job is scheduled.
878
- #
879
- def schedule_info
880
-
881
- Time.at(@at)
882
- end
883
- end
884
-
885
- #
886
- # An 'every' job is simply an extension of an 'at' job.
887
- #
888
- class EveryJob < AtJob
889
-
890
- #
891
- # Returns the frequency string used to schedule this EveryJob,
892
- # like for example "3d" or "1M10d3h".
893
- #
894
- def schedule_info
895
-
896
- @params[:every]
897
- end
898
- end
899
-
900
- #
901
- # A cron job.
902
- #
903
- class CronJob < Job
904
-
905
- #
906
- # The CronLine instance representing the times at which
907
- # the cron job has to be triggered.
908
- #
909
- attr_accessor :cron_line
910
-
911
- def initialize (scheduler, cron_id, line, params, &block)
912
-
913
- super(scheduler, cron_id, params, &block)
914
-
915
- if line.is_a?(String)
916
-
917
- @cron_line = CronLine.new(line)
918
-
919
- elsif line.is_a?(CronLine)
920
-
921
- @cron_line = line
922
-
923
- else
924
-
925
- raise \
926
- "Cannot initialize a CronJob " +
927
- "with a param of class #{line.class}"
928
- end
929
- end
930
-
931
- #
932
- # This is the method called by the scheduler to determine if it
933
- # has to fire this CronJob instance.
934
- #
935
- def matches? (time)
936
-
937
- @cron_line.matches? time
938
- end
939
-
940
- #
941
- # As the name implies.
942
- #
943
- def trigger
944
-
945
- @block.call @job_id, @cron_line
946
- end
947
-
948
- #
949
- # Returns the original cron tab string used to schedule this
950
- # Job. Like for example "60/3 * * * Sun".
951
- #
952
- def schedule_info
953
-
954
- @cron_line.original
955
- end
956
- end
957
-
958
- #
959
- # A 'cron line' is a line in the sense of a crontab
960
- # (man 5 crontab) file line.
961
- #
962
- class CronLine
963
-
964
- #
965
- # The string used for creating this cronline instance.
966
- #
967
- attr_reader :original
968
-
969
- attr_reader \
970
- :seconds,
971
- :minutes,
972
- :hours,
973
- :days,
974
- :months,
975
- :weekdays
976
-
977
- def initialize (line)
978
-
979
- super()
980
-
981
- @original = line
982
-
983
- items = line.split
984
-
985
- unless [ 5, 6 ].include?(items.length)
986
- raise \
987
- "cron '#{line}' string should hold 5 or 6 items, " +
988
- "not #{items.length}" \
989
- end
990
-
991
- offset = items.length - 5
992
-
993
- @seconds = if offset == 1
994
- parse_item(items[0], 0, 59)
995
- else
996
- [ 0 ]
997
- end
998
- @minutes = parse_item(items[0+offset], 0, 59)
999
- @hours = parse_item(items[1+offset], 0, 24)
1000
- @days = parse_item(items[2+offset], 1, 31)
1001
- @months = parse_item(items[3+offset], 1, 12)
1002
- @weekdays = parse_weekdays(items[4+offset])
1003
-
1004
- #adjust_arrays()
1005
- end
1006
-
1007
- #
1008
- # Returns true if the given time matches this cron line.
1009
- #
1010
- def matches? (time)
1011
-
1012
- if time.kind_of?(Float) or time.kind_of?(Integer)
1013
- time = Time.at(time)
1014
- end
1015
-
1016
- return false if no_match?(time.sec, @seconds)
1017
- return false if no_match?(time.min, @minutes)
1018
- return false if no_match?(time.hour, @hours)
1019
- return false if no_match?(time.day, @days)
1020
- return false if no_match?(time.month, @months)
1021
- return false if no_match?(time.wday, @weekdays)
1022
-
1023
- true
1024
- end
1025
-
1026
- #
1027
- # Returns an array of 6 arrays (seconds, minutes, hours, days,
1028
- # months, weekdays).
1029
- # This method is used by the cronline unit tests.
1030
- #
1031
- def to_array
1032
- [ @seconds, @minutes, @hours, @days, @months, @weekdays ]
1033
- end
1034
-
1035
- private
1036
-
1037
- #--
1038
- # adjust values to Ruby
1039
- #
1040
- #def adjust_arrays()
1041
- # @hours = @hours.collect { |h|
1042
- # if h == 24
1043
- # 0
1044
- # else
1045
- # h
1046
- # end
1047
- # } if @hours
1048
- # @weekdays = @weekdays.collect { |wd|
1049
- # wd - 1
1050
- # } if @weekdays
1051
- #end
1052
- #
1053
- # dead code, keeping as a reminder
1054
- #++
1055
-
1056
- WDS = [ "mon", "tue", "wed", "thu", "fri", "sat", "sun" ]
1057
- #
1058
- # used by parse_weekday()
1059
-
1060
- def parse_weekdays (item)
1061
-
1062
- item = item.downcase
1063
-
1064
- WDS.each_with_index do |day, index|
1065
- item = item.gsub(day, "#{index+1}")
1066
- end
1067
-
1068
- parse_item(item, 1, 7)
1069
- end
1070
-
1071
- def parse_item (item, min, max)
1072
-
1073
- return nil \
1074
- if item == "*"
1075
- return parse_list(item, min, max) \
1076
- if item.index(",")
1077
- return parse_range(item, min, max) \
1078
- if item.index("*") or item.index("-")
1079
-
1080
- i = Integer(item)
1081
-
1082
- i = min if i < min
1083
- i = max if i > max
1084
-
1085
- [ i ]
1086
- end
1087
-
1088
- def parse_list (item, min, max)
1089
-
1090
- items = item.split(",")
1091
- result = []
1092
- items.each do |i|
1093
- i = Integer(i)
1094
- i = min if i < min
1095
- i = max if i > max
1096
- result << i
1097
- end
1098
- result
1099
- end
1100
-
1101
- def parse_range (item, min, max)
1102
-
1103
- i = item.index("-")
1104
- j = item.index("/")
1105
-
1106
- inc = 1
1107
-
1108
- inc = Integer(item[j+1..-1]) if j
1109
-
1110
- istart = -1
1111
- iend = -1
1112
-
1113
- if i
1114
-
1115
- istart = Integer(item[0..i-1])
1116
-
1117
- if j
1118
- iend = Integer(item[i+1..j])
1119
- else
1120
- iend = Integer(item[i+1..-1])
1121
- end
1122
-
1123
- else # case */x
1124
-
1125
- istart = min
1126
- iend = max
1127
- end
1128
-
1129
- istart = min if istart < min
1130
- iend = max if iend > max
1131
-
1132
- result = []
1133
-
1134
- value = istart
1135
- while true
1136
-
1137
- result << value
1138
- value = value + inc
1139
- break if value > iend
1140
- end
1141
-
1142
- result
1143
- end
1144
-
1145
- def no_match? (value, cron_values)
1146
-
1147
- return false if not cron_values
1148
-
1149
- cron_values.each do |v|
1150
- return false if value == v
1151
- end
1152
-
1153
- true
1154
- end
1155
- end
1156
-
1157
- end
1158
-