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.
- data/CHANGELOG.txt +32 -0
- data/CREDITS.txt +3 -0
- data/Rakefile +4 -4
- data/TODO.txt +55 -11
- data/examples/barley.rb +2 -1
- data/examples/flickr_report.rb +5 -6
- data/examples/web_first_page.rb +11 -0
- data/lib/ruote/context.rb +36 -13
- data/lib/ruote/engine.rb +88 -56
- data/lib/ruote/engine/process_error.rb +13 -0
- data/lib/ruote/engine/process_status.rb +33 -1
- data/lib/ruote/error_handler.rb +122 -0
- data/lib/ruote/evt/tracker.rb +27 -10
- data/lib/ruote/exp/fe_apply.rb +69 -0
- data/lib/ruote/exp/fe_participant.rb +33 -5
- data/lib/ruote/exp/flowexpression.rb +37 -5
- data/lib/ruote/exp/ro_persist.rb +8 -4
- data/lib/ruote/exp/ro_variables.rb +2 -2
- data/lib/ruote/fei.rb +59 -7
- data/lib/ruote/log/storage_history.rb +2 -0
- data/lib/ruote/log/test_logger.rb +28 -19
- data/lib/ruote/log/wait_logger.rb +4 -2
- data/lib/ruote/parser.rb +2 -1
- data/lib/ruote/part/dispatch_pool.rb +10 -10
- data/lib/ruote/part/engine_participant.rb +2 -2
- data/lib/ruote/part/local_participant.rb +99 -7
- data/lib/ruote/part/participant_list.rb +18 -7
- data/lib/ruote/part/storage_participant.rb +9 -6
- data/lib/ruote/receiver/base.rb +109 -10
- data/lib/ruote/storage/base.rb +118 -41
- data/lib/ruote/storage/fs_storage.rb +1 -0
- data/lib/ruote/storage/hash_storage.rb +2 -1
- data/lib/ruote/util/lookup.rb +22 -2
- data/lib/ruote/util/misc.rb +5 -5
- data/lib/ruote/version.rb +1 -1
- data/lib/ruote/worker.rb +50 -63
- data/lib/ruote/workitem.rb +64 -0
- data/ruote.gemspec +17 -12
- data/test/functional/base.rb +3 -1
- data/test/functional/concurrent_base.rb +35 -29
- data/test/functional/crunner.sh +19 -0
- data/test/functional/ct_0_concurrence.rb +17 -30
- data/test/functional/ct_1_iterator.rb +20 -17
- data/test/functional/ct_2_cancel.rb +32 -25
- data/test/functional/eft_12_listen.rb +2 -1
- data/test/functional/eft_23_apply.rb +23 -0
- data/test/functional/eft_3_participant.rb +27 -0
- data/test/functional/ft_11_recursion.rb +1 -1
- data/test/functional/ft_13_variables.rb +22 -0
- data/test/functional/ft_14_re_apply.rb +3 -0
- data/test/functional/ft_15_timeout.rb +1 -0
- data/test/functional/ft_20_storage_participant.rb +20 -2
- data/test/functional/ft_21_forget.rb +30 -0
- data/test/functional/ft_22_process_definitions.rb +2 -1
- data/test/functional/ft_24_block_participants.rb +1 -1
- data/test/functional/ft_25_receiver.rb +83 -1
- data/test/functional/ft_26_participant_timeout.rb +1 -1
- data/test/functional/ft_2_errors.rb +2 -5
- data/test/functional/ft_30_smtp_participant.rb +47 -45
- data/test/functional/ft_36_storage_history.rb +4 -4
- data/test/functional/ft_37_engine_participant.rb +14 -10
- data/test/functional/ft_38_participant_more.rb +178 -0
- data/test/functional/ft_39_wait_for.rb +100 -0
- data/test/functional/ft_40_participant_on_reply.rb +87 -0
- data/test/functional/ft_41_participants.rb +65 -0
- data/test/functional/ft_42_storage_copy.rb +67 -0
- data/test/functional/ft_5_on_error.rb +103 -0
- data/test/functional/ft_9_subprocesses.rb +2 -1
- data/test/functional/storage_helper.rb +5 -1
- data/test/functional/test.rb +4 -1
- data/test/functional/vertical.rb +46 -0
- data/test/unit/storage.rb +17 -1
- data/test/unit/storages.rb +27 -7
- data/test/unit/ut_11_lookup.rb +36 -0
- data/test/unit/ut_16_parser.rb +43 -0
- data/test/unit/ut_1_fei.rb +28 -1
- data/test/unit/ut_7_workitem.rb +23 -0
- metadata +67 -105
- data/lib/ruote/log/fs_history.rb +0 -182
- data/test/functional/ft_32_fs_history.rb +0 -188
- data/test/mpc_test.rb +0 -29
@@ -93,8 +93,9 @@ module Ruote
|
|
93
93
|
|
94
94
|
else
|
95
95
|
|
96
|
-
|
97
|
-
|
96
|
+
if entry.last.first == 'Ruote::StorageParticipant'
|
97
|
+
return Ruote::StorageParticipant.new(@context)
|
98
|
+
end
|
98
99
|
|
99
100
|
nil
|
100
101
|
end
|
@@ -110,8 +111,9 @@ module Ruote
|
|
110
111
|
code = nil
|
111
112
|
entry = nil
|
112
113
|
|
113
|
-
|
114
|
-
|
114
|
+
if name_or_participant.is_a?(Symbol)
|
115
|
+
name_or_participant = name_or_participant.to_s
|
116
|
+
end
|
115
117
|
|
116
118
|
if name_or_participant.is_a?(String)
|
117
119
|
|
@@ -160,12 +162,12 @@ module Ruote
|
|
160
162
|
end
|
161
163
|
end
|
162
164
|
|
163
|
-
def lookup (participant_name)
|
165
|
+
def lookup (participant_name, opts={})
|
164
166
|
|
165
167
|
pi = lookup_info(participant_name)
|
166
168
|
|
167
169
|
return nil unless pi
|
168
|
-
return pi unless pi.is_a?(Array)
|
170
|
+
return opts[:on_reply] ? nil : pi unless pi.is_a?(Array)
|
169
171
|
|
170
172
|
class_name, options = pi
|
171
173
|
|
@@ -173,8 +175,17 @@ module Ruote
|
|
173
175
|
require(rp)
|
174
176
|
end
|
175
177
|
|
176
|
-
|
178
|
+
pa_class = Ruote.constantize(class_name)
|
179
|
+
pa_m = pa_class.instance_methods
|
180
|
+
|
181
|
+
return nil if opts[:on_reply] && ! (
|
182
|
+
pa_m.include?(:on_reply) || pa_m.include?('on_reply'))
|
177
183
|
|
184
|
+
pa = if pa_class.instance_method(:initialize).arity > 0
|
185
|
+
pa_class.new(options)
|
186
|
+
else
|
187
|
+
pa_class.new
|
188
|
+
end
|
178
189
|
pa.context = @context if pa.respond_to?(:context=)
|
179
190
|
|
180
191
|
pa
|
@@ -96,7 +96,7 @@ module Ruote
|
|
96
96
|
#
|
97
97
|
def cancel (fei, flavour)
|
98
98
|
|
99
|
-
doc = fetch(fei
|
99
|
+
doc = fetch(fei)
|
100
100
|
|
101
101
|
r = @context.storage.delete(doc)
|
102
102
|
|
@@ -112,16 +112,18 @@ module Ruote
|
|
112
112
|
|
113
113
|
def fetch (fei)
|
114
114
|
|
115
|
-
|
115
|
+
hfei = Ruote::FlowExpressionId.extract_h(fei)
|
116
116
|
|
117
|
-
@context.storage.get('workitems', to_id(
|
117
|
+
@context.storage.get('workitems', to_id(hfei))
|
118
118
|
end
|
119
119
|
|
120
120
|
# Removes the workitem from the in-memory hash and replies to the engine.
|
121
121
|
#
|
122
122
|
def reply (workitem)
|
123
123
|
|
124
|
-
|
124
|
+
# TODO: change method name (receiver mess cleanup)
|
125
|
+
|
126
|
+
doc = fetch(Ruote::FlowExpressionId.extract_h(workitem))
|
125
127
|
|
126
128
|
r = @context.storage.delete(doc)
|
127
129
|
|
@@ -237,8 +239,9 @@ module Ruote
|
|
237
239
|
|
238
240
|
cr = criteria.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
|
239
241
|
|
240
|
-
return @context.storage.query_workitems(cr)
|
241
|
-
|
242
|
+
return @context.storage.query_workitems(cr).collect { |h|
|
243
|
+
Ruote::Workitem.new(h)
|
244
|
+
} if @context.storage.respond_to?(:query_workitems)
|
242
245
|
|
243
246
|
offset = cr.delete('offset')
|
244
247
|
limit = cr.delete('limit')
|
data/lib/ruote/receiver/base.rb
CHANGED
@@ -29,20 +29,19 @@ module Ruote
|
|
29
29
|
# The core methods for the Receiver class (sometimes a Mixin is easier
|
30
30
|
# to integrate).
|
31
31
|
#
|
32
|
-
# (The engine itself includes this mixin
|
32
|
+
# (The engine itself includes this mixin, the LocalParticipant module
|
33
|
+
# includes it as well).
|
33
34
|
#
|
34
35
|
module ReceiverMixin
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def reply (workitem)
|
37
|
+
# This method pipes back a workitem into the engine, letting it resume
|
38
|
+
# in its flow, hopefully.
|
39
|
+
#
|
40
|
+
def receive (workitem)
|
42
41
|
|
43
42
|
workitem = workitem.to_h if workitem.respond_to?(:to_h)
|
44
43
|
|
45
|
-
@storage.put_msg(
|
44
|
+
@context.storage.put_msg(
|
46
45
|
'receive',
|
47
46
|
'fei' => workitem['fei'],
|
48
47
|
'workitem' => workitem,
|
@@ -50,10 +49,108 @@ module Ruote
|
|
50
49
|
'receiver' => sign)
|
51
50
|
end
|
52
51
|
|
52
|
+
# Given a process definitions and optional initial fields and variables,
|
53
|
+
# launches a new process instance.
|
54
|
+
#
|
55
|
+
# This method is mostly used from the Ruote::Engine class (which includes
|
56
|
+
# this mixin).
|
57
|
+
#
|
58
|
+
def launch (process_definition, fields={}, variables={})
|
59
|
+
|
60
|
+
wfid = @context.wfidgen.generate
|
61
|
+
|
62
|
+
@context.storage.put_msg(
|
63
|
+
'launch',
|
64
|
+
'wfid' => wfid,
|
65
|
+
'tree' => @context.parser.parse(process_definition),
|
66
|
+
'workitem' => { 'fields' => fields },
|
67
|
+
'variables' => variables)
|
68
|
+
|
69
|
+
wfid
|
70
|
+
end
|
71
|
+
|
72
|
+
# Wraps a call to receive(workitem)
|
73
|
+
#
|
74
|
+
# Not aliasing so that if someone changes the receive implementation,
|
75
|
+
# reply is affected as well.
|
76
|
+
#
|
77
|
+
def reply (workitem)
|
78
|
+
|
79
|
+
receive (workitem)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Wraps a call to receive(workitem)
|
83
|
+
#
|
84
|
+
# Not aliasing so that if someone changes the receive implementation,
|
85
|
+
# reply_to_engine is affected as well.
|
86
|
+
#
|
87
|
+
def reply_to_engine (workitem)
|
88
|
+
|
89
|
+
receive (workitem)
|
90
|
+
end
|
91
|
+
|
92
|
+
# A receiver signs a workitem when it comes back.
|
93
|
+
#
|
94
|
+
# Not used much as of now.
|
95
|
+
#
|
53
96
|
def sign
|
54
97
|
|
55
98
|
self.class.to_s
|
56
99
|
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
# Convenience method, fetches the flow expression (ParticipantExpression)
|
104
|
+
# that emitted that workitem.
|
105
|
+
#
|
106
|
+
def fetch_flow_expression (workitem)
|
107
|
+
|
108
|
+
Ruote::Exp::FlowExpression.fetch(@context, workitem.fei.to_h)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Stashes values in the participant expression (in the storage).
|
112
|
+
#
|
113
|
+
# put(workitem.fei, 'key' => 'value', 'colour' => 'blue')
|
114
|
+
#
|
115
|
+
# Remember that keys/values must be serializable in JSON.
|
116
|
+
#
|
117
|
+
# put & get are useful for a participant that needs to communicate
|
118
|
+
# between its consume and its cancel.
|
119
|
+
#
|
120
|
+
# See the thread at
|
121
|
+
# http://groups.google.com/group/openwferu-users/t/2e6a95708c10847b for the
|
122
|
+
# justification.
|
123
|
+
#
|
124
|
+
def put (fei, hash)
|
125
|
+
|
126
|
+
fexp = Ruote::Exp::FlowExpression.fetch(@context, fei.to_h)
|
127
|
+
|
128
|
+
(fexp.h['stash'] ||= {}).merge!(hash)
|
129
|
+
|
130
|
+
fexp.persist_or_raise
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fetches back a stashed value.
|
134
|
+
#
|
135
|
+
# get(fei, 'colour')
|
136
|
+
# # => 'blue'
|
137
|
+
#
|
138
|
+
# To return the whole stash
|
139
|
+
#
|
140
|
+
# get(fei)
|
141
|
+
# # => { 'colour' => 'blue' }
|
142
|
+
#
|
143
|
+
# put & get are useful for a participant that needs to communicate
|
144
|
+
# between its consume and its cancel.
|
145
|
+
#
|
146
|
+
def get (fei, key=nil)
|
147
|
+
|
148
|
+
fexp = Ruote::Exp::FlowExpression.fetch(@context, fei.to_h)
|
149
|
+
|
150
|
+
stash = fexp.h['stash'] rescue {}
|
151
|
+
|
152
|
+
key ? stash[key] : stash
|
153
|
+
end
|
57
154
|
end
|
58
155
|
|
59
156
|
#
|
@@ -63,9 +160,11 @@ module Ruote
|
|
63
160
|
class Receiver
|
64
161
|
include ReceiverMixin
|
65
162
|
|
66
|
-
|
163
|
+
# Accepts context, worker, engine or storage as first argument.
|
164
|
+
#
|
165
|
+
def initialize (cwes, options={})
|
67
166
|
|
68
|
-
@
|
167
|
+
@context = cwes.context
|
69
168
|
@options = options
|
70
169
|
end
|
71
170
|
end
|
data/lib/ruote/storage/base.rb
CHANGED
@@ -32,6 +32,24 @@ module Ruote
|
|
32
32
|
#
|
33
33
|
module StorageBase
|
34
34
|
|
35
|
+
def context
|
36
|
+
|
37
|
+
@context ||= Ruote::Context.new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def context= (c)
|
41
|
+
|
42
|
+
@context = c
|
43
|
+
end
|
44
|
+
|
45
|
+
# Attempts to delete a document, returns true if the deletion
|
46
|
+
# succeeded. This is used with msgs to reserve work on them.
|
47
|
+
#
|
48
|
+
def reserve (doc)
|
49
|
+
|
50
|
+
delete(doc).nil?
|
51
|
+
end
|
52
|
+
|
35
53
|
#--
|
36
54
|
# configurations
|
37
55
|
#++
|
@@ -47,28 +65,16 @@ module Ruote
|
|
47
65
|
|
48
66
|
def put_msg (action, options)
|
49
67
|
|
50
|
-
|
51
|
-
|
52
|
-
@counter ||= 0
|
53
|
-
|
54
|
-
t = Time.now.utc
|
55
|
-
ts = "#{t.strftime('%Y-%m-%d')}!#{t.to_i}.#{'%06d' % t.usec}"
|
56
|
-
_id = "#{$$}!#{Thread.current.object_id}!#{ts}!#{'%03d' % @counter}"
|
57
|
-
|
58
|
-
@counter = (@counter + 1) % 1000
|
59
|
-
# some platforms (windows) have shallow usecs, so adding that counter...
|
60
|
-
|
61
|
-
msg = options.merge!('type' => 'msgs', '_id' => _id, 'action' => action)
|
62
|
-
|
63
|
-
msg.delete('_rev')
|
64
|
-
# in case of message replay
|
68
|
+
msg = prepare_msg_doc(action, options)
|
65
69
|
|
66
70
|
put(msg)
|
71
|
+
|
67
72
|
#put(msg, :update_rev => true)
|
68
|
-
#(@local_msgs ||= []) <<
|
73
|
+
#(@local_msgs ||= []) << Ruote.fulldup(msg)
|
69
74
|
end
|
70
75
|
|
71
76
|
#def get_local_msgs
|
77
|
+
# p @local_msgs
|
72
78
|
# if @local_msgs
|
73
79
|
# r = @local_msgs
|
74
80
|
# @local_msgs = nil
|
@@ -87,6 +93,11 @@ module Ruote
|
|
87
93
|
}
|
88
94
|
end
|
89
95
|
|
96
|
+
def empty? (type)
|
97
|
+
|
98
|
+
(get_many(type) == [])
|
99
|
+
end
|
100
|
+
|
90
101
|
#--
|
91
102
|
# expressions
|
92
103
|
#++
|
@@ -116,6 +127,9 @@ module Ruote
|
|
116
127
|
|
117
128
|
def get_schedules (delta, now)
|
118
129
|
|
130
|
+
# TODO : bring that 'optimization' back in,
|
131
|
+
# maybe every minute, if min != last_min ...
|
132
|
+
|
119
133
|
#if delta < 1.0
|
120
134
|
# at = now.strftime('%Y%m%d%H%M%S')
|
121
135
|
# get_many('schedules', /-#{at}$/)
|
@@ -133,33 +147,12 @@ module Ruote
|
|
133
147
|
|
134
148
|
def put_schedule (flavour, owner_fei, s, msg)
|
135
149
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
Rufus::CronLine.new(s).next_time(Time.now + 1)
|
140
|
-
else # at or every
|
141
|
-
Ruote.s_to_at(s)
|
142
|
-
end
|
143
|
-
at = at.utc
|
144
|
-
|
145
|
-
if at <= Time.now.utc && flavour == 'at'
|
146
|
-
put_msg(msg.delete('action'), msg)
|
147
|
-
return
|
150
|
+
if doc = prepare_schedule_doc(flavour, owner_fei, s, msg)
|
151
|
+
put(doc)
|
152
|
+
return doc['_id']
|
148
153
|
end
|
149
154
|
|
150
|
-
|
151
|
-
i = "#{flavour}-#{Ruote.to_storage_id(owner_fei)}-#{sat}"
|
152
|
-
|
153
|
-
put(
|
154
|
-
'_id' => i,
|
155
|
-
'type' => 'schedules',
|
156
|
-
'flavour' => flavour,
|
157
|
-
'original' => s,
|
158
|
-
'at' => Ruote.time_to_utc_s(at),
|
159
|
-
'owner' => owner_fei,
|
160
|
-
'msg' => msg)
|
161
|
-
|
162
|
-
i
|
155
|
+
nil
|
163
156
|
end
|
164
157
|
|
165
158
|
def delete_schedule (schedule_id)
|
@@ -185,8 +178,92 @@ module Ruote
|
|
185
178
|
put_engine_variable(k, v) unless put(vars).nil?
|
186
179
|
end
|
187
180
|
|
181
|
+
#--
|
182
|
+
# migrations
|
183
|
+
#++
|
184
|
+
|
185
|
+
# Copies the content of this storage into a target storage.
|
186
|
+
#
|
187
|
+
# Of course, the target storage may be a different implementation.
|
188
|
+
#
|
189
|
+
def copy_to (target, opts={})
|
190
|
+
|
191
|
+
counter = 0
|
192
|
+
|
193
|
+
%w[
|
194
|
+
configurations errors expressions msgs schedules variables workitems
|
195
|
+
].each do |type|
|
196
|
+
|
197
|
+
get_many(type).each do |item|
|
198
|
+
|
199
|
+
item.delete('_rev')
|
200
|
+
target.put(item)
|
201
|
+
|
202
|
+
counter += 1
|
203
|
+
puts(" #{type}/#{item['_id']}") if opts[:verbose]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
counter
|
208
|
+
end
|
209
|
+
|
188
210
|
protected
|
189
211
|
|
212
|
+
# Used by put_msg
|
213
|
+
#
|
214
|
+
def prepare_msg_doc (action, options)
|
215
|
+
|
216
|
+
# merge! is way faster than merge (no object creation probably)
|
217
|
+
|
218
|
+
@counter ||= 0
|
219
|
+
|
220
|
+
t = Time.now.utc
|
221
|
+
ts = "#{t.strftime('%Y-%m-%d')}!#{t.to_i}.#{'%06d' % t.usec}"
|
222
|
+
_id = "#{$$}!#{Thread.current.object_id}!#{ts}!#{'%03d' % @counter}"
|
223
|
+
|
224
|
+
@counter = (@counter + 1) % 1000
|
225
|
+
# some platforms (windows) have shallow usecs, so adding that counter...
|
226
|
+
|
227
|
+
msg = options.merge!('type' => 'msgs', '_id' => _id, 'action' => action)
|
228
|
+
|
229
|
+
msg.delete('_rev')
|
230
|
+
# in case of message replay
|
231
|
+
|
232
|
+
msg
|
233
|
+
end
|
234
|
+
|
235
|
+
# Used by put_schedule
|
236
|
+
#
|
237
|
+
def prepare_schedule_doc (flavour, owner_fei, s, msg)
|
238
|
+
|
239
|
+
at = if s.is_a?(Time) # at or every
|
240
|
+
s
|
241
|
+
elsif Ruote.is_cron_string(s) # cron
|
242
|
+
Rufus::CronLine.new(s).next_time(Time.now + 1)
|
243
|
+
else # at or every
|
244
|
+
Ruote.s_to_at(s)
|
245
|
+
end
|
246
|
+
at = at.utc
|
247
|
+
|
248
|
+
if at <= Time.now.utc && flavour == 'at'
|
249
|
+
put_msg(msg.delete('action'), msg)
|
250
|
+
return false
|
251
|
+
end
|
252
|
+
|
253
|
+
sat = at.strftime('%Y%m%d%H%M%S')
|
254
|
+
i = "#{flavour}-#{Ruote.to_storage_id(owner_fei)}-#{sat}"
|
255
|
+
|
256
|
+
{
|
257
|
+
'_id' => i,
|
258
|
+
'type' => 'schedules',
|
259
|
+
'flavour' => flavour,
|
260
|
+
'original' => s,
|
261
|
+
'at' => Ruote.time_to_utc_s(at),
|
262
|
+
'owner' => owner_fei,
|
263
|
+
'msg' => msg
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
190
267
|
def get_engine_variables
|
191
268
|
|
192
269
|
get('variables', 'variables') || {
|