ruote 2.1.9 → 2.1.10

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 (81) hide show
  1. data/CHANGELOG.txt +32 -0
  2. data/CREDITS.txt +3 -0
  3. data/Rakefile +4 -4
  4. data/TODO.txt +55 -11
  5. data/examples/barley.rb +2 -1
  6. data/examples/flickr_report.rb +5 -6
  7. data/examples/web_first_page.rb +11 -0
  8. data/lib/ruote/context.rb +36 -13
  9. data/lib/ruote/engine.rb +88 -56
  10. data/lib/ruote/engine/process_error.rb +13 -0
  11. data/lib/ruote/engine/process_status.rb +33 -1
  12. data/lib/ruote/error_handler.rb +122 -0
  13. data/lib/ruote/evt/tracker.rb +27 -10
  14. data/lib/ruote/exp/fe_apply.rb +69 -0
  15. data/lib/ruote/exp/fe_participant.rb +33 -5
  16. data/lib/ruote/exp/flowexpression.rb +37 -5
  17. data/lib/ruote/exp/ro_persist.rb +8 -4
  18. data/lib/ruote/exp/ro_variables.rb +2 -2
  19. data/lib/ruote/fei.rb +59 -7
  20. data/lib/ruote/log/storage_history.rb +2 -0
  21. data/lib/ruote/log/test_logger.rb +28 -19
  22. data/lib/ruote/log/wait_logger.rb +4 -2
  23. data/lib/ruote/parser.rb +2 -1
  24. data/lib/ruote/part/dispatch_pool.rb +10 -10
  25. data/lib/ruote/part/engine_participant.rb +2 -2
  26. data/lib/ruote/part/local_participant.rb +99 -7
  27. data/lib/ruote/part/participant_list.rb +18 -7
  28. data/lib/ruote/part/storage_participant.rb +9 -6
  29. data/lib/ruote/receiver/base.rb +109 -10
  30. data/lib/ruote/storage/base.rb +118 -41
  31. data/lib/ruote/storage/fs_storage.rb +1 -0
  32. data/lib/ruote/storage/hash_storage.rb +2 -1
  33. data/lib/ruote/util/lookup.rb +22 -2
  34. data/lib/ruote/util/misc.rb +5 -5
  35. data/lib/ruote/version.rb +1 -1
  36. data/lib/ruote/worker.rb +50 -63
  37. data/lib/ruote/workitem.rb +64 -0
  38. data/ruote.gemspec +17 -12
  39. data/test/functional/base.rb +3 -1
  40. data/test/functional/concurrent_base.rb +35 -29
  41. data/test/functional/crunner.sh +19 -0
  42. data/test/functional/ct_0_concurrence.rb +17 -30
  43. data/test/functional/ct_1_iterator.rb +20 -17
  44. data/test/functional/ct_2_cancel.rb +32 -25
  45. data/test/functional/eft_12_listen.rb +2 -1
  46. data/test/functional/eft_23_apply.rb +23 -0
  47. data/test/functional/eft_3_participant.rb +27 -0
  48. data/test/functional/ft_11_recursion.rb +1 -1
  49. data/test/functional/ft_13_variables.rb +22 -0
  50. data/test/functional/ft_14_re_apply.rb +3 -0
  51. data/test/functional/ft_15_timeout.rb +1 -0
  52. data/test/functional/ft_20_storage_participant.rb +20 -2
  53. data/test/functional/ft_21_forget.rb +30 -0
  54. data/test/functional/ft_22_process_definitions.rb +2 -1
  55. data/test/functional/ft_24_block_participants.rb +1 -1
  56. data/test/functional/ft_25_receiver.rb +83 -1
  57. data/test/functional/ft_26_participant_timeout.rb +1 -1
  58. data/test/functional/ft_2_errors.rb +2 -5
  59. data/test/functional/ft_30_smtp_participant.rb +47 -45
  60. data/test/functional/ft_36_storage_history.rb +4 -4
  61. data/test/functional/ft_37_engine_participant.rb +14 -10
  62. data/test/functional/ft_38_participant_more.rb +178 -0
  63. data/test/functional/ft_39_wait_for.rb +100 -0
  64. data/test/functional/ft_40_participant_on_reply.rb +87 -0
  65. data/test/functional/ft_41_participants.rb +65 -0
  66. data/test/functional/ft_42_storage_copy.rb +67 -0
  67. data/test/functional/ft_5_on_error.rb +103 -0
  68. data/test/functional/ft_9_subprocesses.rb +2 -1
  69. data/test/functional/storage_helper.rb +5 -1
  70. data/test/functional/test.rb +4 -1
  71. data/test/functional/vertical.rb +46 -0
  72. data/test/unit/storage.rb +17 -1
  73. data/test/unit/storages.rb +27 -7
  74. data/test/unit/ut_11_lookup.rb +36 -0
  75. data/test/unit/ut_16_parser.rb +43 -0
  76. data/test/unit/ut_1_fei.rb +28 -1
  77. data/test/unit/ut_7_workitem.rb +23 -0
  78. metadata +67 -105
  79. data/lib/ruote/log/fs_history.rb +0 -182
  80. data/test/functional/ft_32_fs_history.rb +0 -188
  81. data/test/mpc_test.rb +0 -29
