ruote 2.1.10 → 2.1.11

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.
Files changed (94) hide show
  1. data/CHANGELOG.txt +51 -1
  2. data/CREDITS.txt +9 -0
  3. data/README.rdoc +13 -0
  4. data/Rakefile +50 -21
  5. data/TODO.txt +42 -4
  6. data/examples/pong.rb +37 -0
  7. data/lib/ruote/context.rb +19 -9
  8. data/lib/ruote/engine/process_error.rb +10 -0
  9. data/lib/ruote/engine/process_status.rb +140 -41
  10. data/lib/ruote/engine.rb +394 -27
  11. data/lib/ruote/exp/command.rb +2 -0
  12. data/lib/ruote/exp/fe_concurrence.rb +8 -0
  13. data/lib/ruote/exp/fe_concurrent_iterator.rb +3 -0
  14. data/lib/ruote/exp/fe_cursor.rb +48 -4
  15. data/lib/ruote/exp/fe_iterator.rb +40 -0
  16. data/lib/ruote/exp/fe_listen.rb +3 -3
  17. data/lib/ruote/exp/fe_participant.rb +30 -12
  18. data/lib/ruote/exp/fe_ref.rb +126 -0
  19. data/lib/ruote/exp/fe_subprocess.rb +20 -1
  20. data/lib/ruote/exp/fe_wait.rb +4 -1
  21. data/lib/ruote/exp/fe_when.rb +7 -10
  22. data/lib/ruote/exp/flowexpression.rb +23 -12
  23. data/lib/ruote/exp/ro_attributes.rb +5 -8
  24. data/lib/ruote/exp/ro_variables.rb +4 -2
  25. data/lib/ruote/fei.rb +2 -0
  26. data/lib/ruote/id/wfid_generator.rb +1 -1
  27. data/lib/ruote/log/pretty.rb +137 -0
  28. data/lib/ruote/log/storage_history.rb +1 -1
  29. data/lib/ruote/log/test_logger.rb +51 -126
  30. data/lib/ruote/log/wait_logger.rb +8 -13
  31. data/lib/ruote/parser/ruby_dsl.rb +4 -4
  32. data/lib/ruote/parser.rb +2 -2
  33. data/lib/ruote/part/block_participant.rb +1 -1
  34. data/lib/ruote/part/engine_participant.rb +1 -1
  35. data/lib/ruote/part/storage_participant.rb +27 -28
  36. data/lib/ruote/part/template.rb +8 -3
  37. data/lib/ruote/receiver/base.rb +24 -6
  38. data/lib/ruote/storage/base.rb +76 -11
  39. data/lib/ruote/storage/fs_storage.rb +10 -0
  40. data/lib/ruote/storage/hash_storage.rb +19 -8
  41. data/lib/ruote/{part → svc}/dispatch_pool.rb +3 -2
  42. data/lib/ruote/svc/dollar_sub.rb +265 -0
  43. data/lib/ruote/{error_handler.rb → svc/error_handler.rb} +6 -1
  44. data/lib/ruote/{exp → svc}/expression_map.rb +31 -37
  45. data/lib/ruote/{part → svc}/participant_list.rb +165 -25
  46. data/lib/ruote/{evt → svc}/tracker.rb +0 -0
  47. data/lib/ruote/{util → svc}/treechecker.rb +0 -0
  48. data/lib/ruote/util/look.rb +4 -1
  49. data/lib/ruote/util/ometa.rb +21 -5
  50. data/lib/ruote/{subprocess.rb → util/subprocess.rb} +0 -0
  51. data/lib/ruote/version.rb +1 -1
  52. data/lib/ruote/worker.rb +29 -69
  53. data/lib/ruote/workitem.rb +28 -1
  54. data/ruote.gemspec +26 -22
  55. data/test/functional/base.rb +3 -0
  56. data/test/functional/concurrent_base.rb +1 -0
  57. data/test/functional/crunner.sh +1 -1
  58. data/test/functional/ct_0_concurrence.rb +6 -0
  59. data/test/functional/ct_1_iterator.rb +3 -0
  60. data/test/functional/ct_2_cancel.rb +5 -0
  61. data/test/functional/eft_13_iterator.rb +39 -4
  62. data/test/functional/eft_14_cursor.rb +39 -0
  63. data/test/functional/eft_30_ref.rb +140 -0
  64. data/test/functional/eft_3_participant.rb +25 -23
  65. data/test/functional/ft_10_dollar.rb +17 -1
  66. data/test/functional/ft_14_re_apply.rb +76 -0
  67. data/test/functional/ft_1_process_status.rb +170 -29
  68. data/test/functional/ft_20_storage_participant.rb +14 -0
  69. data/test/functional/ft_24_block_participants.rb +1 -1
  70. data/test/functional/ft_26_participant_timeout.rb +93 -0
  71. data/test/functional/ft_2_errors.rb +24 -17
  72. data/test/functional/ft_30_smtp_participant.rb +7 -2
  73. data/test/functional/ft_38_participant_more.rb +15 -0
  74. data/test/functional/ft_39_wait_for.rb +34 -1
  75. data/test/functional/ft_3_participant_registration.rb +270 -2
  76. data/test/functional/ft_40_wait_logger.rb +61 -0
  77. data/test/functional/ft_42_storage_copy.rb +4 -0
  78. data/test/functional/{ft_40_participant_on_reply.rb → ft_43_participant_on_reply.rb} +17 -0
  79. data/test/functional/ft_44_var_participant.rb +35 -0
  80. data/test/functional/ft_45_participant_accept.rb +64 -0
  81. data/test/functional/ft_46_launch_single.rb +49 -0
  82. data/test/functional/ft_5_on_error.rb +39 -1
  83. data/test/functional/storage_helper.rb +7 -1
  84. data/test/test_helper.rb +1 -1
  85. data/test/unit/storage.rb +105 -32
  86. data/test/unit/ut_0_ruby_parser.rb +31 -1
  87. data/test/unit/ut_16_parser.rb +20 -0
  88. data/test/unit/ut_19_part_template.rb +11 -1
  89. data/test/unit/ut_20_composite_storage.rb +1 -1
  90. data/test/unit/ut_4_expmap.rb +1 -1
  91. data/test/unit/ut_6_condition.rb +2 -2
  92. metadata +112 -74
  93. data/lib/ruote/exp/raw.rb +0 -44
  94. data/lib/ruote/util/dollar.rb +0 -193
