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
@@ -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
@@ -50,7 +50,7 @@ module Ruote
50
50
  "#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
51
51
  end
52
52
 
53
- # Returns a parsable representation of the UTC time now.
53
+ # Returns a parseable representation of the UTC time now.
54
54
  #
55
55
  # like "2009/11/23 11:11:50.947109 UTC"
56
56
  #
@@ -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
@@ -47,12 +47,74 @@ module Ruote
47
47
  # # 0_0_0 alpha {}
48
48
  # # 0_0_1 bravo {}
49
49
  #
50
- def Ruote.tree_to_s(tree, expid='0')
50
+ def self.tree_to_s(tree, expid='0')
51
51
 
52
52
  d = expid.split('_').size - 1
53
53
  s = "#{' ' * d * 2}#{expid} #{tree[0]} #{tree[1].inspect}\n"
54
54
  tree[2].each_with_index { |t, i| s << tree_to_s(t, "#{expid}_#{i}") }
55
55
  s
56
56
  end
57
+
58
+ # Used by Ruote::ProcessStatus.
59
+ #
60
+ # Given a tree
61
+ #
62
+ # [ 'define', { 'name' => 'nada' }, [
63
+ # [ 'sequence', {}, [ [ 'alpha', {}, [] ], [ 'bravo', {}, [] ] ] ]
64
+ # ] ]
65
+ #
66
+ # will output something like
67
+ #
68
+ # { '0' => [ 'define', { 'name' => 'nada' } ],
69
+ # '0_0' => [ 'sequence', {} ],
70
+ # '0_0_0' => [ 'alpha', {} ],
71
+ # '0_0_1' => [ 'bravo', {} ] },
72
+ #
73
+ # An initial offset can be specifid with the 'pos' argument.
74
+ #
75
+ # Don't touch 'h', it's an accumulator.
76
+ #
77
+ def self.decompose_tree(t, pos='0', h={})
78
+
79
+ h[pos] = t[0, 2]
80
+ t[2].each_with_index { |c, i| decompose_tree(c, "#{pos}_#{i}", h) }
81
+ h
82
+ end
83
+
84
+ # Used by Ruote::ProcessStatus.
85
+ #
86
+ # Given a decomposed tree like
87
+ #
88
+ # { '0' => [ 'define', { 'name' => 'nada' } ],
89
+ # '0_0' => [ 'sequence', {} ],
90
+ # '0_0_0' => [ 'alpha', {} ],
91
+ # '0_0_1' => [ 'bravo', {} ] },
92
+ #
93
+ # will recompose it to
94
+ #
95
+ # [ 'define', { 'name' => 'nada' }, [
96
+ # [ 'sequence', {}, [ [ 'alpha', {}, [] ], [ 'bravo', {}, [] ] ] ]
97
+ # ] ]
98
+ #
99
+ # A starting point in the recomposition can be given with the 'pos' argument.
100
+ #
101
+ def self.recompose_tree(h, pos='0')
102
+
103
+ t = h[pos]
104
+
105
+ return nil unless t
106
+
107
+ t << []
108
+ i = 0
109
+
110
+ loop do
111
+ tt = recompose_tree(h, "#{pos}_#{i}")
112
+ break unless tt
113
+ t.last << tt
114
+ i = i + 1
115
+ end
116
+
117
+ t
118
+ end
57
119
  end
58
120
 
@@ -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
@@ -23,6 +23,7 @@
23
23
  #++
24
24
 
25
25
  module Ruote
26
- VERSION = '2.2.0'
26
+
27
+ VERSION = '2.3.0'
27
28
  end
28
29
 
@@ -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
@@ -34,37 +34,56 @@ module Ruote
34
34
  #
35
35
  class Worker
36
36
 
37
- EXP_ACTIONS = %w[ reply cancel fail receive dispatched ]
37
+ EXP_ACTIONS = %w[ reply cancel fail receive dispatched pause resume ]
38
38
  # 'apply' is comprised in 'launch'