@@ -69,6 +69,19 @@ module Ruote
69
69
  @h
70
70
  end
71
71
 
72
+ # 'apply', 'reply', 'receive', ... Indicates in which "direction" the
73
+ # error occured.
74
+ #
75
+ def action
76
+ @h['msg']['action']
77
+ end
78
+
79
+ # Exposes the workitem fields directly.
80
+ #
81
+ def fields
82
+ @h['msg']['workitem']['fields']
83
+ end
84
+
72
85
  protected
73
86
 
74
87
  def to_dot (opts)
@@ -58,7 +58,39 @@ module Ruote
58
58
  #
59
59
  def root_expression
60
60
 
61
- @expressions.find { |e| e.fei.expid == '0' && e.fei.sub_wfid == nil }
61
+ #@expressions.find { |e| e.fei.expid == '0' && e.fei.sub_wfid == nil }
62
+ # vanilla implementation
63
+
64
+ root_expressions.first
65
+ end
66
+
67
+ # Returns a list of all the expressions that have no parent expression.
68
+ # The list is sorted with the deeper (closer to the original root) first.
69
+ #
70
+ def root_expressions
71
+
72
+ roots = @expressions.select { |e| e.h.parent_id == nil }
73
+
74
+ roots = roots.inject({}) { |h, e|
75
+ h["#{e.h.fei['expid']}__#{e.h.fei['sub_wfid']}"] = e; h
76
+ }
77
+
78
+ roots.keys.sort.collect { |k| roots[k] }
79
+ end
80
+
81
+ # Given an expression id, returns the root (top ancestor) for its
82
+ # expression.
83
+ #
84
+ def root_expression_for (fei)
85
+
86
+ sfei = Ruote.sid(fei)
87
+
88
+ exp = @expressions.find { |fe| sfei == Ruote.sid(fe.fei) }
89
+
90
+ return nil unless exp
91
+ return exp if exp.parent_id.nil?
92
+
93
+ root_expression_for(exp.parent_id)
62
94
  end
63
95
 
64
96
  # Returns the process variables set for this process instance.