@@ -31,6 +31,10 @@ module Ruote
31
31
  #
32
32
  # Tracking participants to [business] processes.
33
33
  #
34
+ # The methods here are mostly called via the engine (registering /
35
+ # unregistering participants) and via the dispatch_pool (when handing
36
+ # workitems to participants).
37
+ #
34
38
  class ParticipantList
35
39
 
36
40
  attr_reader :instantiated_participants
@@ -41,20 +45,16 @@ module Ruote
41
45
  @instantiated_participants = {}
42
46
  end
43
47
 
44
- # Registers the participant.
45
- #
46
- # Called by the register_participant method of the engine.
48
+ # Registers a participant. Called by Engine#register_participant.
47
49
  #
48
- def register (name, participant, options, block, list=nil)
49
-
50
- list ||= get_list
50
+ def register (name, participant, options, block)
51
51
 
52
52
  options = options.inject({}) { |h, (k, v)|
53
53
  h[k.to_s] = v.is_a?(Symbol) ? v.to_s : v
54
54
  h
55
55
  }
56
56
 
57
- entry = if participant.is_a?(Class)
57
+ entry = if participant.is_a?(Class) || participant.is_a?(String)
58
58
  [ participant.to_s, options ]
59
59
  else
60
60
  "inpa_#{name.inspect}"
@@ -65,6 +65,8 @@ module Ruote
65
65
 
66
66
  entry = [ key, entry ]
67
67
 
68
+ list = get_list
69
+
68
70
  list['list'].delete_if { |e| e.first == key }
69
71
 
70
72
  position = options['position'] || 'last'
@@ -104,12 +106,13 @@ module Ruote
104
106
  # Removes a participant, given via its name or directly from this
105
107
  # participant list.
106
108
  #
107
- def unregister (name_or_participant, list=nil)
108
-
109
- list ||= get_list
109
+ # Called usually by Engine#unregister_participant.
110
+ #
111
+ def unregister (name_or_participant)
110
112
 
111
113
  code = nil
