ruote 2.2.0 → 2.3.0

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 (305) hide show
  1. data/CHANGELOG.txt +166 -1
  2. data/CREDITS.txt +36 -17
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +1 -7
  5. data/Rakefile +38 -29
  6. data/TODO.txt +93 -52
  7. data/lib/ruote-fs.rb +3 -0
  8. data/lib/ruote.rb +5 -1
  9. data/lib/ruote/context.rb +140 -35
  10. data/lib/ruote/dashboard.rb +1247 -0
  11. data/lib/ruote/{engine → dboard}/process_error.rb +22 -2
  12. data/lib/ruote/dboard/process_status.rb +587 -0
  13. data/lib/ruote/engine.rb +6 -871
  14. data/lib/ruote/exp/command.rb +7 -2
  15. data/lib/ruote/exp/commanded.rb +2 -2
  16. data/lib/ruote/exp/condition.rb +38 -13
  17. data/lib/ruote/exp/fe_add_branches.rb +1 -1
  18. data/lib/ruote/exp/fe_apply.rb +1 -1
  19. data/lib/ruote/exp/fe_await.rb +357 -0
  20. data/lib/ruote/exp/fe_cancel_process.rb +17 -3
  21. data/lib/ruote/exp/fe_command.rb +8 -4
  22. data/lib/ruote/exp/fe_concurrence.rb +218 -18
  23. data/lib/ruote/exp/fe_concurrent_iterator.rb +71 -10
  24. data/lib/ruote/exp/fe_cron.rb +3 -10
  25. data/lib/ruote/exp/fe_cursor.rb +14 -4
  26. data/lib/ruote/exp/fe_define.rb +3 -1
  27. data/lib/ruote/exp/fe_echo.rb +1 -1
  28. data/lib/ruote/exp/fe_equals.rb +1 -1
  29. data/lib/ruote/exp/fe_error.rb +1 -1
  30. data/lib/ruote/exp/fe_filter.rb +163 -4
  31. data/lib/ruote/exp/fe_forget.rb +21 -4
  32. data/lib/ruote/exp/fe_given.rb +1 -1
  33. data/lib/ruote/exp/fe_if.rb +1 -1
  34. data/lib/ruote/exp/fe_inc.rb +102 -35
  35. data/lib/ruote/exp/fe_iterator.rb +47 -12
  36. data/lib/ruote/exp/fe_listen.rb +96 -11
  37. data/lib/ruote/exp/fe_lose.rb +31 -4
  38. data/lib/ruote/exp/fe_noop.rb +1 -1
  39. data/lib/ruote/exp/fe_on_error.rb +109 -0
  40. data/lib/ruote/exp/fe_once.rb +10 -19
  41. data/lib/ruote/exp/fe_participant.rb +90 -28
  42. data/lib/ruote/exp/fe_read.rb +69 -0
  43. data/lib/ruote/exp/fe_redo.rb +3 -2
  44. data/lib/ruote/exp/fe_ref.rb +57 -27
  45. data/lib/ruote/exp/fe_registerp.rb +1 -3
  46. data/lib/ruote/exp/fe_reserve.rb +1 -1
  47. data/lib/ruote/exp/fe_restore.rb +6 -6
  48. data/lib/ruote/exp/fe_save.rb +12 -19
  49. data/lib/ruote/exp/fe_sequence.rb +38 -2
  50. data/lib/ruote/exp/fe_set.rb +143 -40
  51. data/lib/ruote/exp/{fe_let.rb → fe_stall.rb} +7 -38
  52. data/lib/ruote/exp/fe_subprocess.rb +8 -2
  53. data/lib/ruote/exp/fe_that.rb +1 -1
  54. data/lib/ruote/exp/fe_undo.rb +40 -4
  55. data/lib/ruote/exp/fe_unregisterp.rb +1 -3
  56. data/lib/ruote/exp/fe_wait.rb +12 -25
  57. data/lib/ruote/exp/{flowexpression.rb → flow_expression.rb} +375 -229
  58. data/lib/ruote/exp/iterator.rb +2 -2
  59. data/lib/ruote/exp/merge.rb +78 -17
  60. data/lib/ruote/exp/ro_attributes.rb +46 -36
  61. data/lib/ruote/exp/ro_filters.rb +34 -8
  62. data/lib/ruote/exp/ro_on_x.rb +431 -0
  63. data/lib/ruote/exp/ro_persist.rb +19 -7
  64. data/lib/ruote/exp/ro_timers.rb +123 -0
  65. data/lib/ruote/exp/ro_variables.rb +90 -29
  66. data/lib/ruote/fei.rb +57 -3
  67. data/lib/ruote/fs.rb +3 -0
  68. data/lib/ruote/id/mnemo_wfid_generator.rb +30 -7
  69. data/lib/ruote/id/wfid_generator.rb +17 -38
  70. data/lib/ruote/log/default_history.rb +23 -9
  71. data/lib/ruote/log/fancy_printing.rb +265 -0
  72. data/lib/ruote/log/storage_history.rb +23 -13
  73. data/lib/ruote/log/wait_logger.rb +224 -17
  74. data/lib/ruote/observer.rb +82 -0
  75. data/lib/ruote/part/block_participant.rb +65 -28
  76. data/lib/ruote/part/code_participant.rb +81 -0
  77. data/lib/ruote/part/engine_participant.rb +7 -2
  78. data/lib/ruote/part/local_participant.rb +221 -21
  79. data/lib/ruote/part/no_op_participant.rb +1 -1
  80. data/lib/ruote/part/null_participant.rb +1 -1
  81. data/lib/ruote/part/participant.rb +50 -0
  82. data/lib/ruote/part/rev_participant.rb +178 -0
  83. data/lib/ruote/part/smtp_participant.rb +2 -2
  84. data/lib/ruote/part/storage_participant.rb +228 -60
  85. data/lib/ruote/part/template.rb +1 -1
  86. data/lib/ruote/participant.rb +2 -0
  87. data/lib/ruote/reader.rb +205 -68
  88. data/lib/ruote/reader/json.rb +49 -0
  89. data/lib/ruote/reader/radial.rb +303 -0
  90. data/lib/ruote/reader/ruby_dsl.rb +44 -9
  91. data/lib/ruote/reader/xml.rb +11 -8
  92. data/lib/ruote/receiver/base.rb +98 -45
  93. data/lib/ruote/storage/base.rb +104 -35
  94. data/lib/ruote/storage/composite_storage.rb +50 -60
  95. data/lib/ruote/storage/fs_storage.rb +25 -34
  96. data/lib/ruote/storage/hash_storage.rb +38 -36
  97. data/lib/ruote/svc/dispatch_pool.rb +104 -35
  98. data/lib/ruote/svc/dollar_sub.rb +10 -8
  99. data/lib/ruote/svc/error_handler.rb +108 -52
  100. data/lib/ruote/svc/expression_map.rb +3 -3
  101. data/lib/ruote/svc/participant_list.rb +160 -55
  102. data/lib/ruote/svc/tracker.rb +31 -31
  103. data/lib/ruote/svc/treechecker.rb +28 -16
  104. data/lib/ruote/tree_dot.rb +1 -1
  105. data/lib/ruote/util/deep.rb +143 -0
  106. data/lib/ruote/util/filter.rb +125 -18
  107. data/lib/ruote/util/hashdot.rb +15 -13
  108. data/lib/ruote/util/look.rb +1 -1
  109. data/lib/ruote/util/lookup.rb +60 -22
  110. data/lib/ruote/util/misc.rb +63 -18
  111. data/lib/ruote/util/mpatch.rb +53 -0
  112. data/lib/ruote/util/ometa.rb +1 -2
  113. data/lib/ruote/util/process_observer.rb +177 -0
  114. data/lib/ruote/util/subprocess.rb +1 -1
  115. data/lib/ruote/util/time.rb +2 -2
  116. data/lib/ruote/util/tree.rb +64 -2
  117. data/lib/ruote/version.rb +3 -2
  118. data/lib/ruote/worker.rb +421 -92
  119. data/lib/ruote/workitem.rb +157 -22
  120. data/ruote.gemspec +15 -9
  121. data/test/bm/ci.rb +0 -2
  122. data/test/bm/ici.rb +0 -2
  123. data/test/bm/load_26c.rb +0 -3
  124. data/test/bm/mega.rb +0 -2
  125. data/test/functional/base.rb +57 -43
  126. data/test/functional/concurrent_base.rb +16 -13
  127. data/test/functional/ct_0_concurrence.rb +7 -11
  128. data/test/functional/ct_1_iterator.rb +9 -11
  129. data/test/functional/ct_2_cancel.rb +28 -17
  130. data/test/functional/eft_0_flow_expression.rb +35 -0
  131. data/test/functional/eft_10_cancel_process.rb +1 -1
  132. data/test/functional/eft_11_wait.rb +13 -13
  133. data/test/functional/eft_12_listen.rb +199 -66
  134. data/test/functional/eft_13_iterator.rb +95 -29
  135. data/test/functional/eft_14_cursor.rb +74 -24
  136. data/test/functional/eft_15_loop.rb +7 -7
  137. data/test/functional/eft_16_if.rb +1 -1
  138. data/test/functional/eft_17_equals.rb +1 -1
  139. data/test/functional/eft_18_concurrent_iterator.rb +156 -68
  140. data/test/functional/eft_19_reserve.rb +15 -15
  141. data/test/functional/eft_1_echo.rb +1 -1
  142. data/test/functional/eft_20_save.rb +51 -9
  143. data/test/functional/eft_21_restore.rb +1 -1
  144. data/test/functional/eft_22_noop.rb +1 -1
  145. data/test/functional/eft_23_apply.rb +1 -1
  146. data/test/functional/eft_24_add_branches.rb +7 -8
  147. data/test/functional/eft_25_command.rb +1 -1
  148. data/test/functional/eft_26_error.rb +11 -11
  149. data/test/functional/eft_27_inc.rb +111 -67
  150. data/test/functional/eft_28_once.rb +16 -16
  151. data/test/functional/eft_29_cron.rb +9 -9
  152. data/test/functional/eft_2_sequence.rb +23 -4
  153. data/test/functional/eft_30_ref.rb +36 -24
  154. data/test/functional/eft_31_registerp.rb +24 -24
  155. data/test/functional/eft_32_lose.rb +46 -20
  156. data/test/functional/eft_34_given.rb +1 -1
  157. data/test/functional/eft_35_filter.rb +161 -7
  158. data/test/functional/eft_36_read.rb +97 -0
  159. data/test/functional/{eft_0_process_definition.rb → eft_37_process_definition.rb} +4 -4
  160. data/test/functional/eft_38_on_error.rb +195 -0
  161. data/test/functional/eft_39_stall.rb +35 -0
  162. data/test/functional/eft_3_participant.rb +77 -22
  163. data/test/functional/eft_40_await.rb +297 -0
  164. data/test/functional/eft_4_set.rb +110 -11
  165. data/test/functional/eft_5_subprocess.rb +27 -5
  166. data/test/functional/eft_6_concurrence.rb +299 -60
  167. data/test/functional/eft_7_forget.rb +24 -22
  168. data/test/functional/eft_8_undo.rb +52 -15
  169. data/test/functional/eft_9_redo.rb +18 -20
  170. data/test/functional/ft_0_worker.rb +122 -13
  171. data/test/functional/ft_10_dollar.rb +77 -16
  172. data/test/functional/ft_11_recursion.rb +9 -9
  173. data/test/functional/ft_12_launchitem.rb +7 -9
  174. data/test/functional/ft_13_variables.rb +125 -22
  175. data/test/functional/ft_14_re_apply.rb +112 -56
  176. data/test/functional/ft_15_timeout.rb +64 -33
  177. data/test/functional/ft_16_participant_params.rb +59 -6
  178. data/test/functional/ft_17_conditional.rb +68 -2
  179. data/test/functional/ft_18_kill.rb +48 -30
  180. data/test/functional/ft_19_participant_code.rb +67 -0
  181. data/test/functional/ft_1_process_status.rb +222 -150
  182. data/test/functional/ft_20_storage_participant.rb +445 -44
  183. data/test/functional/ft_21_forget.rb +21 -26
  184. data/test/functional/ft_22_process_definitions.rb +8 -6
  185. data/test/functional/ft_23_load_defs.rb +29 -5
  186. data/test/functional/ft_24_block_participant.rb +199 -20
  187. data/test/functional/ft_25_receiver.rb +98 -46
  188. data/test/functional/ft_26_participant_rtimeout.rb +34 -26
  189. data/test/functional/ft_27_var_indirection.rb +40 -5
  190. data/test/functional/ft_28_null_noop_participants.rb +5 -5
  191. data/test/functional/ft_29_part_template.rb +2 -2
  192. data/test/functional/ft_2_errors.rb +106 -74
  193. data/test/functional/ft_30_smtp_participant.rb +7 -7
  194. data/test/functional/ft_31_part_blocking.rb +11 -11
  195. data/test/functional/ft_32_scope.rb +50 -0
  196. data/test/functional/ft_33_participant_subprocess_priority.rb +3 -3
  197. data/test/functional/ft_34_cursor_rewind.rb +14 -14
  198. data/test/functional/ft_35_add_service.rb +67 -9
  199. data/test/functional/ft_36_storage_history.rb +92 -24
  200. data/test/functional/ft_37_default_history.rb +35 -23
  201. data/test/functional/ft_38_participant_more.rb +189 -32
  202. data/test/functional/ft_39_wait_for.rb +25 -25
  203. data/test/functional/ft_3_participant_registration.rb +235 -107
  204. data/test/functional/ft_40_wait_logger.rb +105 -18
  205. data/test/functional/ft_41_participants.rb +13 -12
  206. data/test/functional/ft_42_storage_copy.rb +12 -12
  207. data/test/functional/ft_43_participant_on_reply.rb +85 -11
  208. data/test/functional/ft_44_var_participant.rb +5 -5
  209. data/test/functional/ft_45_participant_accept.rb +3 -3
  210. data/test/functional/ft_46_launch_single.rb +17 -17
  211. data/test/functional/ft_47_wfids.rb +41 -0
  212. data/test/functional/ft_48_lose.rb +19 -25
  213. data/test/functional/ft_49_engine_on_error.rb +54 -70
  214. data/test/functional/ft_4_cancel.rb +84 -26
  215. data/test/functional/ft_50_engine_config.rb +4 -4
  216. data/test/functional/ft_51_misc.rb +12 -12
  217. data/test/functional/ft_52_case.rb +17 -17
  218. data/test/functional/ft_53_engine_on_terminate.rb +18 -21
  219. data/test/functional/ft_54_patterns.rb +18 -16
  220. data/test/functional/ft_55_engine_participant.rb +55 -55
  221. data/test/functional/ft_56_filter_attribute.rb +90 -52
  222. data/test/functional/ft_57_rev_participant.rb +252 -0
  223. data/test/functional/ft_58_workitem.rb +150 -0
  224. data/test/functional/ft_59_pause.rb +329 -0
  225. data/test/functional/ft_5_on_error.rb +430 -77
  226. data/test/functional/ft_60_code_participant.rb +65 -0
  227. data/test/functional/ft_61_trailing_fields.rb +34 -0
  228. data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
  229. data/test/functional/ft_63_participants_221.rb +458 -0
  230. data/test/functional/ft_64_stash.rb +41 -0
  231. data/test/functional/ft_65_timers.rb +313 -0
  232. data/test/functional/ft_66_flank.rb +133 -0
  233. data/test/functional/ft_67_radial_misc.rb +34 -0
  234. data/test/functional/ft_68_reput.rb +72 -0
  235. data/test/functional/ft_69_worker_info.rb +56 -0
  236. data/test/functional/ft_6_on_cancel.rb +189 -36
  237. data/test/functional/ft_70_take_and_discard_attributes.rb +94 -0
  238. data/test/functional/ft_71_retries.rb +144 -0
  239. data/test/functional/ft_72_on_terminate.rb +60 -0
  240. data/test/functional/ft_73_raise_msg.rb +107 -0
  241. data/test/functional/ft_74_respark.rb +106 -0
  242. data/test/functional/ft_75_context.rb +66 -0
  243. data/test/functional/ft_76_observer.rb +53 -0
  244. data/test/functional/ft_77_process_observer.rb +157 -0
  245. data/test/functional/ft_78_part_participant.rb +37 -0
  246. data/test/functional/ft_7_tags.rb +238 -50
  247. data/test/functional/ft_8_participant_consumption.rb +27 -21
  248. data/test/functional/ft_9_subprocesses.rb +48 -18
  249. data/test/functional/restart_base.rb +4 -6
  250. data/test/functional/rt_0_wait.rb +10 -10
  251. data/test/functional/rt_1_listen.rb +6 -6
  252. data/test/functional/rt_2_errors.rb +12 -12
  253. data/test/functional/rt_3_once.rb +17 -12
  254. data/test/functional/rt_4_cron.rb +17 -17
  255. data/test/functional/rt_5_timeout.rb +13 -13
  256. data/test/functional/signals.rb +103 -0
  257. data/test/functional/storage.rb +730 -0
  258. data/test/functional/storage_helper.rb +48 -35
  259. data/test/functional/test.rb +6 -2
  260. data/test/misc/idle.rb +21 -0
  261. data/test/misc/light.rb +29 -0
  262. data/test/path_helper.rb +1 -1
  263. data/test/test.rb +2 -5
  264. data/test/test_helper.rb +13 -0
  265. data/test/unit/test.rb +1 -4
  266. data/test/unit/ut_0_ruby_reader.rb +25 -9
  267. data/test/unit/ut_10_participants.rb +47 -0
  268. data/test/unit/ut_11_lookup.rb +59 -2
  269. data/test/unit/ut_12_wait_logger.rb +123 -0
  270. data/test/unit/ut_14_is_uri.rb +1 -1
  271. data/test/unit/ut_15_util.rb +1 -1
  272. data/test/unit/ut_16_reader.rb +136 -14
  273. data/test/unit/ut_17_merge.rb +155 -0
  274. data/test/unit/ut_19_part_template.rb +1 -1
  275. data/test/unit/ut_1_fei.rb +11 -2
  276. data/test/unit/ut_20_composite_storage.rb +27 -1
  277. data/test/unit/{ut_21_participant_list.rb → ut_21_svc_participant_list.rb} +2 -3
  278. data/test/unit/ut_22_filter.rb +231 -10
  279. data/test/unit/ut_23_svc_tracker.rb +48 -0
  280. data/test/unit/ut_24_radial_reader.rb +458 -0
  281. data/test/unit/ut_25_process_status.rb +143 -0
  282. data/test/unit/ut_26_deep.rb +131 -0
  283. data/test/unit/ut_2_dashboard.rb +114 -0
  284. data/test/unit/ut_3_worker.rb +54 -0
  285. data/test/unit/ut_4_expmap.rb +1 -1
  286. data/test/unit/ut_5_tree.rb +23 -23
  287. data/test/unit/ut_6_condition.rb +71 -29
  288. data/test/unit/ut_7_workitem.rb +18 -4
  289. data/test/unit/ut_8_tree_to_dot.rb +1 -1
  290. data/test/unit/ut_9_xml_reader.rb +1 -1
  291. metadata +142 -63
  292. data/jruby_issue.txt +0 -32
  293. data/lib/ruote/engine/process_status.rb +0 -403
  294. data/lib/ruote/log/pretty.rb +0 -165
  295. data/lib/ruote/log/test_logger.rb +0 -204
  296. data/lib/ruote/util/serializer.rb +0 -103
  297. data/phil.txt +0 -14
  298. data/test/functional/eft_33_let.rb +0 -31
  299. data/test/functional/ft_19_alias.rb +0 -33
  300. data/test/functional/ft_47_wfid_generator.rb +0 -54
  301. data/test/unit/storage.rb +0 -403
  302. data/test/unit/storages.rb +0 -37
  303. data/test/unit/ut_13_serializer.rb +0 -65
  304. data/test/unit/ut_18_engine.rb +0 -47
  305. data/test/unit/ut_3_wait_logger.rb +0 -39
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,883 +22,18 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- require 'ruote/context'
26
- require 'ruote/engine/process_status'
27
- require 'ruote/receiver/base'
25
+ require 'ruote/dashboard'
28
26
 