@@ -0,0 +1,122 @@
1
+ #--
2
+ # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote
27
+
28
+ #
29
+ # A ruote service for turning exceptions into process errors (or letting
30
+ # those error fire any potential :on_error attributes in the process
31
+ # definition).
32
+ #
33
+ # This service is used, by the worker, the dispatch pool and some
34
+ # receivers (like the one in ruote-beanstalk).
35
+ #
36
+ class ErrorHandler
37
+
38
+ def initialize (context)
39
+
40
+ @context = context
41
+ end
42
+
43
+ # As used by the dispatch pool and the worker.
44
+ #
45
+ def msg_handle (msg, exception)
46
+
47
+ fexp = Ruote::Exp::FlowExpression.fetch(
48
+ @context, msg['fei'] || msg['workitem']['fei'])
49
+
50
+ handle(msg, fexp, exception)
51
+ end
52
+
53
+ # As used by some receivers (see ruote-beanstalk's receiver).
54
+ #
55
+ def action_handle (action, fei, exception)
56
+
57
+ fexp = Ruote::Exp::FlowExpression.fetch(@context, fei)
58
+
59
+ msg = {
60
+ 'action' => action,
61
+ 'fei' => fei,
62
+ 'participant_name' => fexp.h.participant_name,
63
+ 'workitem' => fexp.h.applied_workitem }
64
+
65
+ handle(msg, fexp, exception)
66
+ end
67
+
68
+ protected
69
+
70
+ # As used by the worker.
71
+ #
72
+ def handle (msg, fexp, exception)
73
+
74
+ wfid = msg['wfid'] || (msg['fei']['wfid'] rescue nil)
75
+ fei = msg['fei'] || (fexp.h.fei rescue nil)
76
+
77
+ backtrace = exception.backtrace || []
78
+
79
+ # debug only
80
+
81
+ if $DEBUG || ARGV.include?('-d')
82
+
83
+ puts "\n== worker intercepted error =="
84
+ puts
85
+ p exception
86
+ puts backtrace[0, 20].join("\n")
87
+ puts "..."
88
+ puts
89
+ puts "-- msg --"
90
+ msg.keys.sort.each { |k|
91
+ puts " #{k.inspect} =>\n#{msg[k].inspect}"
92
+ }
93
+ puts "-- . --"
94
+ puts
95
+ end
96
+
97
+ # on_error ?
98
+
99
+ return if fexp && fexp.handle_on_error(msg, exception)
100
+
101
+ # emit 'msg'
102
+
103
+ @context.storage.put_msg(
104
+ 'error_intercepted',
105
+ 'message' => exception.inspect,
106
+ 'wfid' => wfid,
107
+ 'msg' => msg)
108
+
109
+ # fill error in the error journal
110
+
111
+ @context.storage.put(
112
+ 'type' => 'errors',
113
+ '_id' => "err_#{Ruote.to_storage_id(fei)}",
114
+ 'message' => exception.inspect,
115
+ 'trace' => backtrace.join("\n"),
116
+ 'fei' => fei,
117
+ 'msg' => msg
118
+ ) if fei
119
+ end
120
+ end
121
+ end
122
+
@@ -25,6 +25,13 @@
25
25
 
26
26
  module Ruote
27
27
 
28
+ #
29
+ # The tracker service is used by the "listen" expression. This services
30
+ # sees all the msg processed by a worker and triggers any
31
+ # listener interested in a particular msg.
32
+ #
33
+ # Look at the ListenExpression for more details.
34
+ #
28
35
  class Tracker
29
36
 
30
37
  def initialize (context)
@@ -44,6 +51,9 @@ module Ruote
44
51
  end
45
52
  end
46
53
 
54
+ # The worker passes all the messages it has to process to the tracker via
55
+ # this method.
56
+ #
47
57
  def notify (msg)
48
58
 
49
59
  doc = @context.storage.get_trackers
@@ -67,16 +77,8 @@ module Ruote
67
77
  end
68
78
  end
69
79
 
70
- def does_match? (msg, conditions)
71
-
72
- conditions.each do |k, v|
73
- val = msg[k]
74
- return false unless val && val.match(v)
75
- end
76
-
77
- true
78
- end
79
-
80
+ # Adds a tracker (usually when a 'listen' expression gets applied).
81
+ #
80
82
  def add_tracker (wfid, action, fei, conditions, msg, doc=nil)
81
83
 
82
84
  doc ||= @context.storage.get_trackers
@@ -94,6 +96,9 @@ module Ruote
94
96
  # the put failed, have to redo the work
95
97
  end
96
98
 
99
+ # Removes a tracker (usually when a 'listen' expression replies to its
100
+ # parent expression or is cancelled).
101
+ #
97
102
  def remove_tracker (fei, doc=nil)
98
103
 
99
104
  doc ||= @context.storage.get_trackers
@@ -105,6 +110,18 @@ module Ruote
105
110
  remove_tracker(fei, r) if r
106
111
  # the put failed, have to redo the work
107
112
  end
113
+
114
+ protected
115
+
116
+ def does_match? (msg, conditions)
117
+
118
+ conditions.each do |k, v|
119
+ val = msg[k]
120
+ return false unless val && val.match(v)
121
+ end
122
+
123
+ true
124
+ end
108
125
  end
109
126
  end
110
127
 
@@ -55,6 +55,73 @@ module Ruote::Exp
55
55
  # echo 'nada'
56
56
  #