112
114
  entry = nil
115
+ list = get_list
113
116
 
114
117
  if name_or_participant.is_a?(Symbol)
115
118
  name_or_participant = name_or_participant.to_s
@@ -151,35 +154,75 @@ module Ruote
151
154
  entry.first
152
155
  end
153
156
 
154
- def lookup_info (participant_name)
157
+ # Returns a participant instance, or nil if there is no participant
158
+ # for the given participant name.
159
+ #
160
+ # Mostly a combination of #lookup_info and #instantiate.
161
+ #
162
+ def lookup (participant_name, workitem, opts={})
155
163
 
156
- re, pa = get_list['list'].find { |rr, pp| participant_name.match(rr) }
164
+ pinfo = participant_name
157
165
 
158
- case pa
159
- when nil then nil
160
- when String then @instantiated_participants[pa]
161
- else pa
166
+ if participant_name.is_a?(String) && participant_name[0, 5] != 'inpa_'
167
+ pinfo = lookup_info(participant_name, workitem)
162
168
  end
169
+
170
+ pinfo ? instantiate(pinfo, opts) : nil
163
171
  end
164
172
 
165
- def lookup (participant_name, opts={})
173
+ # Given a participant name, returns
174
+ #
175
+ # Returns nil if there is no participant registered that covers the given
176
+ # participant name.
177
+ #
178
+ def lookup_info (pname, workitem)
179
+
180
+ get_list['list'].each do |regex, pinfo|
166
181
 
167
- pi = lookup_info(participant_name)
182
+ next unless pname.match(regex)
168
183
 
169
- return nil unless pi
170
- return opts[:on_reply] ? nil : pi unless pi.is_a?(Array)
184
+ pa = instantiate(pinfo, :if_respond_to? => :accept?)
171
185
 
172
- class_name, options = pi
186
+ return pinfo unless pa
187
+
188
+ return pinfo if pa.accept?(
189
+ Ruote::Workitem.new(workitem.merge('participant_name' => pname))
190
+ )
191
+ end
192
+
193
+ nil
194
+ end
195
+
196
+ # Returns an instance of a participant
197
+ #
198
+ def instantiate (pinfo, opts={})
199
+
200
+ irt = opts[:if_respond_to?]
201
+
202
+ pinfo = @instantiated_participants[pinfo] if pinfo.is_a?(String)
203
+
204
+ if pinfo.respond_to?(:consume)
205
+ return (pinfo.respond_to?(irt) ? pinfo : nil) if irt
206
+ return pinfo
207
+ end
208
+
209
+ return nil unless pinfo
210
+
211
+ pa_class_name, options = pinfo
173
212
 
174
213
  if rp = options['require_path']
175
214
  require(rp)
176
215
  end
216
+ if lp = options['load_path']
217
+ load(lp)
218
+ end
177
219
 
178
- pa_class = Ruote.constantize(class_name)
220
+ pa_class = Ruote.constantize(pa_class_name)
179
221
  pa_m = pa_class.instance_methods
180
222
 
181
- return nil if opts[:on_reply] && ! (
182
- pa_m.include?(:on_reply) || pa_m.include?('on_reply'))
223
+ if irt && ! (pa_m.include?(irt.to_s) || pa_m.include?(irt.to_sym))
224
+ return nil
225
+ end
183
226
 
184
227
  pa = if pa_class.instance_method(:initialize).arity > 0
185
228
  pa_class.new(options)
@@ -195,7 +238,7 @@ module Ruote
195
238
  #
196
239
  def names
197
240
 
198
- get_list['list'].map { |re, pa| re }
241
+ get_list['list'].collect { |re, pa| re }
199
242
  end
200
243
 
201
244
  # Shuts down the 'instantiated participants' (engine worker participants)
@@ -208,8 +251,40 @@ module Ruote
208
251
  }
209
252
  end
210
253
 