29
27
 
30
28
  module Ruote
31
29
 
32
30
  #
33
- # This class holds the 'engine' name, perhaps 'dashboard' would have been
34
- # a better name. Anyway, the methods here allow to launch processes
35
- # and to query about their status. There are also methods for fixing
36
- # issues with stalled processes or processes stuck in errors.
31
+ # This class has been replaced by Ruote::Dashboard.
37
32
  #
38
- # NOTE : the methods #launch and #reply are implemented in
39
- # Ruote::ReceiverMixin (this Engine class has all the methods of a Receiver).
33
+ # It will be slowly phased out as documentation and tutorials move
34
+ # from Ruote::Engine to Ruote::Dashboard.
40
35
  #
41
- class Engine
42
-
43
- include ReceiverMixin
44
-
45
- attr_reader :context
46
- attr_reader :variables
47
-
48
- # Creates an engine using either worker or storage.
49
- #
50
- # If a storage instance is given as the first argument, the engine will be
51
- # able to manage processes (for example, launch and cancel workflows) but
52
- # will not actually run any workflows.
53
- #
54
- # If a worker instance is given as the first argument and the second
55
- # argument is true, engine will start the worker and will be able to both
56
- # manage and run workflows.
57
- #
58
- # If the second options is set to { :join => true }, the worker wil
59
- # be started and run in the current thread.
60
- #
61
- def initialize(worker_or_storage, opts=true)
62
-
63
- @context = worker_or_storage.context
64
- @context.engine = self
65
-
66
- @variables = EngineVariables.new(@context.storage)
67
-
68
- if @context.worker
69
- if opts == true
70
- @context.worker.run_in_thread
71
- # runs worker in its own thread
72
- elsif opts == { :join => true }
73
- @context.worker.run
74
- # runs worker in current thread (and doesn't return)
75
- #else
76
- # worker is not run
77
- end
78
- #else
79
- # no worker
80
- end
81
- end
82
-
83
- # Returns the storage this engine works with passed at engine
84
- # initialization.
85
- #
86
- def storage
87
-
88
- @context.storage
89
- end
90
-
91
- # Returns the worker nested inside this engine (passed at initialization).
92
- # Returns nil if this engine is only linked to a storage (and the worker
93
- # is running somewhere else (hopefully)).
94
- #
95
- def worker
96
-
97
- @context.worker
98
- end
99
-
100
- # A shortcut for engine.context.history
101
- #
102
- def history
103
-
104
- @context.history
105
- end
106
-
107
- # Quick note : the implementation of launch is found in the module
108
- # Ruote::ReceiverMixin that the engine includes.
109
- #
110
- # Some processes have to have one and only one instance of themselves
111
- # running, these are called 'singles' ('singleton' is too object-oriented).
112
- #
113
- # When called, this method will check if an instance of the pdef is
114
- # already running (it uses the process definition name attribute), if
115
- # yes, it will return without having launched anything. If there is no
116
- # such process running, it will launch it (and register it).
117
- #
118
- # Returns the wfid (workflow instance id) of the running single.
119
- #
120
- def launch_single(process_definition, fields={}, variables={})
121
-
122
- tree = @context.reader.read(process_definition)
123
- name = tree[1]['name'] || (tree[1].find { |k, v| v.nil? } || []).first
124
-
125
- raise ArgumentError.new(
126
- 'process definition is missing a name, cannot launch as single'
127
- ) unless name
128
-
129
- singles = @context.storage.get('variables', 'singles') || {
130
- '_id' => 'singles', 'type' => 'variables', 'h' => {}
131
- }
132
- wfid, timestamp = singles['h'][name]
133
-
134
- return wfid if wfid && (ps(wfid) || Time.now.to_f - timestamp < 1.0)
135
- # return wfid if 'singleton' process is already running
136
-
137
- wfid = @context.wfidgen.generate
138
-
139
- singles['h'][name] = [ wfid, Time.now.to_f ]
140
-
141
- r = @context.storage.put(singles)
142
-
143
- return launch_single(tree, fields, variables) unless r.nil?
144
- #
145
- # the put failed, back to the start...
146
- #
147
- # all this to prevent races between multiple engines,
148
- # multiple launch_single calls (from different Ruby runtimes)
149
-
150
- # ... green for launch
151
-
152
- @context.storage.put_msg(
153
- 'launch',
154
- 'wfid' => wfid,
155
- 'tree' => tree,
156
- 'workitem' => { 'fields' => fields },
157
- 'variables' => variables)
158
-
159
- wfid
160
- end
161
-
162
- # Given a workitem or a fei, will do a cancel_expression,
163
- # else it's a wfid and it does a cancel_process.
164
- #
165
- def cancel(wi_or_fei_or_wfid)
166
-
167
- target = Ruote.extract_id(wi_or_fei_or_wfid)
168
-
169
- if target.is_a?(String)
170
- @context.storage.put_msg('cancel_process', 'wfid' => target)
171
- else
172
- @context.storage.put_msg('cancel', 'fei' => target)
173
- end
174
- end
175
-
176
- alias cancel_process cancel
177
- alias cancel_expression cancel
178
-
179
- # Given a workitem or a fei, will do a kill_expression,
180
- # else it's a wfid and it does a kill_process.
181
- #
182
- def kill(wi_or_fei_or_wfid)
183
-
184
- target = Ruote.extract_id(wi_or_fei_or_wfid)
185
-
186
- if target.is_a?(String)
187
- @context.storage.put_msg('kill_process', 'wfid' => target)
188
- else
189
- @context.storage.put_msg('cancel', 'fei' => target, 'flavour' => 'kill')
190
- end
191
- end
192
-
193
- alias kill_process kill
194
- alias kill_expression kill
195
-
196
- # Replays at a given error (hopefully you fixed the cause of the error
197
- # before replaying...)
198
- #
199
- def replay_at_error(err)
200
-
201
- msg = err.msg.dup
202
- action = msg.delete('action')
203
-
204
- msg['replay_at_error'] = true
205
- # just an indication
206
-
207
- if msg['tree'] && fei = msg['fei']
208
- #
209
- # nukes the expression in case of [re]apply
210
- #
211
- exp = Ruote::Exp::FlowExpression.fetch(@context, fei)
212
- exp.unpersist_or_raise if exp
213
- end
214
-
215
- @context.storage.delete(err.to_h) # remove error
216
-
217
- @context.storage.put_msg(action, msg) # trigger replay
218
- end
219
-
220
- # Re-applies an expression (given via its FlowExpressionId).
221
- #
222
- # That will cancel the expression and, once the cancel operation is over
223
- # (all the children have been cancelled), the expression will get
224
- # re-applied.
225
- #
226
- # == options
227
- #
228
- # :tree is used to completely change the tree of the expression at re_apply
229
- #
230
- # engine.re_apply(fei, :tree => [ 'participant', { 'ref' => 'bob' }, [] ])
231
- #
232
- # :fields is used to replace the fields of the workitem at re_apply
233
- #
234
- # engine.re_apply(fei, :fields => { 'customer' => 'bob' })
235
- #
236
- # :merge_in_fields is used to add / override fields
237
- #
238
- # engine.re_apply(fei, :merge_in_fields => { 'customer' => 'bob' })
239
- #
240
- def re_apply(fei, opts={})
241
-
242
- @context.storage.put_msg('cancel', 'fei' => fei.to_h, 're_apply' => opts)
243
- end
244
-
245
- # Returns a ProcessStatus instance describing the current status of
246
- # a process instance.
247
- #
248
- def process(wfid)
249
-
250
- statuses([ wfid ], {}).first
251
- end
252
-
253
- # Returns an array of ProcessStatus instances.
254
- #
255
- # WARNING : this is an expensive operation, but it understands :skip
256
- # and :limit, so pagination is our friend.
257
- #
258
- # Please note, if you're interested only in processes that have errors,
259
- # Engine#errors is a more efficient means.
260
- #
261
- # To simply list the wfids of the currently running, Engine#process_wfids
262
- # is way cheaper to call.
263
- #
264
- def processes(opts={})
265
-
266
- wfids = @context.storage.expression_wfids(opts)
267
-
268
- opts[:count] ? wfids.size : statuses(wfids, opts)
269
- end
270
-
271
- # Returns a list of processes or the process status of a given process
272
- # instance.
273
- #
274
- def ps(wfid=nil)
275
-
276
- wfid == nil ? processes : process(wfid)
277
- end
278
-
279
- # Returns an array of current errors (hashes)
280
- #
281
- # Can be called in two ways :
282
- #
283
- # engine.errors(wfid)
284
- #
285
- # and
286
- #
287
- # engine.errors(:skip => 100, :limit => 100)
288
- #
289
- def errors(wfid=nil)
290
-
291
- wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
292
-
293
- errs = wfid.nil? ?
294
- @context.storage.get_many('errors', nil, options) :
295
- @context.storage.get_many('errors', wfid)
296
-
297
- return errs if options[:count]
298
-
299
- errs.collect { |err| ProcessError.new(err) }
300
- end
301
-
302
- # Returns an array of schedules. Those schedules are open structs
303
- # with various properties, like target, owner, at, put_at, ...
304
- #
305
- # Introduced mostly for ruote-kit.
306
- #
307
- # Can be called in two ways :
308
- #
309
- # engine.schedules(wfid)
310
- #
311
- # and
312
- #
313
- # engine.schedules(:skip => 100, :limit => 100)
314
- #
315
- def schedules(wfid=nil)
316
-
317
- wfid, options = wfid.is_a?(Hash) ? [ nil, wfid ] : [ wfid, {} ]
318
-
319
- scheds = wfid.nil? ?
320
- @context.storage.get_many('schedules', nil, options) :
321
- @context.storage.get_many('schedules', /!#{wfid}-\d+$/)
322
-
323
- return scheds if options[:count]
324
-
325
- scheds.collect { |s| Ruote.schedule_to_h(s) }.sort_by { |s| s['wfid'] }
326
- end
327
-
328
- # Returns a [sorted] list of wfids of the process instances currently
329
- # running in the engine.
330
- #
331
- # This operation is substantially less costly than Engine#processes (though
332
- # the 'how substantially' depends on the storage chosen).
333
- #
334
- def process_ids
335
-
336
- @context.storage.expression_wfids({})
337
- end
338
-
339
- alias process_wfids process_ids
340
-
341
- # Warning : expensive operation.
342
- #
343
- # Leftovers are workitems, errors and schedules belonging to process
344
- # instances for which there are no more expressions left.
345
- #
346
- # Better delete them or investigate why they are left here.
347
- #
348
- # The result is a list of documents (hashes) as found in the storage. Each
349
- # of them might represent a workitem, an error or a schedule.
350
- #
351
- # If you want to delete one of them you can do
352
- #
353
- # engine.storage.delete(doc)
354
- #
355
- def leftovers
356
-
357
- wfids = @context.storage.expression_wfids({})
358
-
359
- wis = @context.storage.get_many('workitems').compact
360
- ers = @context.storage.get_many('errors').compact
361
- scs = @context.storage.get_many('schedules').compact
362
- # some slow storages need the compaction... [c]ouch...
363
-
364
- (wis + ers + scs).reject { |doc| wfids.include?(doc['fei']['wfid']) }
365
- end
366
-
367
- # Shuts down the engine, mostly passes the shutdown message to the other
368
- # services and hope they'll shut down properly.
369
- #
370
- def shutdown
371
-
372
- @context.shutdown
373
- end
374
-
375
- # This method expects there to be a logger with a wait_for method in the
376
- # context, else it will raise an exception.
377
- #
378
- # *WARNING* : wait_for() is meant for environments where there is a unique
379
- # worker and that worker is nested in this engine. In a multiple worker
380
- # environment wait_for doesn't see events handled by 'other' workers.
381
- #
382
- # This method is only useful for test/quickstart/examples environments.
383
- #
384
- # engine.wait_for(:alpha)
385
- # # will make the current thread block until a workitem is delivered
386
- # # to the participant named 'alpha'
387
- #
388
- # engine.wait_for('123432123-9043')
389
- # # will make the current thread block until the processed whose
390
- # # wfid is given (String) terminates or produces an error.
391
- #
392
- # engine.wait_for(5)
393
- # # will make the current thread block until 5 messages have been
394
- # # processed on the workqueue...
395
- #
396
- # engine.wait_for(:empty)
397
- # # will return as soon as the engine/storage is empty, ie as soon
398
- # # as there are no more processes running in the engine (no more
399
- # # expressions placed in the storage)
400
- #
401
- # It's OK to wait for multiple wfids :
402
- #
403
- # engine.wait_for('20100612-bezerijozo', '20100612-yakisoba')
404
- #
405
- def wait_for(*items)
406
-
407
- logger = @context['s_logger']
408
-
409
- raise(
410
- "can't wait_for, there is no logger that responds to that call"
411
- ) unless logger.respond_to?(:wait_for)
412
-
413
- logger.wait_for(items)
414
- end
415
-
416
- # Joins the worker thread. If this engine has no nested worker, calling
417
- # this method will simply return immediately.
418
- #
419
- def join
420
-
421
- worker.join if worker
422
- end
423
-
424
- # Loads (and turns into a tree) the process definition at the given path.
425
- #
426
- def load_definition(path)
427
-
428
- @context.reader.read(path)
429
- end
430
-
431
- # Registers a participant in the engine.
432
- #
433
- # Takes the form
434
- #
435
- # engine.register_participant name_or_regex, klass, opts={}
436
- #
437
- # With the form
438
- #
439
- # engine.register_participant name_or_regex do |workitem|
440
- # # ...
441
- # end
442
- #
443
- # A BlockParticipant is automatically created.
444
- #
445
- #
446
- # == name or regex
447
- #
448
- # When registering participants, strings or regexes are accepted. Behind
449
- # the scenes, a regex is kept.
450
- #
451
- # Passing a string like "alain" will get ruote to automatically turn it
452
- # into the following regex : /^alain$/.
453
- #
454
- # For finer control over this, pass a regex directly
455
- #
456
- # engine.register_participant /^user-/, MyParticipant
457
- # # will match all workitems whose participant name starts with "user-"
458
- #
459
- #
460
- # == some examples
461
- #
462
- # engine.register_participant 'compute_sum' do |wi|
463
- # wi.fields['sum'] = wi.fields['articles'].inject(0) do |s, (c, v)|
464
- # s + c * v # sum + count * value
465
- # end
466
- # # a block participant implicitely replies to the engine immediately
467
- # end
468
- #
469
- # class MyParticipant
470
- # def initialize(opts)
471
- # @name = opts['name']
472
- # end
473
- # def consume(workitem)
474
- # workitem.fields['rocket_name'] = @name
475
- # send_to_the_moon(workitem)
476
- # end
477
- # def cancel(fei, flavour)
478
- # # do nothing
479
- # end
480
- # end
481
- #
482
- # engine.register_participant(
483
- # /^moon-.+/, MyParticipant, 'name' => 'Saturn-V')
484
- #
485
- # # computing the total for a invoice being passed in the workitem.
486
- # #
487
- # class TotalParticipant
488
- # include Ruote::LocalParticipant
489
- #
490
- # def consume(workitem)
491
- # workitem['total'] = workitem.fields['items'].inject(0.0) { |t, item|
492
- # t + item['count'] * PricingService.lookup(item['id'])
493
- # }
494
- # reply_to_engine(workitem)
495
- # end
496
- # end
497
- # engine.register_participant 'total', TotalParticipant
498
- #
499
- # Remember that the options (the hash that follows the class name), must be
500
- # serializable via JSON.
501
- #
502
- #
503
- # == require_path and load_path
504
- #
505
- # It's OK to register a participant by passing its full classname as a
506
- # String.
507
- #
508
- # engine.register_participant(
509
- # 'auditor', 'AuditParticipant', 'require_path' => 'part/audit.rb')
510
- # engine.register_participant(
511
- # 'auto_decision', 'DecParticipant', 'load_path' => 'part/dec.rb')
512
- #
513
- # Note the option load_path / require_path that point to the ruby file
514
- # containing the participant implementation. 'require' will load and eval
515
- # the ruby code only once, 'load' each time.
516
- #
517
- def register_participant(regex, participant=nil, opts={}, &block)
518
-
519
- if participant.is_a?(Hash)
520
- opts = participant
521
- participant = nil
522
- end
523
-
524
- pa = @context.plist.register(regex, participant, opts, block)
525
-
526
- @context.storage.put_msg(
527
- 'participant_registered',
528
- 'regex' => regex.is_a?(Regexp) ? regex.inspect : regex.to_s)
529
-
530
- pa
531
- end
532
-
533
- # A shorter version of #register_participant
534
- #
535
- # engine.register 'alice', MailParticipant, :target => 'alice@example.com'
536
- #
537
- # or a block registering mechanism.
538
- #
539
- # engine.register do
540
- # alpha 'Participants::Alpha', 'flavour' => 'vanilla'
541
- # participant 'bravo', 'Participants::Bravo', :flavour => 'peach'
542
- # catchall ParticipantCharlie, 'flavour' => 'coconut'
543
- # end
544
- #
545
- # Originally implemented in ruote-kit by Torsten Schoenebaum.
546
- #
547
- def register(*args, &block)
548
-
549
- if args.size > 0
550
- register_participant(*args, &block)
551
- else
552
- proxy = ParticipantRegistrationProxy.new(self)
553
- block.arity < 1 ? proxy.instance_eval(&block) : block.call(proxy)
554
- end
555
- end
556
-
557
- # Removes/unregisters a participant from the engine.
558
- #
559
- def unregister_participant(name_or_participant)
560
-
561
- re = @context.plist.unregister(name_or_participant)
562
-
563
- raise(ArgumentError.new('participant not found')) unless re
564
-
565
- @context.storage.put_msg(
566
- 'participant_unregistered',
567
- 'regex' => re.to_s)
568
- end
569
-
570
- alias :unregister :unregister_participant
571
-
572
- # Returns a list of Ruote::ParticipantEntry instances.
573
- #
574
- # engine.register_participant :alpha, MyParticipant, 'message' => 'hello'
575
- #
576
- # # interrogate participant list
577
- # #
578
- # list = engine.participant_list
579
- # participant = list.first
580
- # p participant.regex
581
- # # => "^alpha$"
582
- # p participant.classname
583
- # # => "MyParticipant"
584
- # p participant.options
585
- # # => {"message"=>"hello"}
586
- #
587
- # # update participant list
588
- # #
589
- # participant.regex = '^alfred$'
590
- # engine.participant_list = list
591
- #
592
- def participant_list
593
-
594
- @context.plist.list
595
- end
596
-
597
- # Accepts a list of Ruote::ParticipantEntry instances or a list of
598
- # [ regex, [ classname, opts ] ] arrays.
599
- #
600
- # See Engine#participant_list
601
- #
602
- # Some examples :
603
- #
604
- # engine.participant_list = [
605
- # [ '^charly$', [ 'Ruote::StorageParticipant', {} ] ],
606
- # [ '.+', [ 'MyDefaultParticipant', { 'default' => true } ]
607
- # ]
608
- #
609
- # This method writes the participant list in one go, it might be easier to
610
- # use than to register participant one by ones.
611
- #
612
- def participant_list=(pl)
613
-
614
- @context.plist.list = pl
615
- end
616
-
617
- # A convenience method for
618
- #
619
- # sp = Ruote::StorageParticipant.new(engine)
620
- #
621
- # simply do
622
- #
623
- # sp = engine.storage_participant
624
- #
625
- def storage_participant
626
-
627
- @storage_participant ||= Ruote::StorageParticipant.new(self)
628
- end
629
-
630
- # Returns an instance of the participant registered under the given name.
631
- # Returns nil if there is no participant registered for that name.
632
- #
633
- def participant(name)
634
-
635
- @context.plist.lookup(name, nil)
636
- end
637
-
638
- # Adds a service locally (will not get propagated to other workers).
639
- #
640
- # tracer = Tracer.new
641
- # @engine.add_service('tracer', tracer)
642
- #
643
- # or
644
- #
645
- # @engine.add_service('tracer', 'ruote/exp/tracer', 'Ruote::Exp::Tracer')
646
- #
647
- # This method returns the service instance it just bound.
648
- #
649
- def add_service(name, path_or_instance, classname=nil, opts=nil)
650
-
651
- @context.add_service(name, path_or_instance, classname, opts)
652
- end
653
-
654
- # Sets a configuration option. Examples:
655
- #
656
- # # allow remote workflow definitions (for subprocesses or when launching
657
- # # processes)
658
- # @engine.configure('remote_definition_allowed', true)
659
- #
660
- # # allow ruby_eval
661
- # @engine.configure('ruby_eval_allowed', true)
662
- #
663
- def configure(config_key, value)
664
-
665
- @context[config_key] = value
666
- end
667
-
668
- # Returns a configuration value.
669
- #
670
- # engine.configure('ruby_eval_allowed', true)
671
- #
672
- # p engine.configuration('ruby_eval_allowed')
673
- # # => true
674
- #
675
- def configuration(config_key)
676
-
677
- @context[config_key]
678
- end
679
-
680
- # Returns the process tree that is triggered in case of error.
681
- #
682
- # Note that this 'on_error' doesn't trigger if an on_error is defined
683
- # in the process itself.
684
- #
685
- # Returns nil if there is no 'on_error' set.
686
- #
687
- def on_error
688
-
689
- @context.storage.get_trackers['trackers']['on_error']['msg']['tree']
690
-
691
- rescue
692
- nil
693
- end
694
-
695
- # Returns the process tree that is triggered in case of process termination.
696
- #
697
- # Note that a termination process doesn't raise a termination process when
698
- # it terminates itself.
699
- #
700
- # Returns nil if there is no 'on_terminate' set.
701
- #
702
- def on_terminate
703
-
704
- @context.storage.get_trackers['trackers']['on_terminate']['msg']['tree']
705
-
706
- rescue
707
- nil
708
- end
709
-
710
- # Sets a participant or subprocess to be triggered when an error occurs
711
- # in a process instance.
712
- #
713
- # engine.on_error = participant_name
714
- #
715
- # engine.on_error = subprocess_name
716
- #
717
- # engine.on_error = Ruote.process_definition do
718
- # alpha
719
- # end
720
- #
721
- # Note that this 'on_error' doesn't trigger if an on_error is defined
722
- # in the process itself.
723
- #
724
- def on_error=(target)
725
-
726
- @context.tracker.add_tracker(
727
- nil, # do not track a specific wfid
728
- 'error_intercepted', # react on 'error_intercepted' msgs
729
- 'on_error', # the identifier
730
- nil, # no specific condition
731
- { 'action' => 'launch',
732
- 'wfid' => 'replace',
733
- 'tree' => target.is_a?(String) ?
734
- [ 'define', {}, [ [ target, {}, [] ] ] ] : target,
735
- 'workitem' => 'replace',
736
- 'variables' => 'compile' })
737
- end
738
-
739
- # Sets a participant or a subprocess that is to be launched/called whenever
740
- # a regular process terminates.
741
- #
742
- # engine.on_terminate = participant_name
743
- #
744
- # engine.on_terminate = subprocess_name
745
- #
746
- # engine.on_terminate = Ruote.define do
747
- # alpha
748
- # bravo
749
- # end
750
- #
751
- # Note that a termination process doesn't raise a termination process when
752
- # it terminates itself.
753
- #
754
- # on_terminate processes are not triggered for on_error processes.
755
- # on_error processes are triggered for on_terminate processes as well.
756
- #
757
- def on_terminate=(target)
758
-
759
- @context.tracker.add_tracker(
760
- nil, # do not track a specific wfid
761
- 'terminated', # react on 'error_intercepted' msgs
762
- 'on_terminate', # the identifier
763
- nil, # no specific condition
764
- { 'action' => 'launch',
765
- 'tree' => target.is_a?(String) ?
766
- [ 'define', {}, [ [ target, {}, [] ] ] ] : target,
767
- 'workitem' => 'replace' })
768
- end
769
-
770
- # A debug helper :
771
- #
772
- # engine.noisy = true
773
- #
774
- # will let the engine (in fact the worker) pour all the details of the
775
- # executing process instances to STDOUT.
776
- #
777
- def noisy=(b)
778
-
779
- @context.logger.noisy = b
780
- end
781
-
782
- protected
783
-
784
- # Used by #process and #processes
785
- #
786
- def statuses(wfids, opts)
787
-
788
- swfids = wfids.collect { |wfid| /!#{wfid}-\d+$/ }
789
-
790
- exps = @context.storage.get_many('expressions', wfids).compact
791
- swis = @context.storage.get_many('workitems', wfids).compact
792
- errs = @context.storage.get_many('errors', wfids).compact
793
- schs = @context.storage.get_many('schedules', swfids).compact
794
- # some slow storages need the compaction... couch...
795
-
796
- errs = errs.collect { |err| ProcessError.new(err) }
797
- schs = schs.collect { |sch| Ruote.schedule_to_h(sch) }
798
-
799
- by_wfid = {}
800
-
801
- exps.each do |exp|
802
- (by_wfid[exp['fei']['wfid']] ||= [ [], [], [], [] ])[0] << exp
803
- end
804
- swis.each do |swi|
805
- (by_wfid[swi['fei']['wfid']] ||= [ [], [], [], [] ])[1] << swi
806
- end
807
- errs.each do |err|
808
- (by_wfid[err.wfid] ||= [ [], [], [], [] ])[2] << err
809
- end
810
- schs.each do |sch|
811
- (by_wfid[sch['wfid']] ||= [ [], [], [], [] ])[3] << sch
812
- end
813
-
814
- wfids = by_wfid.keys.sort
815
- wfids = wfids.reverse if opts[:descending]
816
- # re-adjust list of wfids, only take what was found
817
-
818
- wfids.inject([]) { |a, wfid|
819
- info = by_wfid[wfid]
820
- a << ProcessStatus.new(@context, *info) if info
821
- a
822
- }
823
- end
824
- end
825
-
826
- #
827
- # A wrapper class giving easy access to engine variables.
828
- #
829
- # There is one instance of this class for an Engine instance. It is
830
- # returned when calling Engine#variables.
831
- #
832
- class EngineVariables
833
-
834
- def initialize(storage)
835
-
836
- @storage = storage
837
- end
838
-
839
- def [](k)
840
-
841
- @storage.get_engine_variable(k)
842
- end
843
-
844
- def []=(k, v)
845
-
846
- @storage.put_engine_variable(k, v)
847
- end
848
- end
849
-
850
- #
851
- # Engine#register uses this proxy when it's passed a block.
852
- #
853
- # Originally written by Torsten Schoenebaum for ruote-kit.
854
- #
855
- class ParticipantRegistrationProxy
856
-
857
- def initialize(engine)
858
-
859
- @engine = engine
860
- end
861
-
862
- def participant(name, klass=nil, options={}, &block)
863
-
864
- @engine.register_participant(name, klass, options, &block)
865
- end
866
-
867
- def catchall(*args)
868
-
869
- klass = args.empty? ? Ruote::StorageParticipant : args.first
870
- options = args[1] || {}
871
-
872
- participant('.+', klass, options)
873
- end
874
-
875
- # Maybe a bit audacious...
876
- #
877
- def method_missing(method_name, *args)
878
-
879
- participant(method_name, *args)
880
- end
881
- end
882
-
883
- # Refines a schedule as found in the ruote storage into something a bit
884
- # easier to present.
885
- #
886
- def self.schedule_to_h(sched)
887
-
888
- h = sched.dup
889
-
890
- h.delete('_rev')
891
- h.delete('type')
892
- msg = h.delete('msg')
893
- owner = h.delete('owner')
894
-
895
- h['wfid'] = owner['wfid']
896
- h['action'] = msg['action']
897
- h['type'] = msg['flavour']
898
- h['owner'] = Ruote::FlowExpressionId.new(owner)
899
- h['target'] = Ruote::FlowExpressionId.new(msg['fei'])
900
-
901
- h
36
+ class Engine < Dashboard
902
37
  end
903
38
  end
904
39