57
57
  #
58
+ # == apply and subprocesses
59
+ #
60
+ # There is an interesting way of using 'apply', it's close to the way of
61
+ # the Ruby "yield" expression.
62
+ #
63
+ # pdef = Ruote.process_definition 'test' do
64
+ # sequence do
65
+ # handle do
66
+ # participant 'alpha'
67
+ # end
68
+ # handle do
69
+ # participant 'bravo'
70
+ # end
71
+ # end
72
+ # define 'handle' do
73
+ # sequence do
74
+ # participant 'prepare_data'
75
+ # apply
76
+ # participant 'rearrange_data'
77
+ # end
78
+ # end
79
+ # end
80
+ #
81
+ # With this process definition, the particpant alpha and bravo are handed
82
+ # a workitem in sequence, but each time, the data gets prepared and
83
+ # re-arranged.
84
+ #
85
+ # 'apply' simply picks the value of the tree to apply in the local variable
86
+ # 'tree'.
87
+ #
88
+ # Passing variables to applied trees is possible :
89
+ #
90
+ # pdef = Ruote.process_definition do
91
+ # handle do
92
+ # participant '${v:target}', :message => 'x'
93
+ # end
94
+ # define 'handle' do
95
+ # sequence do
96
+ # participant 'prepare_data'
97
+ # apply :v => 'alpha'
98
+ # apply :v => 'bravo'
99
+ # participant 'rearrange_data'
100
+ # end
101
+ # end
102
+ # end
103
+ #
104
+ #
105
+ # == on_error
106
+ #
107
+ # It's OK, to place an on_error on the apply
108
+ #
109
+ # pdef = Ruote.process_definition do
110
+ # handle do
111
+ # sequence do
112
+ # echo 'in'
113
+ # nemo
114
+ # end
115
+ # end
116
+ # define 'handle' do
117
+ # apply :on_error => 'notify' # <==
118
+ # echo 'over.'
119
+ # end
120
+ # define 'notify' do
121
+ # echo 'error'
122
+ # end
123
+ # end
124
+ #
58
125
  class ApplyExpression < FlowExpression
59
126
 
60
127
  names :apply
@@ -62,6 +129,8 @@ module Ruote::Exp
62
129
  # TODO : maybe accept directly ruby and xml (and json)
63
130
  # TODO : _yield ?
64
131
 
132
+ # TODO : apply [ 'echo', { 'nada' => nil }, [] ]
133
+
65
134
  def apply
66
135
 
67
136
  #
@@ -23,7 +23,6 @@
23
23
  #++
24
24
 
25
25
 
26
- require 'ruote/workitem'
27
26
  require 'ruote/exp/condition'
28
27
 
29
28
 
@@ -120,6 +119,10 @@ module Ruote::Exp
120
119
 
121
120
  names :participant
122
121
 
122
+ # Should return true when the dispatch was successful.
123
+ #
124
+ h_reader :dispatched
125
+
123
126
  def apply
124
127
 
125
128
  #
@@ -157,17 +160,18 @@ module Ruote::Exp
157
160
  'fei' => h.fei,
158
161
  'participant_name' => h.participant_name,
159
162
  'workitem' => h.applied_workitem,
160
- 'for_engine_worker?' => participant_info.class != Array)
161
- #
162
- # NOTE : is this for_engine_worker? still necessary ?
163
+ 'for_engine_worker?' => (participant_info.class != Array))
163
164
  end
164
165
 
165
166
  def cancel (flavour)
166
167
 
168
+ return reply_to_parent(h.applied_workitem) unless h.participant_name
169
+ # no participant, reply immediately
170
+
167
171
  do_persist || return
168
172
  #
169
173
  # if do_persist returns false, it means we're operating on stale
170
- # data and have thus to cease
174
+ # data and cannot continue
171
175
 
172
176
  @context.storage.put_msg(
173
177
  'dispatch_cancel',
@@ -177,6 +181,16 @@ module Ruote::Exp
177
181
  'workitem' => h.applied_workitem)
178
182
  end
179
183
 
184
+ def reply (workitem)
185
+
186
+ pa = @context.plist.lookup(
187
+ workitem['participant_name'], :on_reply => true)
188
+
189
+ pa.on_reply(Ruote::Workitem.new(workitem)) if pa
190
+
191
+ super(workitem)
192
+ end
193
+
180
194
  def reply_to_parent (workitem)