39
39
  # 'receive' is a ParticipantExpression alias for 'reply'
40
40
 
41
- PROC_ACTIONS = %w[ cancel_process kill_process ]
42
- DISP_ACTIONS = %w[ dispatch dispatch_cancel ]
41
+ PROC_ACTIONS = %w[ cancel kill pause resume ].collect { |a| a + '_process' }
42
+ DISP_ACTIONS = %w[ dispatch dispatch_cancel dispatch_pause dispatch_resume ]
43
+
44
+ attr_reader :name
43
45
 
44
46
  attr_reader :storage
45
47
  attr_reader :context
46
48
 
49
+ attr_reader :state
47
50
  attr_reader :run_thread
48
- attr_reader :running
49
51
 
50
52
  # Given a storage, creates a new instance of a Worker.
51
53
  #
52
- def initialize(storage)
54
+ def initialize(name, storage=nil)
55
+
56
+ if storage.nil?
57
+ storage = name
58
+ name = nil
59
+ end
60
+
61
+ @name = name || 'worker'
62
+
63
+ if storage.respond_to?(:storage)
64
+ @storage = storage.storage
65
+ @context = storage.context
66
+ else
67
+ @storage = storage
68
+ @context = Ruote::Context.new(storage)
69
+ end
53
70
 
54
- @subscribers = []
55
- # must be ready before the storage is created
56
- # services like Logger to subscribe to the worker
71
+ service_name = @name
72
+ service_name << '_worker' unless service_name.match(/worker$/)
57
73
 
58
- @storage = storage
59
- @context = Ruote::Context.new(storage, self)
74
+ @context.add_service(service_name, self)
60
75
 
61
76
  @last_time = Time.at(0.0).utc # 1970...
62
77
 
63
- @running = true
78
+ @state = 'running'
64
79
  @run_thread = nil
80
+ @state_mutex = Mutex.new
65
81
 
66
82
  @msgs = []
67
- @sleep_time = 0.000
83
+
84
+ @sleep_time = @context['restless_worker'] ? nil : 0.000
85
+
86
+ @info = @context['worker_info_enabled'] == false ? nil : Info.new(self)
68
87
  end
69
88
 
70
89
  # Runs the worker in the current thread. See #run_in_thread for running
@@ -72,19 +91,19 @@ module Ruote
72
91
  #
73
92
  def run
74
93
 
75
- step while @running
94
+ step while @state != 'stopped'
76
95
  end
77
96
 
78
97
  # Triggers the run method of the worker in a dedicated thread.
79
98
  #
80
99
  def run_in_thread
81
100
 
82
- Thread.abort_on_exception = true
83
- # TODO : remove me at some point
101
+ #Thread.abort_on_exception = true
84
102
 
85
- @running = true
103
+ @state = 'running'
86
104
 
87
105
  @run_thread = Thread.new { run }
106
+ @run_thread['ruote_worker'] = self
88
107
  end
89
108
 
90
109
  # Joins the run thread of this worker (if there is no such thread, this
@@ -92,15 +111,7 @@ module Ruote
92
111
  #
93
112
  def join
94
113
 
95
- @run_thread.join if @run_thread
96
- end
97
-
98
- # Loggers and trackers call this method when subscribing for events /
99
- # actions in this worker.
100
- #
101
- def subscribe(actions, subscriber)
102
-
103
- @subscribers << [ actions, subscriber ]
114
+ @run_thread.join rescue nil
104
115
  end
105
116
 
106
117
  # Shuts down this worker (makes sure it won't fetch further messages
@@ -108,12 +119,9 @@ module Ruote
108
119
  #
109
120
  def shutdown
110
121
 
111
- @running = false
122
+ @state_mutex.synchronize { @state = 'stopped' }
112
123
 
113
- begin
114
- @run_thread.join
115
- rescue Exception => e
116
- end
124
+ join
117
125
  end
118
126
 
119
127
  # Returns true if the engine system is inactive, ie if all the process
@@ -144,70 +152,192 @@ module Ruote
144
152
 
