weel 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/AUTHORS +2 -0
  2. data/COPYING +504 -0
  3. data/FEATURES +20 -0
  4. data/INSTALL +7 -0
  5. data/README +4 -0
  6. data/Rakefile +15 -0
  7. data/example/SimpleHandlerWrapper.rb +68 -0
  8. data/example/SimpleWorkflow.rb +15 -0
  9. data/example/runme.rb +12 -0
  10. data/lib/weel.rb +749 -0
  11. data/test/ContinueTest.rb +25 -0
  12. data/test/TestHandlerWrapper.rb +120 -0
  13. data/test/TestWorkflow.rb +35 -0
  14. data/test/basic/tc_choose.rb +82 -0
  15. data/test/basic/tc_codereplace.rb +38 -0
  16. data/test/basic/tc_data.rb +32 -0
  17. data/test/basic/tc_endpoint.rb +40 -0
  18. data/test/basic/tc_handler.rb +19 -0
  19. data/test/basic/tc_parallel.rb +115 -0
  20. data/test/basic/tc_search.rb +37 -0
  21. data/test/basic/tc_state.rb +17 -0
  22. data/test/basic/tc_wf_control.rb +68 -0
  23. data/test/complex/tc_generalsynchonizingmerge_loopsearch.rb +113 -0
  24. data/test/complex/tc_parallel_stop.rb +32 -0
  25. data/test/wfp_adv_branching/tc_generalizedjoin.rb +11 -0
  26. data/test/wfp_adv_branching/tc_generalsynchronizingmerge.rb +45 -0
  27. data/test/wfp_adv_branching/tc_localsynchronizingmerge.rb +37 -0
  28. data/test/wfp_adv_branching/tc_multichoice_structuredsynchronizingmerge.rb +47 -0
  29. data/test/wfp_adv_branching/tc_multimerge.rb +11 -0
  30. data/test/wfp_adv_branching/tc_structured_discriminator.rb +28 -0
  31. data/test/wfp_adv_branching/tc_structured_partial_join.rb +41 -0
  32. data/test/wfp_adv_branching/tc_threadmerge.rb +11 -0
  33. data/test/wfp_adv_branching/tc_threadsplit.rb +11 -0
  34. data/test/wfp_basic/tc_exclusivechoice_simplemerge.rb +22 -0
  35. data/test/wfp_basic/tc_parallelsplit_synchronization.rb +26 -0
  36. data/test/wfp_basic/tc_sequence.rb +16 -0
  37. data/test/wfp_iteration/tc_structuredloop.rb +65 -0
  38. data/test/wfp_state_based/tc_deferredchoice.rb +38 -0
  39. data/test/wfp_state_based/tc_interleavedparallelrouting.rb +30 -0
  40. data/weel.gemspec +25 -0
  41. metadata +127 -0