254
+ # Used by Engine#participant_list
255
+ #
256
+ # Returns a representation of this participant list as an array of
257
+ # ParticipantEntry instances.
258
+ #
259
+ def list
260
+
261
+ get_list['list'].collect { |e| ParticipantEntry.new(e) }
262
+ end
263
+
264
+ # Used by Engine#participant_list=
265
+ #
266
+ # Takes as input an array of ParticipantEntry instances and updates
267
+ # this participant list with it.
268
+ #
269
+ # See ParticipantList#list
270
+ #
271
+ def list= (pl)
272
+
273
+ list = get_list
274
+ list['list'] = pl.collect { |e| ParticipantEntry.read(e) }
275
+
276
+ if r = @context.storage.put(list)
277
+ #
278
+ # put failed, have to redo it
279
+ #
280
+ list= (pl)
281
+ end
282
+ end
283
+
211
284
  protected
212
285
 
286
+ # Fetches and returns the participant list in the storage.
287
+ #
213
288
  def get_list
214
289
 
215
290
  @context.storage.get_configuration('participant_list') ||
@@ -217,6 +292,71 @@ module Ruote
217
292
  '_id' => 'participant_list',
218
293
  'list' => [] }
219
294
  end
295
+
296
+ #--
297
+ # Returns an array of all the classes in the ObjectSpace that include the
298
+ # Ruote::LocalParticipant module.
299
+ #
300
+ #def local_participant_classes
301
+ # ObjectSpace.each_object(Class).inject([]) { |a, c|
302
+ # a << c if c.include?(Ruote::LocalParticipant)
303
+ # a
304
+ # }
305
+ #end
306
+ #++
307
+ end
308
+
309
+ #
310
+ # A helper class, for ParticipantList#list, which returns a list (order
311
+ # matters) of ParticipantEntry instances.
312
+ #
313
+ # See Engine#participant_list
314
+ #
315
+ class ParticipantEntry
316
+
317
+ attr_accessor :regex, :classname, :options
318
+
319
+ def initialize (a)
320
+ @regex = a.first
321
+ if a.last.is_a?(Array)
322
+ @classname = a.last.first
323
+ @options = a.last.last
324
+ else
325
+ @classname = a.last
326
+ @options = nil
327
+ end
328
+ end
329
+
330
+ def to_a
331
+ @classname[0, 5] == 'inpa_' ?
332
+ [ @regex, @classname ] :
333
+ [ @regex, [ @classname, @options ] ]
334
+ end
335
+
336
+ def to_s
337
+ "/#{@regex}/ ==> #{@classname} #{@options.inspect}"
338
+ end
339
+
340
+ def self.read (elt)
341
+
342
+ if elt.is_a?(ParticipantEntry)
343
+ return elt.to_a
344
+ end
345
+
346
+ if elt.is_a?(Hash)
347
+ return elt['classname'][0, 5] == 'inpa_' ?
348
+ [ elt['regex'], elt['classname'] ] :
349
+ [ elt['regex'], [ elt['classname'], elt['options'] ] ]
350
+ end
351
+
352
+ # else elt is a Array
353
+
354
+ if elt.size == 3
355
+ return [ elt[0], [ elt[1], elt[2] ] ]
356
+ end
357
+
358
+ elt
359
+ end
220
360
  end
221
361
  end
222
362
 
File without changes
File without changes
@@ -37,8 +37,11 @@ module Ruote
37
37
 
38
38
  def self.dump_lsof
39
39
 
40
+ result = lsof
41
+
40
42
  puts '= lsof =' + '=' * 71
41
- puts lsof
43
+ puts result
44
+ puts result.split("\n").size
42
45
  puts '=' * 80
43
46
  end
44
47
 
@@ -29,9 +29,9 @@
29
29
 
30
30
  module Ruote
31
31
 
32
+ # meta a la lucky stiff
33
+ #
32
34
  module WithMeta
33
- #
34
- # meta a la lucky stiff
35
35
 
36
36
  def self.included(target)
37
37
 
@@ -40,16 +40,32 @@ module Ruote
40
40
  self
41
41
  end
42
42
  end
43
- def target.meta_eval (&block)
43
+ def target.meta_eval(&block)
44
44
  metaclass.instance_eval(&block)
45
45
  end
46
- def target.meta_def (method_name, &block)
46
+ def target.meta_def(method_name, &block)
47
47
  meta_eval { define_method method_name, &block }
48
48
  end
49
- def class_def (method_name, &block)
49
+ def class_def(method_name, &block)
50
50
  class_eval { define_method name, &block }
51
51
  end
52
52
  end
53
53
  end