145
153
  protected
146
154
 
147
- # One worker step, fetches schedules and triggers those whose time has
148
- # came, then fetches msgs and processes them.
155
+ # If worker_state_enabled is set, check for a potential new state.
149
156
  #
150
- def step
157
+ def determine_state
158
+
159
+ @state_mutex.synchronize do
160
+
161
+ @state = (
162
+ @storage.get('variables', 'worker') || { 'state' => 'running' }
163
+ )['state'] if @state != 'stopped' && @context['worker_state_enabled']
164
+ end
165
+ end
166
+
167
+ # Gets schedules from the storage and if their time has come,
168
+ # turns them into msg (for immediate execution).
169
+ #
170
+ def process_schedules
151
171
 
152
172
  now = Time.now.utc
153
173
  delta = now - @last_time
154
174
 
155
- if delta >= 0.8
175
+ return if delta < 0.8
156
176
  #
157
- # at most once per second, deal with 'ats' and 'crons'
177
+ # consider schedules at most twice per second (don't do that job
178
+ # too often)
158
179
 
159
- @last_time = now
180
+ @last_time = now
160
181
 
161
- @storage.get_schedules(delta, now).each { |sche| trigger(sche) }
162
- end
182
+ @storage.get_schedules(delta, now).each { |s| turn_schedule_to_msg(s) }
183
+ end
163
184
 
164
- # msgs
185
+ # Gets msgs from the storage (if needed) and processes them one by one.
186
+ #
187
+ def process_msgs
165
188
 
166
189
  @msgs = @storage.get_msgs if @msgs.empty?
167
190
 
168
- processed = 0
169
191
  collisions = 0
170
192
 
171
- while msg = @msgs.shift
193
+ while @msg = @msgs.shift
172
194
 
173
- r = process(msg)
195
+ r = process(@msg)
174
196
 
175
197
  if r != false
176
- processed += 1
198
+ @processed_msgs += 1
177
199
  else
178
200
  collisions += 1
179
201
  end
180
202
 
181
203
  if collisions > 2
182
204
  @msgs = @msgs[(@msgs.size / 2)..-1] || []
205
+ collisions = 0
183
206
  end
184
207
 
185
- #@msgs.concat(@storage.get_local_msgs)
208
+ break if Time.now.utc - @last_time >= 0.8
209
+ end
210
+ end
186
211
 
187
- #print r == false ? '*' : '.'
212
+ # Some storage implementations cache information before a step
213
+ # begins, reducing the number of requests to the underlying
214
+ # data system. This begin step notifies the storage that
215
+ # a new step is on and it should refresh the cached information.
216
+ #
217
+ # The storage implementation, if it supports this feature, will cache
218
+ # the information in the thread local info.
219
+ #
220
+ def begin_step
188
221
 
189
- break if Time.now.utc - @last_time >= 0.8
222
+ Thread.current['ruote_worker'] = self
223
+
224
+ @storage.begin_step if @storage.respond_to?(:begin_step)
225
+ end
226
+
227
+ # One worker step, fetches schedules and triggers those whose time has
228
+ # came, then fetches msgs and processes them.
229
+ #
230
+ def step
231
+
232
+ begin_step
233
+
234
+ @msg = nil
235
+ @processed_msgs = 0
236
+
237
+ determine_state
238
+
239
+ if @state == 'stopped'
240
+ return
241
+ elsif @state == 'running'
242
+ process_schedules
243
+ process_msgs
190
244
  end
191
245
 
