ruote 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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