54
+
55
+ # A blank slate of a class
56
+ #
57
+ class BlankSlate
58
+
59
+ instance_methods.each do |m|
60
+
61
+ next if %w[
62
+ method_missing respond_to? instance_eval object_id
63
+ ].include?(m.to_s)
64
+
65
+ next if m.to_s.match(/^__/)
66
+
67
+ undef_method(m)
68
+ end
69
+ end
54
70
  end
55
71
 
File without changes
data/lib/ruote/version.rb CHANGED
@@ -23,6 +23,6 @@
23
23
  #++
24
24
 
25
25
  module Ruote
26
- VERSION = '2.1.10'
26
+ VERSION = '2.1.11'
27
27
  end
28
28
 
data/lib/ruote/worker.rb CHANGED
@@ -39,6 +39,7 @@ module Ruote
39
39
  # 'receive' is a ParticipantExpression alias for 'reply'
40
40
 
41
41
  PROC_ACTIONS = %w[ cancel_process kill_process ]
42
+ DISP_ACTIONS = %w[ dispatch dispatch_cancel ]
42
43
 
43
44
  attr_reader :storage
44
45
  attr_reader :context
@@ -64,13 +65,16 @@ module Ruote
64
65
  @sleep_time = 0.000
65
66
  end
66
67
 
68
+ # Runs the worker in the current thread. See #run_in_thread for running
69
+ # in a dedicated thread.
70
+ #
67
71
  def run
68
72
 
69
- while(@running) do
70
- step
71
- end
73
+ step while @running
72
74
  end
73
75
 
76
+ # Triggers the run method of the worker in a dedicated thread.
77
+ #
74
78
  def run_in_thread
75
79
 
76
80
  Thread.abort_on_exception = true
@@ -81,6 +85,14 @@ module Ruote
81
85
  @run_thread = Thread.new { run }
82
86
  end
83
87
 
88
+ # Joins the run thread of this worker (if there is no such thread, this
89
+ # method will return immediately, without any effect).
90
+ #
91
+ def join
92
+
93
+ @run_thread.join if @run_thread
94
+ end
95
+
84
96
  def subscribe (actions, subscriber)
85
97
 
86
98
  @subscribers << [ actions, subscriber ]
@@ -101,10 +113,9 @@ module Ruote
101
113
  # Returns true if the engine system is inactive, ie if all the process
102
114
  # instances are terminated or are stuck in an error.
103
115
  #
104
- # NOTE : for now, if a branch of a process is in errors while another is
105
- # still running, this methods will still consider the process instance
106
- # as inactive (and it will return true if all the processes are considered
107
- # inactive).
116
+ # NOTE : for now, if a branch of a process is in error while another is
117
+ # still running, this method will consider the process instance inactive
118
+ # (and it will return true if all the processes are considered inactive).
108
119
  #
109
120
  def inactive?
110
121
 
@@ -116,11 +127,11 @@ module Ruote
116
127
 
117
128
  wfids = @context.storage.get_many('expressions').collect { |exp|
118
129
  exp['fei']['wfid']
119
- }.sort.uniq
130
+ }
120
131
 
121
132
  error_wfids = @context.storage.get_many('errors').collect { |err|
122
133
  err['fei']['wfid']
123
- }.sort.uniq
134
+ }
124
135
 
125
136
  (wfids - error_wfids == [])
126
137
  end
@@ -213,7 +224,7 @@ module Ruote
213
224
 
214
225
  Ruote::Exp::FlowExpression.do_action(@context, msg)
215
226
 
216
- elsif action.match(/^dispatch/)
227
+ elsif DISP_ACTIONS.include?(action)
217
228
 
218
229
  @context.dispatch_pool.handle(msg)
219
230
 
@@ -283,78 +294,27 @@ module Ruote
283
294
 
284
295
  if not exp_class
285
296
 
286
- exp_class, tree = lookup_subprocess_or_participant(exp_hash)
297
+ exp_class = Ruote::Exp::RefExpression
298
+
299
+ #elsif msg['action'] == 'launch' && exp_class == Ruote::Exp::DefineExpression
300
+ elsif is_launch?(msg, exp_class)
287
301
 