192
- #p processed
246
+ take_a_rest # 'running' or 'paused'
247
+
248
+ rescue => err
249
+
250
+ handle_step_error(err, @msg) # msg may be nil
251
+ end
252
+
253
+ # This default implementation dumps error information to $stderr as
254
+ # soon as #step intercepts the error.
255
+ #
256
+ # Normally such information should only appear when developing a
257
+ # storage, the information here is thus helpful for storage developers.
258
+ # If such info is emitted in production or in application development,
259
+ # you should pass the info to the storage developer/maintainer.
260
+ #
261
+ # Feel free to override this method if you need it to output to
262
+ # a channel different than $stderr (or rebind $stderr).
263
+ #
264
+ # The second parameter is "msg", if the error occured while processing a
265
+ # msg, then this message is passed to handle_step_error. msg will be
266
+ # nil if the error occurred while doing get_msgs or get_schedules.
267
+ #
268
+ def handle_step_error(err, msg)
269
+
270
+ $stderr.puts '#' * 80
271
+ $stderr.puts
272
+ $stderr.puts '** worker#step intercepted exception **'
273
+ $stderr.puts
274
+ $stderr.puts "Please report issue or fix your #{@storage.class} impl,"
275
+ $stderr.puts
276
+ $stderr.puts "or override Ruote::Worker#handle_step_error(e, msg) so that"
277
+ $stderr.puts "the issue is dealt with appropriately. For example:"
278
+ $stderr.puts
279
+ $stderr.puts " class Ruote::Worker"
280
+ $stderr.puts " def handle_step_error(e, msg)"
281
+ $stderr.puts " logger.error('ruote step error: ' + e.inspect)"
282
+ $stderr.puts " mailer.send_error('admin@acme.com', e)"
283
+ $stderr.puts " end"
284
+ $stderr.puts " end"
285
+ $stderr.puts
286
+ $stderr.puts '# ' * 40
287
+ $stderr.puts
288
+ $stderr.puts 'error class/message/backtrace:'
289
+ $stderr.puts err.class.name
290
+ $stderr.puts err.message.inspect
291
+ $stderr.puts *err.backtrace
292
+ $stderr.puts err.details if err.respond_to?(:details)
293
+ $stderr.puts
294
+ $stderr.puts 'msg:'
295
+ if msg && msg.is_a?(Hash)
296
+ $stderr.puts msg.select { |k, v|
297
+ %w[ action wfid fei ].include?(k)
298
+ }.inspect
299
+ else
300
+ $stderr.puts msg.inspect
301
+ end
302
+ $stderr.puts
303
+ $stderr.puts '#' * 80
304
+
305
+ $stderr.flush
306
+ end
307
+
308
+ # In order not to hammer the storage for msgs too much, take a rest.
309
+ #
310
+ # If the number of processed messages is more than zero, there are probably
311
+ # more msgs coming, no time for a rest...
312
+ #
313
+ # If @sleep_time is nil (restless_worker option set to true), the worker
314
+ # will never rest.
315
+ #
316
+ def take_a_rest
317
+
318
+ return if @sleep_time == nil
319
+
320
+ if @processed_msgs < 1
193
321
 
194
- if processed == 0
195
322
  @sleep_time += 0.001
196
323
  @sleep_time = 0.499 if @sleep_time > 0.499
324
+
197
325
  sleep(@sleep_time)
326
+
198
327
  else
328
+
199
329
  @sleep_time = 0.000
200
330
  end
201
331
  end
202
332
 
203
333
  # Given a schedule, attempts to trigger it.
204
334
  #
205
- # It first tries to
206
- # reserve the schedule. If the reservation fails (another worker
207
- # was successful probably), false is returned. The schedule is
208
- # triggered if the reservation was successful, true is returned.
335
+ # It first tries to reserve the schedule. If the reservation fails
336
+ # (another worker was successful probably), false is returned.
337
+ # The schedule is triggered if the reservation was successful, true
338
+ # is returned.
209
339
  #
210
- def trigger(schedule)
340
+ def turn_schedule_to_msg(schedule)
211
341
 
212
342
  msg = Ruote.fulldup(schedule['msg'])
213
343
 
@@ -234,51 +364,66 @@ module Ruote
234
364
 
235
365
  begin
236
366
 
237
- action = msg['action']
367
+ @context.pre_notify(msg)
368
+
369
+ case msg['action']
370
+
371
+ when 'launch', 'apply', 'regenerate'
372
+
373
+ launch(msg)
238
374
 