@@ -0,0 +1,20 @@
1
+ WEEL Features
2
+ * Reuses an existing virtual machine for executing control flow
3
+ * Has a core size of ~ 700 LOC.
4
+ * Uses ~ 2 MiB of RAM per instance.
5
+ * Can utilize multiple threads per instance.
6
+ * Cold starts an instance in less than 2ms.
7
+ * Provides a DSL for defining processes.
8
+ * It provides better coverage of workflow patterns compared to Oracle BPELPM,
9
+ jBOSS jBPM, and Apache Ode.
10
+ * Supports BPEL and other languages through transformation to a directly
11
+ executable DSL.
12
+ * Pluggable event & protocol backend for activities.
13
+
14
+ WEEL Applied Benefits
15
+ * Repair & adpatation - change description, change thread of control.
16
+ * Stop & restart process instances.
17
+ * Pause & restart activities (through flexible HandlerWrapper implementation).
18
+ * Build your own process engines & process languages.
19
+ * Never worry about the speed of the engine - updates of ruby interpreter
20
+ (ruby 1.9, ruby 2.0, jruby) ensure speed.
data/INSTALL ADDED
@@ -0,0 +1,7 @@
1
+ BEWARE - PRELIMINARY VERSION
2
+ ----------------------------
3
+
4
+ For WEE Library and tests:
5
+ * gem install weel
6
+ * No dependencies required
7
+ * rake test
data/README ADDED
@@ -0,0 +1,4 @@
1
+ All code in this package is provided under the LGPL license.
2
+ Please read the file COPYING.
3
+
4
+ Tested for MRI >= 1.8.7 (including 1.9.2 and 1.9.3)
@@ -0,0 +1,15 @@
1
+ require 'rake'
2
+ require 'rubygems/package_task'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['./test/*/tc_*.rb']
8
+ t.verbose = false
9
+ end
10
+
11
+ spec = eval(File.read('weel.gemspec'))
12
+ Gem::PackageTask.new(spec) do |pkg|
13
+ pkg.need_zip = true
14
+ pkg.need_tar = true
15
+ end
@@ -0,0 +1,68 @@
1
+ require "pp"
2
+
3
+ class SimpleHandlerWrapper < WEEL::HandlerWrapperBase
4
+ def initialize(args,endpoint=nil,position=nil,continue=nil)
5
+ @__myhandler_stopped = false
6
+ @__myhandler_position = position
7
+ @__myhandler_continue = continue
8
+ @__myhandler_endpoint = endpoint
9
+ @__myhandler_returnValue = nil
10
+ end
11
+
12
+ # executes a ws-call to the given endpoint with the given parameters. the call
13
+ # can be executed asynchron, see finished_call & return_value
14
+ def activity_handle(passthrough, parameters)
15
+ puts "Handle call:"
16
+ puts " position=[#{@__myhandler_position}]"
17
+ puts " passthrough=[#{passthrough}]"
18
+ puts " endpoint=[#{@__myhandler_endpoint}]"
19
+ print " parameters="
20
+ pp parameters
21
+ @__myhandler_returnValue = 'Handler_Dummy_Result'
22
+ @__myhandler_continue.continue
23
+ end
24
+
25
+ # returns the result of the last handled call
26
+ def activity_result_value
27
+ @__myhandler_returnValue
28
+ end
29
+ # Called if the WS-Call should be interrupted. The decision how to deal
30
+ # with this situation is given to the handler. To provide the possibility
31
+ # of a continue the Handler will be asked for a passthrough
32
+ def activity_stop
33
+ puts "Handler: Recieved stop signal, deciding if stopping\n"
34
+ @__myhandler_stopped = true
35
+ end
36
+ # is called from WEEL after stop_call to ask for a passthrough-value that may give
37
+ # information about how to continue the call. This passthrough-value is given
38
+ # to activity_handle if the workflow is configured to do so.
39
+ def activity_passthrough_value
40
+ nil
41
+ end
42
+
43
+ # Called if the execution of the actual activity_handle is not necessary anymore
44
+ # It is definit that the call will not be continued.
45
+ # At this stage, this is only the case if parallel branches are not needed
46
+ # anymore to continue the workflow
47
+ def activity_no_longer_necessary
48
+ puts "Handler: Recieved no_longer_necessary signal, deciding if stopping\n"
49
+ @__myhandler_stopped = true
50
+ end
51
+ # Is called if a Activity is executed correctly
52
+ def inform_activity_done
53
+ puts "Activity #{@__myhandler_position} done\n"
54
+ end
55
+ # Is called if a Activity is executed with an error
56
+ def inform_activity_failed(err)
57
+ puts "Activity #{@__myhandler_position} failed with error #{err}\n"
58
+ raise(err)
59
+ end
60
+ def inform_syntax_error(err,code)
61
+ puts "Syntax messed with error #{code}:#{err}\n"
62
+ raise(err)
63
+ end
64
+ def inform_state_change(newstate)
65
+ puts "State changed to #{newstate}.\n"
66
+ end
67
+
68
+ end
@@ -0,0 +1,15 @@
1
+ require ::File.dirname(__FILE__) + '/../lib/weel'
2
+ require ::File.dirname(__FILE__) + '/SimpleHandlerWrapper'
3
+
4
+ class SimpleWorkflow < WEEL
5
+ handlerwrapper SimpleHandlerWrapper
6
+
7
+ endpoint :ep1 => "orf.at"
8
+ data :a => 17
9
+
10
+ control flow do
11
+ activity :a1, :call, :ep1, :a => data.a, :b => 2 do
12
+ data.a += 3
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require ::File.dirname(__FILE__) + '/SimpleWorkflow'
3
+
4
+ t = SimpleWorkflow.new
5
+ execution = t.start
6
+ execution.join()
7
+ puts "========> Ending-Result:"
8
+ puts " data:#{t.data.inspect}"
9
+ puts " status:#{t.status.inspect}"
10
+ puts " state:#{t.state}"
11
+
12
+
@@ -0,0 +1,749 @@
1
+ require 'thread'
2
+
3
+ # OMG!111! strings have to be emptied
4
+ class String # {{{
5
+ def clear
6
+ self.slice!(0..-1)
7
+ end
8
+ end # }}}
9
+
10
+ class WEEL
11
+ def initialize(*args)# {{{
12
+ @wfsource = nil
13
+ @dslr = DSLRealization.new
14
+ @dslr.__weel_handlerwrapper_args = args
15
+
16
+ ### 1.8
17
+ initialize_search if methods.include?('initialize_search')
18
+ initialize_data if methods.include?('initialize_data')
19
+ initialize_endpoints if methods.include?('initialize_endpoints')
20
+ initialize_handlerwrapper if methods.include?('initialize_handlerwrapper')
21
+ initialize_control if methods.include?('initialize_control')
22
+ ### 1.9
23
+ initialize_search if methods.include?(:initialize_search)
24
+ initialize_data if methods.include?(:initialize_data)
25
+ initialize_endpoints if methods.include?(:initialize_endpoints)
26
+ initialize_handlerwrapper if methods.include?(:initialize_handlerwrapper)
27
+ initialize_control if methods.include?(:initialize_control)
28
+ end # }}}
29
+
30
+ module Signal # {{{
31
+ class SkipManipulate < Exception; end
32
+ class StopSkipManipulate < Exception; end
33
+ class Stop < Exception; end
34
+ class Proceed < Exception; end
35
+ class NoLongerNecessary < Exception; end
36
+ end # }}}
37
+
38
+ class ManipulateRealization # {{{
39
+ def initialize(data,endpoints,status)
40
+ @__weel_data = data
41
+ @__weel_endpoints = endpoints
42
+ @__weel_status = status
43
+ @changed_status = status.id
44
+ @changed_data = []
45
+ @changed_endpoints = []
46
+ end
47
+
48
+ attr_reader :changed_data, :changed_endpoints
49
+
50
+ def changed_status
51
+ @changed_status != status.id
52
+ end
53
+
54
+ def data
55
+ ManipulateHash.new(@__weel_data,@changed_data)
56
+ end
57
+ def endpoints
58
+ ManipulateHash.new(@__weel_endpoints,@changed_endpoints)
59
+ end
60
+ def status
61
+ @__weel_status
62
+ end
63
+ end # }}}
64
+ class ManipulateHash # {{{
65
+ def initialize(values,what)
66
+ @__weel_values = values
67
+ @__weel_what = what
68
+ end
69
+
70
+ def delete(value)
71
+ if @__weel_values.has_key?(value)
72
+ @__weel_what << value
73
+ @__weel_values.delete(value)
74
+ end
75
+ end
76
+
77
+ def clear
78
+ @__weel_what += @__weel_values.keys
79
+ @__weel_values.clear
80
+ end
81
+
82
+ def method_missing(name,*args)
83
+
84
+ if args.empty? && @__weel_values.has_key?(name)
85
+ @__weel_values[name]
86
+ elsif name.to_s[-1..-1] == "=" && args.length == 1
87
+ temp = name.to_s[0..-2]
88
+ @__weel_what << temp.to_sym
89
+ @__weel_values[temp.to_sym] = args[0]
90
+ elsif name.to_s == "[]=" && args.length == 2
91
+ @__weel_values[args[0]] = args[1]
92
+ elsif name.to_s == "[]" && args.length == 1
93
+ @__weel_values[args[0]]
94
+ else
95
+ nil
96
+ end
97
+ end
98
+ end # }}}
99
+
100
+ class Status #{{{
101
+ def initialize(id,message)
102
+ @id = id
103
+ @message = message
104
+ end
105
+ def update(id,message)
106
+ @id = id
107
+ @message = message
108
+ end
109
+ attr_reader :id, :message
110
+ end #}}}
111
+
112
+ class ReadHash # {{{
113
+ def initialize(values)
114
+ @__weel_values = values
115
+ end
116
+
117
+ def method_missing(name,*args)
118
+ temp = nil
119
+ if args.empty? && @__weel_values.has_key?(name)
120
+ @__weel_values[name]
121
+ #TODO dont let user change stuff
122
+ else
123
+ nil
124
+ end
125
+ end
126
+ end # }}}
127
+
128
+ class HandlerWrapperBase # {{{
129
+ def initialize(arguments,endpoint=nil,position=nil,continue=nil); end
130
+
131
+ def activity_handle(passthrough, endpoint, parameters); end
132
+
133
+ def activity_result_value; end
134
+ def activity_result_status; end
135
+
136
+ def activity_stop; end
137
+ def activity_passthrough_value; end
138
+
139
+ def activity_no_longer_necessary; end
140
+
141
+ def inform_activity_done; end
142
+ def inform_activity_manipulate; end
143
+ def inform_activity_failed(err); end
144
+
145
+ def inform_syntax_error(err,code); end
146
+ def inform_manipulate_change(status,data,endpoints); end
147
+ def inform_position_change(ipc); end
148
+ def inform_state_change(newstate); end
149
+
150
+ def vote_sync_before; true; end
151
+ def vote_sync_after; true; end
152
+
153
+ def callback(result); end
154
+ end # }}}
155
+
156
+ class Position # {{{
157
+ attr_reader :position
158
+ attr_accessor :detail, :passthrough
159
+ def initialize(position, detail=:at, passthrough=nil) # :at or :after or :unmark
160
+ @position = position
161
+ @detail = detail
162
+ @passthrough = passthrough
163
+ end
164
+ end # }}}
165
+
166
+ class Continue #{{{
167
+ def initialize
168
+ @thread = Thread.new{Thread.stop}
169
+ end
170
+ def waiting?
171
+ @thread.alive?
172
+ end
173
+ def continue
174
+ while @thread.status != 'sleep' && @thread.alive?
175
+ Thread.pass
176
+ end
177
+ @thread.wakeup if @thread.alive?
178
+ end
179
+ def wait
180
+ @thread.join
181
+ end
182
+ end #}}}
183
+
184
+ def self::search(weel_search)# {{{
185
+ define_method :initialize_search do
186
+ self.search weel_search
187
+ end
188
+ end # }}}
189
+ def self::endpoint(new_endpoints)# {{{
190
+ @@__weel_new_endpoints ||= {}
191
+ @@__weel_new_endpoints.merge! new_endpoints
192
+ define_method :initialize_endpoints do
193
+ @@__weel_new_endpoints.each do |name,value|
194
+ @dslr.__weel_endpoints[name.to_s.to_sym] = value
195
+ end
196
+ end
197
+ end # }}}
198
+ def self::data(data_elements)# {{{
199
+ @@__weel_new_data_elements ||= {}
200
+ @@__weel_new_data_elements.merge! data_elements
201
+ define_method :initialize_data do
202
+ @@__weel_new_data_elements.each do |name,value|
203
+ @dslr.__weel_data[name.to_s.to_sym] = value
204
+ end
205
+ end
206
+ end # }}}
207
+ def self::handlerwrapper(aClassname, *args)# {{{
208
+ define_method :initialize_handlerwrapper do
209
+ self.handlerwrapper = aClassname
210
+ self.handlerwrapper_args = args unless args.empty?
211
+ end
212
+ end # }}}
213
+ def self::control(flow, &block)# {{{
214
+ @@__weel_control_block = block
215
+ define_method :initialize_control do
216
+ self.description(&(@@__weel_control_block))
217
+ end
218
+ end # }}}
219
+ def self::flow #{{{
220
+ end #}}}
221
+
222
+ class DSLRealization # {{{
223
+ def initialize
224
+ @__weel_search_positions = {}
225
+ @__weel_positions = Array.new
226
+ @__weel_main = nil
227
+ @__weel_data ||= Hash.new
228
+ @__weel_endpoints ||= Hash.new
229
+ @__weel_handlerwrapper = HandlerWrapperBase
230
+ @__weel_handlerwrapper_args = []
231
+ @__weel_state = :ready
232
+ @__weel_status = Status.new(0,"undefined")
233
+ end
234
+ attr_accessor :__weel_search_positions, :__weel_positions, :__weel_main, :__weel_data, :__weel_endpoints, :__weel_handlerwrapper, :__weel_handlerwrapper_args
235
+ attr_reader :__weel_state, :__weel_status
236
+
237
+ # DSL-Construct for an atomic activity
238
+ # position: a unique identifier within the wf-description (may be used by the search to identify a starting point
239
+ # type:
240
+ # - :manipulate - just yield a given block
241
+ # - :call - order the handlerwrapper to perform a service call
242
+ # endpoint: (only with :call) ep of the service
243
+ # parameters: (only with :call) service parameters
244
+ def activity(position, type, endpoint=nil, *parameters, &blk)# {{{
245
+ position = __weel_position_test position
246
+ begin
247
+ searchmode = __weel_is_in_search_mode(position)
248
+ return if searchmode == true
249
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
250
+
251
+ Thread.current[:continue] = Continue.new
252
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args, @__weel_endpoints[endpoint], position, Thread.current[:continue]
253
+
254
+ ipc = {}
255
+ if searchmode == :after
256
+ wp = WEEL::Position.new(position, :after, nil)
257
+ ipc[:after] = [wp.position]
258
+ else
259
+ if Thread.current[:branch_parent] && Thread.current[:branch_parent][:branch_position]
260
+ @__weel_positions.delete Thread.current[:branch_parent][:branch_position]
261
+ ipc[:unmark] ||= []
262
+ ipc[:unmark] << Thread.current[:branch_parent][:branch_position].position rescue nil
263
+ Thread.current[:branch_parent][:branch_position] = nil
264
+ end
265
+ if Thread.current[:branch_position]
266
+ @__weel_positions.delete Thread.current[:branch_position]
267
+ ipc[:unmark] ||= []
268
+ ipc[:unmark] << Thread.current[:branch_position].position rescue nil
269
+ end
270
+ wp = WEEL::Position.new(position, :at, nil)
271
+ ipc[:at] = [wp.position]
272
+ end
273
+ @__weel_positions << wp
274
+ Thread.current[:branch_position] = wp
275
+
276
+ handlerwrapper.inform_position_change(ipc)
277
+
278
+ # searchmode position is after, jump directly to vote_sync_after
279
+ raise Signal::Proceed if searchmode == :after
280
+
281
+ raise Signal::Stop unless handlerwrapper.vote_sync_before
282
+
283
+ case type
284
+ when :manipulate
285
+ if block_given?
286
+ handlerwrapper.inform_activity_manipulate
287
+ mr = ManipulateRealization.new(@__weel_data,@__weel_endpoints,@__weel_status)
288
+ status = nil
289
+ parameters.delete_if do |para|
290
+ status = para if para.is_a?(Status)
291
+ para.is_a?(Status)
292
+ end
293
+ case blk.arity
294
+ when 1; mr.instance_exec(parameters,&blk)
295
+ when 2; mr.instance_exec(parameters,status,&blk)
296
+ else
297
+ mr.instance_eval(&blk)
298
+ end
299
+ handlerwrapper.inform_manipulate_change(
300
+ (mr.changed_status ? @__weel_status : nil),
301
+ (mr.changed_data.any? ? mr.changed_data.uniq : nil),
302
+ (mr.changed_endpoints.any? ? mr.changed_endpoints.uniq : nil)
303
+ )
304
+ handlerwrapper.inform_activity_done
305
+ wp.detail = :after
306
+ handlerwrapper.inform_position_change :after => [wp.position]
307
+ end
308
+ when :call
309
+ params = { }
310
+ passthrough = @__weel_search_positions[position] ? @__weel_search_positions[position].passthrough : nil
311
+ parameters.each do |p|
312
+ if p.class == Hash && parameters.length == 1
313
+ params = p
314
+ else
315
+ if !p.is_a?(Symbol) || !@__weel_data.include?(p)
316
+ raise("not all passed parameters are data elements")
317
+ end
318
+ params[p] = @__weel_data[p]
319
+ end
320
+ end
321
+ # handshake call and wait until it finished
322
+ handlerwrapper.activity_handle passthrough, params
323
+ Thread.current[:continue].wait unless Thread.current[:nolongernecessary] || self.__weel_state == :stopping || self.__weel_state == :stopped
324
+
325
+ if Thread.current[:nolongernecessary]
326
+ handlerwrapper.activity_no_longer_necessary
327
+ raise Signal::NoLongerNecessary
328
+ end
329
+ if self.__weel_state == :stopping
330
+ handlerwrapper.activity_stop
331
+ wp.passthrough = handlerwrapper.activity_passthrough_value
332
+ end
333
+
334
+ if wp.passthrough.nil? && block_given?
335
+ handlerwrapper.inform_activity_manipulate
336
+ mr = ManipulateRealization.new(@__weel_data,@__weel_endpoints,@__weel_status)
337
+ status = handlerwrapper.activity_result_status
338
+ case blk.arity
339
+ when 1; mr.instance_exec(handlerwrapper.activity_result_value,&blk)
340
+ when 2; mr.instance_exec(handlerwrapper.activity_result_value,(status.is_a?(Status)?status:nil),&blk)
341
+ else
342
+ mr.instance_eval(&blk)
343
+ end
344
+ handlerwrapper.inform_manipulate_change(
345
+ (mr.changed_status ? @__weel_status : nil),
346
+ (mr.changed_data.any? ? mr.changed_data.uniq : nil),
347
+ (mr.changed_endpoints.any? ? mr.changed_endpoints.uniq : nil)
348
+ )
349
+ end
350
+ if wp.passthrough.nil?
351
+ handlerwrapper.inform_activity_done
352
+ wp.detail = :after
353
+ handlerwrapper.inform_position_change :after => [wp.position]
354
+ end
355
+ end
356
+ raise Signal::Proceed
357
+ rescue Signal::SkipManipulate, Signal::Proceed
358
+ if self.__weel_state != :stopping && !handlerwrapper.vote_sync_after
359
+ self.__weel_state = :stopping
360
+ wp.detail = :unmark
361
+ end
362
+ rescue Signal::NoLongerNecessary
363
+ @__weel_positions.delete wp
364
+ Thread.current[:branch_position] = nil
365
+ wp.detail = :unmark
366
+ handlerwrapper.inform_position_change :unmark => [wp.position]
367
+ rescue Signal::StopSkipManipulate, Signal::Stop
368
+ self.__weel_state = :stopping
369
+ rescue => err
370
+ handlerwrapper.inform_activity_failed err
371
+ self.__weel_state = :stopping
372
+ end
373
+ end # }}}
374
+
375
+ # Parallel DSL-Construct
376
+ # Defines Workflow paths that can be executed parallel.
377
+ # May contain multiple branches (parallel_branch)
378
+ def parallel(type=nil)# {{{
379
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
380
+
381
+ Thread.current[:branches] = []
382
+ Thread.current[:branch_finished_count] = 0
383
+ Thread.current[:branch_event] = Continue.new
384
+ Thread.current[:mutex] = Mutex.new
385
+ yield
386
+
387
+ Thread.current[:branch_wait_count] = (type.is_a?(Hash) && type.size == 1 && type[:wait] != nil && (type[:wait].is_a?(Integer)) ? type[:wait] : Thread.current[:branches].size)
388
+ Thread.current[:branches].each do |thread|
389
+ while thread.status != 'sleep' && thread.alive?
390
+ Thread.pass
391
+ end
392
+ # decide after executing block in parallel cause for coopis
393
+ # it goes out of search mode while dynamically counting branches
394
+ if Thread.current[:branch_search] == false
395
+ thread[:branch_search] = false
396
+ end
397
+ thread.wakeup if thread.alive?
398
+ end
399
+
400
+ Thread.current[:branch_event].wait
401
+ #Thread.current[:branch_event] = nil
402
+
403
+ unless self.__weel_state == :stopping || self.__weel_state == :stopped
404
+ # first set all to no_longer_neccessary
405
+ Thread.current[:branches].each do |thread|
406
+ if thread.alive?
407
+ thread[:nolongernecessary] = true
408
+ __weel_recursive_continue(thread)
409
+ end
410
+ end
411
+ # wait for all
412
+ Thread.current[:branches].each do |thread|
413
+ __weel_recursive_join(thread)
414
+ end
415
+ end
416
+ end # }}}
417
+
418
+ # Defines a branch of a parallel-Construct
419
+ def parallel_branch(*vars)# {{{
420
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
421
+ branch_parent = Thread.current
422
+ Thread.current[:branches] << Thread.new(*vars) do |*local|
423
+ branch_parent[:mutex].synchronize do
424
+ Thread.current.abort_on_exception = true
425
+ Thread.current[:branch_status] = false
426
+ Thread.current[:branch_parent] = branch_parent
427
+ if branch_parent[:alternative_executed] && branch_parent[:alternative_executed].length > 0
428
+ Thread.current[:alternative_executed] = [branch_parent[:alternative_executed].last]
429
+ end
430
+ end
431
+
432
+ Thread.stop
433
+ yield(*local)
434
+
435
+ branch_parent[:mutex].synchronize do
436
+ Thread.current[:branch_status] = true
437
+ branch_parent[:branch_finished_count] += 1
438
+ if branch_parent[:branch_finished_count] == branch_parent[:branch_wait_count] && self.__weel_state != :stopping
439
+ branch_parent[:branch_event].continue
440
+ end
441
+ end
442
+ if self.__weel_state != :stopping && self.__weel_state != :stopped
443
+ if Thread.current[:branch_position]
444
+ @__weel_positions.delete Thread.current[:branch_position]
445
+ begin
446
+ ipc = {}
447
+ ipc[:unmark] = [Thread.current[:branch_position].position]
448
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
449
+ handlerwrapper.inform_position_change(ipc)
450
+ end rescue nil
451
+ Thread.current[:branch_position] = nil
452
+ end
453
+ end
454
+ end
455
+ Thread.pass
456
+ end # }}}
457
+
458
+ # Choose DSL-Construct
459
+ # Defines a choice in the Workflow path.
460
+ # May contain multiple execution alternatives
461
+ def choose # {{{
462
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
463
+ Thread.current[:alternative_executed] ||= []
464
+ Thread.current[:alternative_executed] << false
465
+ yield
466
+ Thread.current[:alternative_executed].pop
467
+ nil
468
+ end # }}}
469
+
470
+ # Defines a possible choice of a choose-Construct
471
+ # Block is executed if condition == true or
472
+ # searchmode is active (to find the starting position)
473
+ def alternative(condition)# {{{
474
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
475
+ yield if __weel_is_in_search_mode || condition
476
+ Thread.current[:alternative_executed][-1] = true if condition
477
+ end # }}}
478
+ def otherwise # {{{
479
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
480
+ yield if __weel_is_in_search_mode || !Thread.current[:alternative_executed].last
481
+ end # }}}
482
+
483
+ # Defines a critical block (=Mutex)
484
+ def critical(id)# {{{
485
+ @__weel_critical ||= Mutex.new
486
+ semaphore = nil
487
+ @__weel_critical.synchronize do
488
+ @__weel_critical_sections ||= {}
489
+ semaphore = @__weel_critical_sections[id] ? @__weel_critical_sections[id] : Mutex.new
490
+ @__weel_critical_sections[id] = semaphore if id
491
+ end
492
+ semaphore.synchronize do
493
+ yield
494
+ end
495
+ end # }}}
496
+
497
+ # Defines a Cycle (loop/iteration)
498
+ def loop(condition)# {{{
499
+ unless condition.is_a?(Array) && condition[0].is_a?(Proc) && [:pre_test,:post_test].include?(condition[1])
500
+ raise "condition must be called pre_test{} or post_test{}"
501
+ end
502
+ return if self.__weel_state == :stopping || self.__weel_state == :stopped || Thread.current[:nolongernecessary]
503
+ if __weel_is_in_search_mode
504
+ yield
505
+ return if __weel_is_in_search_mode
506
+ end
507
+ case condition[1]
508
+ when :pre_test
509
+ yield while condition[0].call && self.__weel_state != :stopping && self.__weel_state != :stopped
510
+ when :post_test
511
+ begin; yield; end while condition[0].call && self.__weel_state != :stopping && self.__weel_state != :stopped
512
+ end
513
+ end # }}}
514
+
515
+ def pre_test(&blk)# {{{
516
+ [blk, :pre_test]
517
+ end # }}}
518
+ def post_test(&blk)# {{{
519
+ [blk, :post_test]
520
+ end # }}}
521
+
522
+ def status # {{{
523
+ @__weel_status
524
+ end # }}}
525
+ def data # {{{
526
+ ReadHash.new(@__weel_data)
527
+ end # }}}
528
+ def endpoints # {{{
529
+ ReadHash.new(@__weel_endpoints)
530
+ end # }}}
531
+
532
+ private
533
+ def __weel_recursive_print(thread,indent='')# {{{
534
+ p "#{indent}#{thread}"
535
+ if thread[:branches]
536
+ thread[:branches].each do |b|
537
+ __weel_recursive_print(b,indent+' ')
538
+ end
539
+ end
540
+ end # }}}
541
+ def __weel_recursive_continue(thread)# {{{
542
+ return unless thread
543
+ if thread.alive? && thread[:continue] && thread[:continue].waiting?
544
+ thread[:continue].continue
545
+ end
546
+ if thread.alive? && thread[:branch_event] && thread[:branch_event].waiting?
547
+ thread[:mutex].synchronize do
548
+ unless thread[:branch_event].nil?
549
+ thread[:branch_event].continue
550
+ # thread[:branch_event] = nil
551
+ end
552
+ end
553
+ end
554
+ if thread[:branches]
555
+ thread[:branches].each do |b|
556
+ __weel_recursive_continue(b)
557
+ end
558
+ end
559
+ end # }}}
560
+ def __weel_recursive_join(thread)# {{{
561
+ return unless thread
562
+ if thread.alive? && thread != Thread.current
563
+ thread.join
564
+ end
565
+ if thread[:branches]
566
+ thread[:branches].each do |b|
567
+ __weel_recursive_join(b)
568
+ end
569
+ end
570
+ end # }}}
571
+
572
+ def __weel_position_test(position)# {{{
573
+ if position.is_a?(Symbol) && position.to_s =~ /[a-zA-Z][a-zA-Z0-9_]*/
574
+ position
575
+ else
576
+ self.__weel_state = :stopping
577
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
578
+ handlerwrapper.inform_syntax_error(Exception.new("position (#{position}) not valid"),nil)
579
+ end
580
+ end # }}}
581
+
582
+ def __weel_is_in_search_mode(position = nil)# {{{
583
+ branch = Thread.current
584
+ return false if @__weel_search_positions.empty? || branch[:branch_search] == false
585
+
586
+ if position && @__weel_search_positions.include?(position) # matching searchpos => start execution from here
587
+ branch[:branch_search] = false # execute all activities in THIS branch (thread) after this point
588
+ while branch.key?(:branch_parent) # also all parent branches should execute activities after this point, additional branches spawned by parent branches should still be in search mode
589
+ branch = branch[:branch_parent]
590
+ branch[:branch_search] = false
591
+ end
592
+ @__weel_search_positions[position].detail == :after ? :after : false
593
+ else
594
+ branch[:branch_search] = true
595
+ end
596
+ end # }}}
597
+
598
+ public
599
+ def __weel_finalize
600
+ __weel_recursive_join(@__weel_main)
601
+ @__weel_state = :stopped
602
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
603
+ handlerwrapper.inform_state_change @__weel_state
604
+ end
605
+
606
+ def __weel_state=(newState)# {{{
607
+ return @__weel_state if newState == @__weel_state
608
+ @__weel_positions = Array.new if @__weel_state != newState && newState == :running
609
+ handlerwrapper = @__weel_handlerwrapper.new @__weel_handlerwrapper_args
610
+ @__weel_state = newState
611
+
612
+ if newState == :stopping
613
+ __weel_recursive_continue(@__weel_main)
614
+ end
615
+
616
+ handlerwrapper.inform_state_change @__weel_state
617
+ end # }}}
618
+
619
+ end # }}}
620
+
621
+ public
622
+ def positions # {{{
623
+ @dslr.__weel_positions
624
+ end # }}}
625
+
626
+ # set the handlerwrapper
627
+ def handlerwrapper # {{{
628
+ @dslr.__weel_handlerwrapper
629
+ end # }}}
630
+ def handlerwrapper=(new_weel_handlerwrapper) # {{{
631
+ superclass = new_weel_handlerwrapper
632
+ while superclass
633
+ check_ok = true if superclass == WEEL::HandlerWrapperBase
634
+ superclass = superclass.superclass
635
+ end
636
+ raise "Handlerwrapper is not inherited from HandlerWrapperBase" unless check_ok
637
+ @dslr.__weel_handlerwrapper = new_weel_handlerwrapper
638
+ end # }}}
639
+
640
+ # Get/Set the handlerwrapper arguments
641
+ def handlerwrapper_args # {{{
642
+ @dslr.__weel_handlerwrapper_args
643
+ end # }}}
644
+ def handlerwrapper_args=(args) # {{{
645
+ if args.class == Array
646
+ @dslr.__weel_handlerwrapper_args = args
647
+ end
648
+ nil
649
+ end # }}}
650
+
651
+ # Get the state of execution (ready|running|stopping|stopped|finished)
652
+ def state # {{{
653
+ @dslr.__weel_state
654
+ end # }}}
655
+
656
+ # Set search positions
657
+ # set new_weel_search to a boolean (or anything else) to start the process from beginning (reset serach positions)
658
+ def search(new_weel_search=false) # {{{
659
+ @dslr.__weel_search_positions.clear
660
+
661
+ new_weel_search = [new_weel_search] if new_weel_search.is_a?(Position)
662
+
663
+ if !new_weel_search.is_a?(Array) || new_weel_search.empty?
664
+ false
665
+ else
666
+ new_weel_search.each do |search_position|
667
+ @dslr.__weel_search_positions[search_position.position] = search_position
668
+ end
669
+ true
670
+ end
671
+ end # }}}
672
+
673
+ def data(new_data=nil) # {{{
674
+ unless new_data.nil? || !new_data.is_a?(Hash)
675
+ new_data.each{|k,v|@dslr.__weel_data[k] = v}
676
+ end
677
+ @dslr.__weel_data
678
+ end # }}}
679
+ def endpoints(new_endpoints=nil) # {{{
680
+ unless new_endpoints.nil? || !new_endpoints.is_a?(Hash)
681
+ new_endpoints.each{|k,v|@dslr.__weel_endpoints[k] = v}
682
+ end
683
+ @dslr.__weel_endpoints
684
+ end # }}}
685
+ def endpoint(new_endpoints) # {{{
686
+ unless new_endpoints.nil? || !new_endpoints.is_a?(Hash) || !new_endpoints.length == 1
687
+ new_endpoints.each{|k,v|@dslr.__weel_endpoints[k] = v}
688
+ end
689
+ nil
690
+ end # }}}
691
+ def status # {{{
692
+ @dslr.__weel_status
693
+ end # }}}
694
+
695
+ # get/set workflow description
696
+ def description(code = nil,&blk) # {{{
697
+ bgiven = block_given?
698
+ if code.nil? && !bgiven
699
+ @wfsource
700
+ else
701
+ @wfsource = code unless bgiven
702
+ (class << self; self; end).class_eval do
703
+ define_method :__weel_control_flow do
704
+ @dslr.__weel_positions.clear
705
+ @dslr.__weel_state = :running
706
+ begin
707
+ if bgiven
708
+ @dslr.instance_eval(&blk)
709
+ else
710
+ @dslr.instance_eval(code)
711
+ end
712
+ rescue Exception => err
713
+ @dslr.__weel_state = :stopping
714
+ handlerwrapper = @dslr.__weel_handlerwrapper.new @dslr.__weel_handlerwrapper_args
715
+ handlerwrapper.inform_syntax_error(err,code)
716
+ end
717
+ if @dslr.__weel_state == :running
718
+ @dslr.__weel_state = :finished
719
+ ipc = { :unmark => [] }
720
+ @dslr.__weel_positions.each{|wp| ipc[:unmark] << wp.position}
721
+ @dslr.__weel_positions.clear
722
+ handlerwrapper = @dslr.__weel_handlerwrapper.new @dslr.__weel_handlerwrapper_args
723
+ handlerwrapper.inform_position_change(ipc)
724
+ end
725
+ if @dslr.__weel_state == :stopping
726
+ @dslr.__weel_finalize
727
+ end
728
+ end
729
+ end
730
+ bgiven ? blk : code
731
+ end
732
+ end # }}}
733
+
734
+ # Stop the workflow execution
735
+ def stop # {{{
736
+ Thread.new do
737
+ @dslr.__weel_state = :stopping
738
+ @dslr.__weel_main.join if @dslr.__weel_main
739
+ end
740
+ end # }}}
741
+ # Start the workflow execution
742
+ def start # {{{
743
+ return nil if @dslr.__weel_state != :ready && @dslr.__weel_state != :stopped
744
+ @dslr.__weel_main = Thread.new do
745
+ __weel_control_flow
746
+ end
747
+ end # }}}
748
+
749
+ end