288
- elsif msg['action'] == 'launch' && exp_class == Ruote::Exp::DefineExpression
289
302
  def_name, tree = Ruote::Exp::DefineExpression.reorganize(tree)
290
303
  variables[def_name] = [ '0', tree ] if def_name
291
304
  exp_class = Ruote::Exp::SequenceExpression
292
305
  end
293
306
 
294
- if exp_class == Ruote::Exp::SubprocessExpression && tree[1]['engine']
295
- #
296
- # the subprocess has to be transformed into an EngineParticipant...
297
-
298
- exp_class = Ruote::Exp::ParticipantExpression
299
-
300
- atts = tree[1]
301
-
302
- if ref = atts.find { |k, v| v.nil? }
303
- ref = ref.first
304
- atts.delete(ref)
305
- end
306
-
307
- atts['pdef'] = atts['ref'] || ref
308
- atts['ref'] = atts.delete('engine')
309
- end
310
-
311
- raise_unknown_expression_error(exp_hash) unless exp_class
312
-
313
307
  exp = exp_class.new(@context, exp_hash.merge!('original_tree' => tree))
314
308
 
315
309
  exp.initial_persist
316
310
  exp.do_apply
317
311
  end
318
312
 
319
- def raise_unknown_expression_error (exp_hash)
320
-
321
- exp_hash['state'] = 'failed'
322
- #exp_hash['has_error'] = true
323
-
324
- Ruote::Exp::RawExpression.new(@context, exp_hash).persist_or_raise
325
- # undigested expression is stored
326
-
327
- raise "unknown expression '#{exp_hash['original_tree'].first}'"
328
- end
329
-
330
- def lookup_subprocess_or_participant (exp_hash)
331
-
332
- tree = exp_hash['original_tree']
333
-
334
- key, value = Ruote::Exp::FlowExpression.new(
335
- @context, exp_hash.merge('name' => 'temporary')
336
- ).iterative_var_lookup(tree[0])
337
-
338
- sub = value
339
- part = @context.plist.lookup_info(key)
313
+ def is_launch? (msg, exp_class)
340
314
 
341
- sub = key if (not sub) && (not part) && Ruote.is_uri?(key)
342
- # for when a variable points to the URI of a[n external] subprocess
343
-
344
- if sub or part
345
-
346
- tree[1]['ref'] = key
347
- tree[1]['original_ref'] = tree[0] if key != tree[0]
348
-
349
- if sub
350
- [ Ruote::Exp::SubprocessExpression, [ 'subprocess', *tree[1..2] ] ]
351
- else
352
- [ Ruote::Exp::ParticipantExpression, [ 'participant', *tree[1..2] ] ]
353
- end
354
- else
355
-
356
- [ nil, tree ]
357
- end
315
+ return false if exp_class != Ruote::Exp::DefineExpression
316
+ return true if msg['action'] == 'launch'
317
+ (msg['trigger'] == 'on_re_apply')
358
318
  end
359
319
 
360
320
  def cancel_process (msg)
@@ -144,7 +144,7 @@ module Ruote
144
144
 
145
145
  # When was this workitem dispatched ?
146
146
  #
147
- def dispatch_at
147
+ def dispatched_at
148
148
 
149
149
  fields['dispatched_at']
150
150
  end
@@ -234,6 +234,33 @@ module Ruote
234
234
 
235
235
  @h['fields']['params']
236
236
  end
237
+
238
+ # (advanced)
239
+ #
240
+ # Shortcut for wi.fields['__command__']
241
+ #
242
+ # __command__ is read by the 'cursor' and the 'iterator' expressions
243
+ # when a workitem reaches it (apply and reply).
244
+ #
245
+ def commmand
246
+
247
+ @h['fields']['__command__']
248
+ end
249
+
250
+ # (advanced)
251
+ #
252
+ # Shortcut for wi.fields['__command__'] = x
253
+ #
254
+ # __command__ is read by the 'cursor' and the 'iterator' expressions
255
+ # when a workitem reaches it (apply and reply).
256
+ #
257
+ def command= (com)
258
+
259
+ com = com.is_a?(Array) ? com : com.split(' ')
260
+ com[1] = com[1].to_i if com[1]
261
+
262
+ @h['fields']['__command__'] = com
263
+ end
237
264
  end
238
265
  end
239
266