239
- if msg['tree']
240
- #
241
- # warning here, it could be a reply, with a 'tree' key...
375
+ when *EXP_ACTIONS
242
376
 
243
- launch(msg)
377
+ Ruote::Exp::FlowExpression.do_action(@context, msg)
244
378
 
245
- elsif EXP_ACTIONS.include?(action)
379
+ when *DISP_ACTIONS
246
380
 
247
- Ruote::Exp::FlowExpression.do_action(@context, msg)
381
+ @context.dispatch_pool.handle(msg)
248
382
 
249
- elsif DISP_ACTIONS.include?(action)
383
+ when *PROC_ACTIONS
250
384
 
251
- @context.dispatch_pool.handle(msg)
385
+ self.send(msg['action'], msg)
252
386
 
253
- elsif PROC_ACTIONS.include?(action)
387
+ when 'reput'
254
388
 
255
- self.send(action, msg)
389
+ reput(msg)
256
390
 
257
- #else
258
- # msg got deleted, might still be interesting for a subscriber
391
+ when 'raise'
392
+
393
+ handle_msg_error(msg['msg'], msg['error'])
394
+
395
+ when 'respark'
396
+
397
+ respark(msg)
398
+
399
+ #else
400
+ # no special processing required for message, let it pass
401
+ # to the subscribers (the notify two lines after)
259
402
  end
260
403
 
261
- notify(msg)
404
+ @context.notify(msg)
405
+ # notify subscribers of successfully processed msgs
262
406
 
263
- rescue => exception
407
+ rescue => err
264
408
 
265
- @context.error_handler.msg_handle(msg, exception)
409
+ handle_msg_error(msg, err)
266
410
  end
267
411
 
412
+ @context.storage.done(msg) if @context.storage.respond_to?(:done)
413
+
414
+ @info << msg if @info
415
+ # for the stats
416
+
268
417
  true
269
418
  end
270
419
 
271
- # Given a successfully executed msg, now notifies all the subscribers
272
- # interested in the kind of action the msg ordered.
420
+ # Passes the msg and the err it resulted in to the error_handler.
273
421
  #
274
- def notify(msg)
275
-
276
- @subscribers.each do |actions, subscriber|
422
+ # Some storage/worker implementation may want to override this.
423
+ #
424
+ def handle_msg_error(msg, err)
277
425
 
278
- if actions == :all || actions.include?(msg['action'])
279
- subscriber.notify(msg)
280
- end
281
- end
426
+ @context.error_handler.msg_handle(msg, err)
282
427
  end
283
428
 
284
429
  # Works for both the 'launch' and the 'apply' msgs.
@@ -290,24 +435,43 @@ module Ruote
290
435
 
291
436
  tree = msg['tree']
292
437
  variables = msg['variables']
438
+ wi = msg['workitem']
293
439
 
294
440
  exp_class = @context.expmap.expression_class(tree.first)
295
441
 
296
442
  # msg['wfid'] only : it's a launch
297
443
  # msg['fei'] : it's a sub launch (a supplant ?)
298
444
 
445
+ if is_launch?(msg, exp_class)
446
+
447
+ name = tree[1]['name'] || tree[1].keys.find { |k| tree[1][k] == nil }
448
+ revision = tree[1]['revision'] || tree[1]['rev']
449
+
450
+ wi['wf_name'] ||= name
451
+ wi['wf_revision'] ||= revision
452
+ wi['wf_launched_at'] ||= Ruote.now_to_utc_s
453
+
454
+ wi['sub_wf_name'] = name
455
+ wi['sub_wf_revision'] = revision
456
+ wi['sub_wf_launched_at'] = Ruote.now_to_utc_s
457
+ end
458
+
299
459
  exp_hash = {
300
460
  'fei' => msg['fei'] || {
301
461
  'engine_id' => @context.engine_id,
302
462
  'wfid' => msg['wfid'],
303
463
  'subid' => Ruote.generate_subid(msg.inspect),
304
- 'expid' => '0' },
464
+ 'expid' => msg['expid'] || '0' },
305
465
  'parent_id' => msg['parent_id'],
