ruote 2.1.10 → 2.1.11

Sign up to get free protection for your applications and to get access to all the features.
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