181
195
 
182
196
  workitem['fields'].delete('params')
@@ -186,6 +200,20 @@ module Ruote::Exp
186
200
 
187
201
  protected
188
202
 
203
+ # Once the dispatching work (done by the dispatch pool) is done, a
204
+ # 'dispatched' msg is sent, we have to flag the participant expression
205
+ # as 'dispatched' => true
206
+ #
207
+ # See http://groups.google.com/group/openwferu-users/browse_thread/thread/ff29f26d6b5fd135
208
+ # for the motivation.
209
+ #
210
+ def do_dispatched (msg)
211
+
212
+ h.dispatched = true
213
+ do_persist
214
+ # let's not care if it fails...
215
+ end
216
+
189
217
  # Overriden with an empty behaviour. The work is now done a bit later
190
218
  # via the #schedule_timeout method.
191
219
  #
@@ -31,7 +31,27 @@ require 'ruote/util/hashdot'
31
31
  module Ruote::Exp
32
32
 
33
33
  #
34
- # TODO : document me
34
+ # Ruote is a process definition interpreter. It doesn't directly "read"
35
+ # process definitions, it relies on a parser/generator to produce "abstract
36
+ # syntax trees" that look like
37
+ #
38
+ # [ expression_name, { ... attributes ... }, [ children_expressions ] ]
39
+ #
40
+ # The nodes (and leaves) in the trees are expressions. This is the base
41
+ # class for all expressions.
42
+ #
43
+ # The most visible expressions are "define", "sequence" and "participant".
44
+ # Think :
45
+ #
46
+ # pdef = Ruote.process_definition do
47
+ # sequence do
48
+ # participant :ref => 'customer'
49
+ # participant :ref => 'accounting'
50
+ # participant :ref => 'logistics'
51
+ # end
52
+ # end
53
+ #
54
+ # Each node is an expression...
35
55
  #
36
56
  class FlowExpression
37
57
 
@@ -390,9 +410,14 @@ module Ruote::Exp
390
410
  def do_fail (msg)
391
411
 
392
412
  @h['state'] = 'failing'
393
- persist_or_raise
413
+ @h['applied_workitem'] = msg['workitem']
394
414
 
395
- h.children.each { |i| @context.storage.put_msg('cancel', 'fei' => i) }
415
+ if h.children.size < 1
416
+ reply_to_parent(@h['applied_workitem'])
417
+ else
418
+ persist_or_raise
419
+ h.children.each { |i| @context.storage.put_msg('cancel', 'fei' => i) }
420
+ end
396
421
  end
397
422
 
398
423
  #--
@@ -449,6 +474,7 @@ module Ruote::Exp
449
474
  elsif h.parent_id
450
475
 
451
476
  par = parent
477
+ # :( get_parent would probably be a better name for #parent
452
478
 
453
479
  unless par
454
480
  puts "~~"
@@ -469,7 +495,7 @@ module Ruote::Exp
469
495
 
470
496
  # Looks up parent with on_error attribute and triggers it
471
497
  #
472
- def handle_on_error
498
+ def handle_on_error (msg, error)
473
499
 
474
500
  return false if h.state == 'failing'
475
501
 
@@ -483,10 +509,15 @@ module Ruote::Exp
483
509
  return false if handler == ''
484
510
  # empty on_error handler nullifies ancestor's on_error
485
511
 
512
+ workitem = msg['workitem']
513
+
514
+ workitem['fields']['__error__'] = [
515
+ h.fei, Ruote.now_to_utc_s, error.class.to_s, error.message ]
516
+
486
517
  @context.storage.put_msg(
487
518
  'fail',
488
519
  'fei' => oe_parent.h.fei,
489
- 'error_fei' => h.fei)
520
+ 'workitem' => workitem)
490
521
 
491
522
  true # yes, error is being handled.
492
523
  end
@@ -685,6 +716,7 @@ module Ruote::Exp
685
716
  def trigger (on, workitem)
686
717
 
687
718
  hon = h[on]
719
+
688
720
  t = hon.is_a?(String) ? [ hon, {}, [] ] : hon
689
721
 
690
722
  if on == 'on_error'