306
- 'original_tree' => tree,
307
466
  'variables' => variables,
308
- 'applied_workitem' => msg['workitem'],
309
- 'forgotten' => msg['forgotten']
310
- }
467
+ 'applied_workitem' => wi,
468
+ 'forgotten' => msg['forgotten'],
469
+ 'lost' => msg['lost'],
470
+ 'flanking' => msg['flanking'],
471
+ 'stash' => msg['stash'],
472
+ 'trigger' => msg['trigger'],
473
+ 'on_reply' => msg['on_reply'],
474
+ 'supplanted' => msg['supplanted'] }
311
475
 
312
476
  if not exp_class
313
477
 
@@ -320,19 +484,30 @@ module Ruote
320
484
  exp_class = Ruote::Exp::SequenceExpression
321
485
  end
322
486
 
323
- exp = exp_class.new(@context, exp_hash.merge!('original_tree' => tree))
487
+ exp_hash = exp_hash.reject { |k, v| v.nil? }
488
+ # compact nils away
489
+
490
+ exp_hash['original_tree'] = tree
491
+ # keep track of original tree
492
+
493
+ exp = exp_class.new(@context, exp_hash)
324
494
 
325
495
  exp.initial_persist
326
- exp.do_apply(msg)
496
+ exp.do(:apply, msg)
327
497
  end
328
498
 
329
499
  # Returns true if the msg is a "launch" (ie not a simply "apply").
330
500
  #
331
501
  def is_launch?(msg, exp_class)
332
502
 
333
- return false if exp_class != Ruote::Exp::DefineExpression
334
- return true if msg['action'] == 'launch'
335
- (msg['trigger'] == 'on_re_apply')
503
+ if exp_class != Ruote::Exp::DefineExpression
504
+ false
505
+ elsif %w[ launch regenerate ].include?(msg['action'])
506
+ true
507
+ else
508
+ (msg['trigger'] == 'on_re_apply')
509
+ # let re-apply "define" blocks, as in Ruote.define {}
510
+ end
336
511
  end
337
512
 
338
513
  # Handles a 'cancel_process' msg (translates it into a "cancel root
@@ -346,16 +521,170 @@ module Ruote
346
521
 
347
522
  return unless root
348
523
 
349
- flavour = (msg['action'] == 'kill_process') ? 'kill' : nil
350
-
351
524
  @storage.put_msg(
352
525
  'cancel',
353
526
  'fei' => root['fei'],
354
527
  'wfid' => msg['wfid'], # indicates this was triggered by cancel_process
355
- 'flavour' => flavour)
528
+ 'flavour' => msg['flavour'])
356
529
  end
357
530
 
358
531
  alias kill_process cancel_process
532
+
533
+ # Handles 'pause_process' and 'resume_process'.
534
+ #
535
+ def pause_process(msg)
536
+
537
+ root = @storage.find_root_expression(msg['wfid'])
538
+
539
+ return unless root
540
+
541
+ @storage.put_msg(
542
+ msg['action'] == 'pause_process' ? 'pause' : 'resume',
543
+ 'fei' => root['fei'],
544
+ 'wfid' => msg['wfid']) # it was triggered by {pause|resume}_process
545
+ end
546
+
547
+ alias resume_process pause_process
548
+
549
+ # Reputs a doc or a msg.
550
+ #
551
+ # Used by certain storage implementations to pass documents around workers
552
+ # or to reschedule msgs (see ruote-swf).
553
+ #
554
+ def reput(msg)
555
+
556
+ if doc = msg['doc']
557
+
558
+ r = @storage.put(doc)
559
+
560
+ return unless r.is_a?(Hash)
561
+
562
+ doc['_rev'] = r['_rev']
563
+
564
+ reput(msg)
565
+
566
+ elsif msg = msg['msg']
567
+
568
+ @storage.put_msg(msg['action'], msg)
569
+ end
570
+ end
571
+
572
+ # This action resparks a stalled workflow instance. It's usually
573
+ # triggered via Dashboard#respark
574
+ #
575
+ # It's been made into a msg (worker action) in order to facilitate
576
+ # migration tooling (ruote-swf for example).
577
+ #
578
+ def respark(msg)
579
+
580
+ wfid = msg['wfid']
581
+ opts = msg['respark']
582
+
583
+ ps = ProcessStatus.fetch(@context, [ wfid ], {}).first
584
+
585
+ error_feis = ps.errors.collect(&:fei)
586
+ errors_too = !! opts['errors_too']
587
+
588
+ ps.leaves.each do |fexp|
589
+
590
+ next if errors_too == false && error_feis.include?(fexp.fei)
591
+
592
+ @context.storage.put_msg(
593
+ 'cancel', 'fei' => fexp.fei.h, 're_apply' => {})
594
+ end
595
+ end
596
+
597
+ #
598
+ # Gathering stats about this worker.
599
+ #
600
+ # Those stats can then be obtained via Dashboard#worker_info
601
+ # (Engine#worker_info).
602
+ #
603
+ class Info
604
+
605
+ def initialize(worker)
606
+
607
+ @worker = worker
608
+ @ip = Ruote.local_ip
609
+ @hostname = Socket.gethostname
610
+ @system = `uname -a`.strip rescue nil
611
+
612
+ @since = Time.now
613
+ @msgs = []
614
+ @last_save = Time.now - 2 * 60
615
+ end
616
+
617
+ def <<(msg)
618
+
619
+ if msg['put_at'].nil?
620
+ puts '-' * 80
621
+ puts "msg missing 'put_at':"
622
+ pp msg
623
+ puts '-' * 80
624
+ end
625
+
626
+ @msgs << {
627
+ 'processed_at' => Ruote.now_to_utc_s,
628
+ 'wait_time' => Time.now - Time.parse(msg['put_at'])
629
+ #'action' => msg['action']
630
+ }
631
+
632
+ save if Time.now > @last_save + 60
633
+ end
634
+
635
+ protected
636
+
637
+ def save
638
+
639
+ doc = @worker.storage.get('variables', 'workers') || {}
640
+
641
+ doc['type'] = 'variables'
642
+ doc['_id'] = 'workers'
643
+
644
+ now = Time.now
645
+
646
+ @msgs = @msgs.drop_while { |msg|
647
+ Time.parse(msg['processed_at']) < now - 3600
648
+ }
649
+ mm = @msgs.drop_while { |msg|
650
+ Time.parse(msg['processed_at']) < now - 60
651
+ }
652
+
653
+ hour_count = @msgs.size < 1 ? 1 : @msgs.size
654
+ minute_count = mm.size < 1 ? 1 : mm.size
655
+
656
+ key = [
657
+ @worker.name, @ip.gsub(/\./, '_'), $$.to_s
658
+ ].join('/')
659
+
660
+ (doc['workers'] ||= {})[key] = {
661
+
662
+ 'class' => @worker.class.to_s,
663
+ 'name' => @name,
664
+ 'ip' => @ip,
665
+ 'hostname' => @hostname,
666
+ 'pid' => $$,
667
+ 'system' => @system,
668
+ 'put_at' => Ruote.now_to_utc_s,
669
+ 'uptime' => Time.now - @since,
670
+
671
+ 'processed_last_minute' =>
672
+ minute_count,
673
+ 'wait_time_last_minute' =>
674
+ mm.inject(0.0) { |s, m| s + m['wait_time'] } / minute_count.to_f,
675
+ 'processed_last_hour' =>
676
+ hour_count,
677
+ 'wait_time_last_hour' =>
678
+ @msgs.inject(0.0) { |s, m| s + m['wait_time'] } / hour_count.to_f
679
+ }
680
+
681
+ r = @worker.storage.put(doc)
682
+
683
+ @last_save = Time.now
684
+
685
+ save if r != nil
686
+ end
687
+ end
359
688
  end
360
689
  end
361
690