temporalio 0.0.1 → 0.1.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 (310) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +180 -7
  3. data/bridge/Cargo.lock +208 -76
  4. data/bridge/Cargo.toml +5 -2
  5. data/bridge/sdk-core/Cargo.toml +1 -1
  6. data/bridge/sdk-core/README.md +20 -10
  7. data/bridge/sdk-core/client/Cargo.toml +1 -1
  8. data/bridge/sdk-core/client/src/lib.rs +227 -59
  9. data/bridge/sdk-core/client/src/metrics.rs +17 -8
  10. data/bridge/sdk-core/client/src/raw.rs +13 -12
  11. data/bridge/sdk-core/client/src/retry.rs +132 -43
  12. data/bridge/sdk-core/core/Cargo.toml +28 -15
  13. data/bridge/sdk-core/core/benches/workflow_replay.rs +13 -10
  14. data/bridge/sdk-core/core/src/abstractions.rs +225 -36
  15. data/bridge/sdk-core/core/src/core_tests/activity_tasks.rs +217 -79
  16. data/bridge/sdk-core/core/src/core_tests/determinism.rs +165 -2
  17. data/bridge/sdk-core/core/src/core_tests/local_activities.rs +565 -34
  18. data/bridge/sdk-core/core/src/core_tests/queries.rs +247 -90
  19. data/bridge/sdk-core/core/src/core_tests/workers.rs +3 -5
  20. data/bridge/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
  21. data/bridge/sdk-core/core/src/core_tests/workflow_tasks.rs +430 -67
  22. data/bridge/sdk-core/core/src/ephemeral_server/mod.rs +106 -12
  23. data/bridge/sdk-core/core/src/internal_flags.rs +136 -0
  24. data/bridge/sdk-core/core/src/lib.rs +148 -34
  25. data/bridge/sdk-core/core/src/protosext/mod.rs +1 -1
  26. data/bridge/sdk-core/core/src/replay/mod.rs +185 -41
  27. data/bridge/sdk-core/core/src/telemetry/log_export.rs +190 -0
  28. data/bridge/sdk-core/core/src/telemetry/metrics.rs +219 -140
  29. data/bridge/sdk-core/core/src/telemetry/mod.rs +326 -315
  30. data/bridge/sdk-core/core/src/telemetry/prometheus_server.rs +20 -14
  31. data/bridge/sdk-core/core/src/test_help/mod.rs +85 -21
  32. data/bridge/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +112 -156
  33. data/bridge/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  34. data/bridge/sdk-core/core/src/worker/activities/local_activities.rs +364 -128
  35. data/bridge/sdk-core/core/src/worker/activities.rs +263 -170
  36. data/bridge/sdk-core/core/src/worker/client/mocks.rs +23 -3
  37. data/bridge/sdk-core/core/src/worker/client.rs +48 -6
  38. data/bridge/sdk-core/core/src/worker/mod.rs +186 -75
  39. data/bridge/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
  40. data/bridge/sdk-core/core/src/worker/workflow/driven_workflow.rs +13 -24
  41. data/bridge/sdk-core/core/src/worker/workflow/history_update.rs +879 -226
  42. data/bridge/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +101 -48
  43. data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +8 -12
  44. data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +6 -9
  45. data/bridge/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +90 -32
  46. data/bridge/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +6 -9
  47. data/bridge/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +7 -10
  48. data/bridge/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +6 -9
  49. data/bridge/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +160 -83
  50. data/bridge/sdk-core/core/src/worker/workflow/machines/mod.rs +36 -54
  51. data/bridge/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +179 -0
  52. data/bridge/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +104 -157
  53. data/bridge/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +8 -12
  54. data/bridge/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +9 -13
  55. data/bridge/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +10 -4
  56. data/bridge/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +14 -11
  57. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +6 -17
  58. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +395 -299
  59. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +12 -20
  60. data/bridge/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +33 -18
  61. data/bridge/sdk-core/core/src/worker/workflow/managed_run.rs +1032 -374
  62. data/bridge/sdk-core/core/src/worker/workflow/mod.rs +525 -392
  63. data/bridge/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
  64. data/bridge/sdk-core/core/src/worker/workflow/wft_extraction.rs +125 -0
  65. data/bridge/sdk-core/core/src/worker/workflow/wft_poller.rs +3 -6
  66. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
  67. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
  68. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream.rs +456 -681
  69. data/bridge/sdk-core/core-api/Cargo.toml +6 -4
  70. data/bridge/sdk-core/core-api/src/errors.rs +1 -34
  71. data/bridge/sdk-core/core-api/src/lib.rs +7 -45
  72. data/bridge/sdk-core/core-api/src/telemetry.rs +141 -0
  73. data/bridge/sdk-core/core-api/src/worker.rs +27 -1
  74. data/bridge/sdk-core/etc/deps.svg +115 -140
  75. data/bridge/sdk-core/etc/regen-depgraph.sh +5 -0
  76. data/bridge/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +18 -15
  77. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +1 -1
  78. data/bridge/sdk-core/fsm/rustfsm_trait/src/lib.rs +8 -3
  79. data/bridge/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
  80. data/bridge/sdk-core/histories/evict_while_la_running_no_interference-23_history.bin +0 -0
  81. data/bridge/sdk-core/histories/evict_while_la_running_no_interference-85_history.bin +0 -0
  82. data/bridge/sdk-core/protos/api_upstream/buf.yaml +0 -3
  83. data/bridge/sdk-core/protos/api_upstream/build/go.mod +7 -0
  84. data/bridge/sdk-core/protos/api_upstream/build/go.sum +5 -0
  85. data/bridge/sdk-core/protos/api_upstream/{temporal/api/enums/v1/cluster.proto → build/tools.go} +7 -18
  86. data/bridge/sdk-core/protos/api_upstream/go.mod +6 -0
  87. data/bridge/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +12 -9
  88. data/bridge/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +15 -26
  89. data/bridge/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
  90. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -2
  91. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +4 -9
  92. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +3 -2
  93. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +10 -8
  94. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +28 -2
  95. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +2 -2
  96. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +2 -2
  97. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +2 -2
  98. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +2 -2
  99. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +2 -2
  100. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +24 -19
  101. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +2 -2
  102. data/bridge/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +2 -2
  103. data/bridge/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  104. data/bridge/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +2 -2
  105. data/bridge/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +62 -26
  106. data/bridge/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +4 -2
  107. data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +24 -61
  108. data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +2 -21
  109. data/bridge/sdk-core/protos/api_upstream/temporal/api/protocol/v1/message.proto +57 -0
  110. data/bridge/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +2 -2
  111. data/bridge/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +2 -2
  112. data/bridge/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +110 -31
  113. data/bridge/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  114. data/bridge/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +4 -4
  115. data/bridge/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +71 -6
  116. data/bridge/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +2 -2
  117. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +3 -2
  118. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +111 -36
  119. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +19 -5
  120. data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -0
  121. data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -0
  122. data/bridge/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -0
  123. data/bridge/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  124. data/bridge/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  125. data/bridge/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -0
  126. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +9 -0
  127. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +9 -1
  128. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +6 -0
  129. data/bridge/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +2 -2
  130. data/bridge/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +2 -2
  131. data/bridge/sdk-core/sdk/Cargo.toml +4 -3
  132. data/bridge/sdk-core/sdk/src/interceptors.rs +36 -3
  133. data/bridge/sdk-core/sdk/src/lib.rs +94 -25
  134. data/bridge/sdk-core/sdk/src/workflow_context.rs +13 -2
  135. data/bridge/sdk-core/sdk/src/workflow_future.rs +10 -13
  136. data/bridge/sdk-core/sdk-core-protos/Cargo.toml +5 -2
  137. data/bridge/sdk-core/sdk-core-protos/build.rs +36 -2
  138. data/bridge/sdk-core/sdk-core-protos/src/history_builder.rs +164 -104
  139. data/bridge/sdk-core/sdk-core-protos/src/history_info.rs +27 -23
  140. data/bridge/sdk-core/sdk-core-protos/src/lib.rs +252 -74
  141. data/bridge/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
  142. data/bridge/sdk-core/test-utils/Cargo.toml +4 -1
  143. data/bridge/sdk-core/test-utils/src/canned_histories.rs +106 -296
  144. data/bridge/sdk-core/test-utils/src/histfetch.rs +1 -1
  145. data/bridge/sdk-core/test-utils/src/lib.rs +161 -50
  146. data/bridge/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
  147. data/bridge/sdk-core/test-utils/src/workflows.rs +29 -0
  148. data/bridge/sdk-core/tests/fuzzy_workflow.rs +130 -0
  149. data/bridge/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
  150. data/bridge/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  151. data/bridge/sdk-core/tests/integ_tests/heartbeat_tests.rs +10 -5
  152. data/bridge/sdk-core/tests/integ_tests/metrics_tests.rs +239 -0
  153. data/bridge/sdk-core/tests/integ_tests/polling_tests.rs +4 -60
  154. data/bridge/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
  155. data/bridge/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
  156. data/bridge/sdk-core/tests/integ_tests/workflow_tests/activities.rs +93 -69
  157. data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  158. data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
  159. data/bridge/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +1 -0
  160. data/bridge/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
  161. data/bridge/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
  162. data/bridge/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +151 -116
  163. data/bridge/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +54 -0
  164. data/bridge/sdk-core/tests/integ_tests/workflow_tests/patches.rs +7 -28
  165. data/bridge/sdk-core/tests/integ_tests/workflow_tests/replay.rs +115 -24
  166. data/bridge/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  167. data/bridge/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
  168. data/bridge/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
  169. data/bridge/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
  170. data/bridge/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -4
  171. data/bridge/sdk-core/tests/integ_tests/workflow_tests.rs +27 -18
  172. data/bridge/sdk-core/tests/main.rs +8 -16
  173. data/bridge/sdk-core/tests/runner.rs +75 -36
  174. data/bridge/sdk-core/tests/wf_input_replay.rs +32 -0
  175. data/bridge/src/connection.rs +117 -82
  176. data/bridge/src/lib.rs +356 -42
  177. data/bridge/src/runtime.rs +10 -3
  178. data/bridge/src/test_server.rs +153 -0
  179. data/bridge/src/worker.rs +133 -9
  180. data/lib/gen/temporal/api/batch/v1/message_pb.rb +8 -6
  181. data/lib/gen/temporal/api/command/v1/message_pb.rb +10 -16
  182. data/lib/gen/temporal/api/common/v1/message_pb.rb +5 -1
  183. data/lib/gen/temporal/api/enums/v1/batch_operation_pb.rb +2 -1
  184. data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +3 -3
  185. data/lib/gen/temporal/api/enums/v1/common_pb.rb +2 -1
  186. data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +5 -4
  187. data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +9 -1
  188. data/lib/gen/temporal/api/enums/v1/namespace_pb.rb +1 -1
  189. data/lib/gen/temporal/api/enums/v1/query_pb.rb +1 -1
  190. data/lib/gen/temporal/api/enums/v1/reset_pb.rb +1 -1
  191. data/lib/gen/temporal/api/enums/v1/schedule_pb.rb +1 -1
  192. data/lib/gen/temporal/api/enums/v1/task_queue_pb.rb +1 -1
  193. data/lib/gen/temporal/api/enums/v1/update_pb.rb +7 -10
  194. data/lib/gen/temporal/api/enums/v1/workflow_pb.rb +1 -1
  195. data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +1 -1
  196. data/lib/gen/temporal/api/failure/v1/message_pb.rb +1 -1
  197. data/lib/gen/temporal/api/filter/v1/message_pb.rb +1 -1
  198. data/lib/gen/temporal/api/history/v1/message_pb.rb +34 -25
  199. data/lib/gen/temporal/api/namespace/v1/message_pb.rb +2 -1
  200. data/lib/gen/temporal/api/operatorservice/v1/request_response_pb.rb +14 -51
  201. data/lib/gen/temporal/api/operatorservice/v1/service_pb.rb +1 -1
  202. data/lib/gen/temporal/api/protocol/v1/message_pb.rb +30 -0
  203. data/lib/gen/temporal/api/query/v1/message_pb.rb +1 -1
  204. data/lib/gen/temporal/api/replication/v1/message_pb.rb +1 -1
  205. data/lib/gen/temporal/api/schedule/v1/message_pb.rb +22 -1
  206. data/lib/gen/temporal/api/sdk/v1/task_complete_metadata_pb.rb +23 -0
  207. data/lib/gen/temporal/api/taskqueue/v1/message_pb.rb +2 -2
  208. data/lib/gen/temporal/api/testservice/v1/request_response_pb.rb +49 -0
  209. data/lib/gen/temporal/api/testservice/v1/service_pb.rb +21 -0
  210. data/lib/gen/temporal/api/update/v1/message_pb.rb +49 -3
  211. data/lib/gen/temporal/api/version/v1/message_pb.rb +1 -1
  212. data/lib/gen/temporal/api/workflow/v1/message_pb.rb +2 -1
  213. data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +47 -20
  214. data/lib/gen/temporal/api/workflowservice/v1/service_pb.rb +1 -1
  215. data/lib/gen/temporal/sdk/core/activity_result/activity_result_pb.rb +13 -9
  216. data/lib/gen/temporal/sdk/core/activity_task/activity_task_pb.rb +10 -6
  217. data/lib/gen/temporal/sdk/core/child_workflow/child_workflow_pb.rb +13 -9
  218. data/lib/gen/temporal/sdk/core/common/common_pb.rb +7 -3
  219. data/lib/gen/temporal/sdk/core/core_interface_pb.rb +9 -3
  220. data/lib/gen/temporal/sdk/core/external_data/external_data_pb.rb +7 -3
  221. data/lib/gen/temporal/sdk/core/workflow_activation/workflow_activation_pb.rb +28 -21
  222. data/lib/gen/temporal/sdk/core/workflow_commands/workflow_commands_pb.rb +32 -24
  223. data/lib/gen/temporal/sdk/core/workflow_completion/workflow_completion_pb.rb +12 -5
  224. data/lib/temporalio/activity/context.rb +102 -0
  225. data/lib/temporalio/activity/info.rb +67 -0
  226. data/lib/temporalio/activity.rb +85 -0
  227. data/lib/temporalio/bridge/connect_options.rb +15 -0
  228. data/lib/temporalio/bridge/error.rb +8 -0
  229. data/lib/temporalio/bridge/retry_config.rb +24 -0
  230. data/lib/temporalio/bridge/tls_options.rb +19 -0
  231. data/lib/temporalio/bridge.rb +14 -0
  232. data/lib/{temporal → temporalio}/client/implementation.rb +57 -56
  233. data/lib/{temporal → temporalio}/client/workflow_handle.rb +35 -35
  234. data/lib/{temporal → temporalio}/client.rb +19 -32
  235. data/lib/temporalio/connection/retry_config.rb +44 -0
  236. data/lib/temporalio/connection/service.rb +20 -0
  237. data/lib/temporalio/connection/test_service.rb +92 -0
  238. data/lib/temporalio/connection/tls_options.rb +51 -0
  239. data/lib/temporalio/connection/workflow_service.rb +731 -0
  240. data/lib/temporalio/connection.rb +86 -0
  241. data/lib/{temporal → temporalio}/data_converter.rb +76 -35
  242. data/lib/{temporal → temporalio}/error/failure.rb +6 -6
  243. data/lib/{temporal → temporalio}/error/workflow_failure.rb +4 -2
  244. data/lib/{temporal → temporalio}/errors.rb +19 -1
  245. data/lib/{temporal → temporalio}/failure_converter/base.rb +5 -5
  246. data/lib/{temporal → temporalio}/failure_converter/basic.rb +58 -52
  247. data/lib/temporalio/failure_converter.rb +7 -0
  248. data/lib/temporalio/interceptor/activity_inbound.rb +22 -0
  249. data/lib/temporalio/interceptor/activity_outbound.rb +24 -0
  250. data/lib/{temporal → temporalio}/interceptor/chain.rb +7 -6
  251. data/lib/{temporal → temporalio}/interceptor/client.rb +27 -2
  252. data/lib/temporalio/interceptor.rb +22 -0
  253. data/lib/{temporal → temporalio}/payload_codec/base.rb +5 -5
  254. data/lib/{temporal → temporalio}/payload_converter/base.rb +3 -3
  255. data/lib/{temporal → temporalio}/payload_converter/bytes.rb +4 -3
  256. data/lib/{temporal → temporalio}/payload_converter/composite.rb +7 -5
  257. data/lib/{temporal → temporalio}/payload_converter/encoding_base.rb +4 -4
  258. data/lib/{temporal → temporalio}/payload_converter/json.rb +4 -3
  259. data/lib/{temporal → temporalio}/payload_converter/nil.rb +4 -3
  260. data/lib/temporalio/payload_converter.rb +14 -0
  261. data/lib/{temporal → temporalio}/retry_policy.rb +17 -7
  262. data/lib/{temporal → temporalio}/retry_state.rb +1 -1
  263. data/lib/temporalio/runtime.rb +25 -0
  264. data/lib/temporalio/testing/time_skipping_handle.rb +32 -0
  265. data/lib/temporalio/testing/time_skipping_interceptor.rb +23 -0
  266. data/lib/temporalio/testing/workflow_environment.rb +112 -0
  267. data/lib/temporalio/testing.rb +175 -0
  268. data/lib/{temporal → temporalio}/timeout_type.rb +2 -2
  269. data/lib/temporalio/version.rb +3 -0
  270. data/lib/temporalio/worker/activity_runner.rb +114 -0
  271. data/lib/temporalio/worker/activity_worker.rb +164 -0
  272. data/lib/temporalio/worker/reactor.rb +46 -0
  273. data/lib/temporalio/worker/runner.rb +63 -0
  274. data/lib/temporalio/worker/sync_worker.rb +124 -0
  275. data/lib/temporalio/worker/thread_pool_executor.rb +51 -0
  276. data/lib/temporalio/worker.rb +204 -0
  277. data/lib/temporalio/workflow/async.rb +46 -0
  278. data/lib/{temporal → temporalio}/workflow/execution_info.rb +4 -4
  279. data/lib/{temporal → temporalio}/workflow/execution_status.rb +1 -1
  280. data/lib/temporalio/workflow/future.rb +138 -0
  281. data/lib/{temporal → temporalio}/workflow/id_reuse_policy.rb +6 -6
  282. data/lib/temporalio/workflow/info.rb +76 -0
  283. data/lib/{temporal → temporalio}/workflow/query_reject_condition.rb +5 -5
  284. data/lib/temporalio.rb +12 -3
  285. data/temporalio.gemspec +11 -6
  286. metadata +137 -64
  287. data/bridge/sdk-core/Cargo.lock +0 -2606
  288. data/bridge/sdk-core/bridge-ffi/Cargo.toml +0 -24
  289. data/bridge/sdk-core/bridge-ffi/LICENSE.txt +0 -23
  290. data/bridge/sdk-core/bridge-ffi/build.rs +0 -25
  291. data/bridge/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -249
  292. data/bridge/sdk-core/bridge-ffi/src/lib.rs +0 -825
  293. data/bridge/sdk-core/bridge-ffi/src/wrappers.rs +0 -211
  294. data/bridge/sdk-core/core/src/log_export.rs +0 -62
  295. data/bridge/sdk-core/core/src/worker/workflow/machines/mutable_side_effect_state_machine.rs +0 -127
  296. data/bridge/sdk-core/core/src/worker/workflow/machines/side_effect_state_machine.rs +0 -71
  297. data/bridge/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +0 -83
  298. data/bridge/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -210
  299. data/bridge/sdk-core/sdk/src/conversions.rs +0 -8
  300. data/lib/bridge.so +0 -0
  301. data/lib/gen/temporal/api/cluster/v1/message_pb.rb +0 -67
  302. data/lib/gen/temporal/api/enums/v1/cluster_pb.rb +0 -26
  303. data/lib/gen/temporal/sdk/core/bridge/bridge_pb.rb +0 -222
  304. data/lib/temporal/bridge.rb +0 -14
  305. data/lib/temporal/connection.rb +0 -736
  306. data/lib/temporal/failure_converter.rb +0 -8
  307. data/lib/temporal/payload_converter.rb +0 -14
  308. data/lib/temporal/runtime.rb +0 -22
  309. data/lib/temporal/version.rb +0 -3
  310. data/lib/temporal.rb +0 -8
@@ -0,0 +1,175 @@
1
+ require 'temporalio/bridge'
2
+ require 'temporalio/connection'
3
+ require 'temporalio/runtime'
4
+ require 'temporalio/testing/workflow_environment'
5
+ require 'temporalio/version'
6
+
7
+ module Temporalio
8
+ module Testing
9
+ DEFAULT_NAMESPACE = 'default'.freeze
10
+
11
+ class << self
12
+ # Start a full Temporal server locally, downloading if necessary.
13
+ #
14
+ # This environment is good for testing full server capabilities, but does not support time
15
+ # skipping like {.start_time_skipping} does.
16
+ # {Temporalio::Testing::WorkflowEnvironment#supports_time_skipping} will always return `false`
17
+ # for this environment. {Temporalio::Testing::WorkflowEnvironment#sleep} will sleep the actual
18
+ # amount of time and {Temporalio::Testing::WorkflowEnvironment#get_current_time}` will return
19
+ # the current time.
20
+ #
21
+ # Internally, this uses [Temporalite](https://github.com/temporalio/temporalite). Which is a
22
+ # self-contained binary for Temporal using Sqlite persistence. This will download Temporalite
23
+ # to a temporary directory by default if it has not already been downloaded before and
24
+ # `:existing_path` is not set.
25
+ #
26
+ # In the future, the Temporalite implementation may be changed to another implementation.
27
+ # Therefore, all `temporalite_` prefixed parameters are Temporalite specific and may not apply
28
+ # to newer versions.
29
+ #
30
+ # @param namespace [String] Namespace name to use for this environment.
31
+ # @param ip [String] IP address to bind to, or 127.0.0.1 by default.
32
+ # @param port [Integer] Port number to bind to, or an OS-provided port by default.
33
+ # @param download_dir [String] Directory to download binary to if a download is needed. If
34
+ # unset, this is the system's temporary directory.
35
+ # @param ui [Boolean] If `true`, will start a UI in Temporalite.
36
+ # @param temporalite_existing_path [String] Existing path to the Temporalite binary. If
37
+ # present, no download will be attempted to fetch the binary.
38
+ # @param temporalite_database_filename [String] Path to the Sqlite database to use for
39
+ # Temporalite. Unset default means only in-memory Sqlite will be used.
40
+ # @param temporalite_log_format [String] Log format for Temporalite.
41
+ # @param temporalite_log_level [String] Log level to use for Temporalite.
42
+ # @param temporalite_download_version [String] Specific Temporalite version to download.
43
+ # Defaults to `default` which downloads the version known to work best with this SDK.
44
+ # @param temporalite_extra_args [Array<String>] Extra arguments for the Temporalite binary.
45
+ #
46
+ # @yield Optionally you can provide a block which will ensure that the environment has been
47
+ # shut down after. The newly created {Temporalio::Testing::WorkflowEnvironment} will be
48
+ # passed into the block as a single argument. Alternatively you will need to call
49
+ # {Temporalio::Testing::WorkflowEnvironment#shutdown} explicitly after you're done with it.
50
+ #
51
+ # @return [Temporalio::Testing::WorkflowEnvironment] The newly started Temporalite workflow
52
+ # environment.
53
+ def start_local_environment(
54
+ namespace: DEFAULT_NAMESPACE,
55
+ ip: '127.0.0.1',
56
+ port: nil,
57
+ download_dir: nil,
58
+ ui: false,
59
+ temporalite_existing_path: nil,
60
+ temporalite_database_filename: nil,
61
+ temporalite_log_format: 'pretty',
62
+ temporalite_log_level: 'warn',
63
+ temporalite_download_version: 'default',
64
+ temporalite_extra_args: [],
65
+ &block
66
+ )
67
+ # TODO: Sync with the SDK's logger level when implemented
68
+ runtime = Temporalio::Runtime.instance
69
+ server = Temporalio::Bridge::TestServer.start_temporalite(
70
+ runtime.core_runtime,
71
+ temporalite_existing_path,
72
+ 'sdk-ruby',
73
+ Temporalio::VERSION,
74
+ temporalite_download_version,
75
+ download_dir,
76
+ namespace,
77
+ ip,
78
+ port,
79
+ temporalite_database_filename,
80
+ ui,
81
+ temporalite_log_format,
82
+ temporalite_log_level,
83
+ temporalite_extra_args,
84
+ )
85
+ env = init_workflow_environment_for(server, namespace)
86
+
87
+ return env unless block
88
+
89
+ run_server(server, env, &block)
90
+ end
91
+
92
+ # Start a time skipping workflow environment.
93
+ #
94
+ # Time can be manually skipped forward using {Temporalio::Testing::WorkflowEnvironment#sleep}.
95
+ # The currently known time can be obtained via
96
+ # {Temporalio::Testing::WorkflowEnvironment#get_current_time}.
97
+ #
98
+ # @note Auto time skipping is not yet implemented.
99
+ #
100
+ # Internally, this environment lazily downloads a test-server binary for the current OS/arch
101
+ # into the temp directory if it is not already there. Then the executable is started and will
102
+ # be killed when {Temporalio::Testing::WorkflowEnvironment#shutdown} is called (which is
103
+ # implicitly done if a block is provided to this method).
104
+ #
105
+ # Users can reuse this environment for testing multiple independent workflows, but not
106
+ # concurrently. Time skipping, which is automatically done when awaiting a workflow result
107
+ # (pending implementation) and manually done on
108
+ # {Temporalio::Testing::WorkflowEnvironment#sleep}, is global to the environment, not to the
109
+ # workflow under test.
110
+ #
111
+ # In the future, the test server implementation may be changed to another implementation.
112
+ # Therefore, all `test_server_` prefixed parameters are test server specific and may not apply
113
+ # to newer versions.
114
+ #
115
+ # @param port [Integer] Port number to bind to, or an OS-provided port by default.
116
+ # @param download_dir [String] Directory to download binary to if a download is needed.
117
+ # If unset, this is the system's temporary directory.
118
+ # @param test_server_existing_path [String] Existing path to the test server binary. If
119
+ # present, no download will be attempted to fetch the binary.
120
+ # @param test_server_download_version [String] Specific test server version to download.
121
+ # Defaults to `default` which downloads the version known to work best with this SDK.
122
+ # @param test_server_extra_args [Array<String>] Extra arguments for the test server binary.
123
+ #
124
+ # @yield Optionally you can provide a block which will ensure that the environment has been
125
+ # shut down after. The newly created {Temporalio::Testing::WorkflowEnvironment} will be
126
+ # passed into the block as a single argument. Alternatively you will need to call
127
+ # {Temporalio::Testing::WorkflowEnvironment#shutdown} explicitly after you're done with it.
128
+ #
129
+ # @return [Temporalio::Testing::WorkflowEnvironment] The newly started TestServer workflow
130
+ # environment.
131
+ def start_time_skipping_environment(
132
+ port: nil,
133
+ download_dir: nil,
134
+ test_server_existing_path: nil,
135
+ test_server_download_version: 'default',
136
+ test_server_extra_args: [],
137
+ &block
138
+ )
139
+ # TODO: Use interceptors to inject a time skipping WorkflowHandle.
140
+ runtime = Temporalio::Runtime.instance
141
+ server = Temporalio::Bridge::TestServer.start(
142
+ runtime.core_runtime,
143
+ test_server_existing_path,
144
+ 'sdk-ruby',
145
+ Temporalio::VERSION,
146
+ test_server_download_version,
147
+ download_dir,
148
+ port,
149
+ test_server_extra_args,
150
+ )
151
+ env = init_workflow_environment_for(server, DEFAULT_NAMESPACE)
152
+
153
+ return env unless block
154
+
155
+ run_server(server, env, &block)
156
+ end
157
+
158
+ private
159
+
160
+ def init_workflow_environment_for(server, namespace)
161
+ connection = Temporalio::Connection.new(server.target)
162
+ Temporalio::Testing::WorkflowEnvironment.new(server, connection, namespace)
163
+ rescue Temporalio::Bridge::Error # Shutdown if unable to connect to the server
164
+ server.shutdown
165
+ raise
166
+ end
167
+
168
+ def run_server(server, env, &block)
169
+ block.call(env)
170
+ ensure
171
+ server.shutdown
172
+ end
173
+ end
174
+ end
175
+ end
@@ -1,5 +1,5 @@
1
- module Temporal
2
- # Type of timeout for {Temporal::TimeoutError}.
1
+ module Temporalio
2
+ # Type of timeout for {Temporalio::TimeoutError}.
3
3
  module TimeoutType
4
4
  TYPES = [
5
5
  START_TO_CLOSE = :START_TO_CLOSE,
@@ -0,0 +1,3 @@
1
+ module Temporalio
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,114 @@
1
+ require 'google/protobuf/well_known_types'
2
+ require 'temporalio/activity/context'
3
+ require 'temporalio/activity/info'
4
+ require 'temporalio/error/failure'
5
+ require 'temporalio/errors'
6
+ require 'temporalio/interceptor/activity_inbound'
7
+
8
+ module Temporalio
9
+ class Worker
10
+ # The main class for handling activity processing. It is expected to be executed from
11
+ # some threaded or async executor's context since methods called here might be blocking
12
+ # and this should not affect the main worker reactor.
13
+ #
14
+ # @api private
15
+ class ActivityRunner
16
+ def initialize(
17
+ activity_class,
18
+ start,
19
+ task_queue,
20
+ task_token,
21
+ worker,
22
+ converter,
23
+ inbound_interceptors,
24
+ outbound_interceptors
25
+ )
26
+ @activity_class = activity_class
27
+ @start = start
28
+ @task_queue = task_queue
29
+ @task_token = task_token
30
+ @worker = worker
31
+ @converter = converter
32
+ @inbound_interceptors = inbound_interceptors
33
+ @outbound_interceptors = outbound_interceptors
34
+ end
35
+
36
+ def run
37
+ activity = activity_class.new(context)
38
+ args = converter.from_payload_array(start.input.to_a)
39
+ headers = converter.from_payload_map(start.header_fields)
40
+ input = Temporalio::Interceptor::ActivityInbound::ExecuteActivityInput.new(
41
+ activity: activity_class,
42
+ args: args,
43
+ headers: headers || {},
44
+ )
45
+
46
+ result = inbound_interceptors.invoke(:execute_activity, input) do |i|
47
+ activity.execute(*i.args)
48
+ end
49
+
50
+ converter.to_payload(result)
51
+ rescue StandardError => e
52
+ # Temporal server ignores cancellation failures that were not requested by the server.
53
+ # However within the SDK cancellations are also used during the worker shutdown. In order
54
+ # to provide a seamless handling experience (same error raised within the Activity) we are
55
+ # using the ActivityCancelled error and then swapping it with a CancelledError here.
56
+ #
57
+ # In the future this will be handled by the SDK Core — https://github.com/temporalio/sdk-core/issues/461
58
+ if e.is_a?(Temporalio::Error::ActivityCancelled) && e.by_request?
59
+ e = Temporalio::Error::CancelledError.new(e.message)
60
+ end
61
+
62
+ converter.to_failure(e)
63
+ end
64
+
65
+ def cancel(reason, by_request:)
66
+ context.cancel(reason, by_request: by_request)
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :activity_class, :start, :task_queue, :task_token, :worker, :converter,
72
+ :inbound_interceptors, :outbound_interceptors
73
+
74
+ def context
75
+ return @context if @context
76
+
77
+ heartbeat_proc = ->(*details) { heartbeat(*details) }
78
+ @context = Temporalio::Activity::Context.new(
79
+ generate_activity_info,
80
+ heartbeat_proc,
81
+ outbound_interceptors,
82
+ shielded: activity_class._shielded,
83
+ )
84
+ end
85
+
86
+ def generate_activity_info
87
+ Temporalio::Activity::Info.new(
88
+ activity_id: start.activity_id,
89
+ activity_type: start.activity_type,
90
+ attempt: start.attempt,
91
+ current_attempt_scheduled_time: start.current_attempt_scheduled_time&.to_time,
92
+ heartbeat_details: converter.from_payload_array(start.heartbeat_details.to_a),
93
+ heartbeat_timeout: start.heartbeat_timeout&.to_f,
94
+ local: !start.is_local.nil?,
95
+ schedule_to_close_timeout: start.schedule_to_close_timeout&.to_f,
96
+ scheduled_time: start.scheduled_time&.to_time,
97
+ start_to_close_timeout: start.start_to_close_timeout&.to_f,
98
+ started_time: start.started_time&.to_time,
99
+ task_queue: task_queue,
100
+ task_token: task_token,
101
+ workflow_id: start.workflow_execution&.workflow_id,
102
+ workflow_namespace: start.workflow_namespace,
103
+ workflow_run_id: start.workflow_execution&.run_id,
104
+ workflow_type: start.workflow_type,
105
+ ).freeze
106
+ end
107
+
108
+ def heartbeat(*details)
109
+ payloads = converter.to_payload_array(details)
110
+ worker.record_activity_heartbeat(task_token, payloads)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,164 @@
1
+ require 'temporalio/error/failure'
2
+ require 'temporalio/errors'
3
+ require 'temporalio/interceptor'
4
+ require 'temporalio/interceptor/chain'
5
+ require 'temporalio/worker/activity_runner'
6
+
7
+ module Temporalio
8
+ class Worker
9
+ # @api private
10
+ class ActivityWorker
11
+ def initialize(
12
+ task_queue,
13
+ worker,
14
+ activities,
15
+ converter,
16
+ interceptors,
17
+ executor,
18
+ graceful_timeout
19
+ )
20
+ @task_queue = task_queue
21
+ @worker = worker
22
+ @activities = prepare_activities(activities)
23
+ @converter = converter
24
+ @inbound_interceptors = Temporalio::Interceptor::Chain.new(
25
+ Temporalio::Interceptor.filter(interceptors, :activity_inbound),
26
+ )
27
+ @outbound_interceptors = Temporalio::Interceptor::Chain.new(
28
+ Temporalio::Interceptor.filter(interceptors, :activity_outbound),
29
+ )
30
+ @executor = executor
31
+ @graceful_timeout = graceful_timeout
32
+ @running_activities = {}
33
+ @cancellations = []
34
+ @drain_queue = Queue.new
35
+ end
36
+
37
+ def run(reactor)
38
+ # @type var outstanding_tasks: Array[Async::Task]
39
+ outstanding_tasks = []
40
+
41
+ loop do
42
+ activity_task = worker.poll_activity_task
43
+ outstanding_tasks << reactor.async do |async_task|
44
+ if activity_task.start
45
+ handle_start_activity(activity_task.task_token, activity_task.start)
46
+ elsif activity_task.cancel
47
+ handle_cancel_activity(activity_task.task_token, activity_task.cancel)
48
+ end
49
+ ensure
50
+ outstanding_tasks.delete(async_task)
51
+ end
52
+ end
53
+ rescue Temporalio::Bridge::Error::WorkerShutdown
54
+ # No need to re-raise this error, it's a part of a normal shutdown
55
+ ensure
56
+ outstanding_tasks.each(&:wait)
57
+ @cancelation_task&.wait
58
+ drain_queue.close
59
+ end
60
+
61
+ def setup_graceful_shutdown_timer(reactor)
62
+ if graceful_timeout
63
+ reactor.async do |async_task|
64
+ @cancelation_task = async_task.async do
65
+ sleep graceful_timeout
66
+ @running_activities.each_value do |activity_runner|
67
+ activity_runner.cancel('Worker is shutting down', by_request: false)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def drain
75
+ drain_queue.pop
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :task_queue, :worker, :activities, :converter, :inbound_interceptors,
81
+ :outbound_interceptors, :executor, :graceful_timeout, :running_activities,
82
+ :cancellations, :drain_queue
83
+
84
+ def prepare_activities(activities)
85
+ activities.each_with_object({}) do |activity, result|
86
+ unless activity.ancestors.include?(Temporalio::Activity)
87
+ raise ArgumentError, 'Activity must be a subclass of Temporalio::Activity'
88
+ end
89
+
90
+ if result[activity._name]
91
+ raise ArgumentError, "More than one activity named #{activity._name}"
92
+ end
93
+
94
+ result[activity._name] = activity
95
+ result
96
+ end
97
+ end
98
+
99
+ def lookup_activity(activity_type)
100
+ activities.fetch(activity_type) do
101
+ activity_names = activities.keys.sort.join(', ')
102
+ raise Temporalio::Error::ApplicationError.new(
103
+ "Activity #{activity_type} is not registered on this worker, available activities: #{activity_names}",
104
+ type: 'NotFoundError',
105
+ )
106
+ end
107
+ end
108
+
109
+ def run_activity(token, start)
110
+ activity_class = lookup_activity(start.activity_type)
111
+ runner = ActivityRunner.new(
112
+ activity_class,
113
+ start,
114
+ task_queue,
115
+ token,
116
+ worker,
117
+ converter,
118
+ inbound_interceptors,
119
+ outbound_interceptors,
120
+ )
121
+ running_activities[token] = runner
122
+ queue = Queue.new
123
+
124
+ executor.schedule do
125
+ queue << runner.run
126
+ end
127
+
128
+ queue.pop
129
+ rescue StandardError => e
130
+ converter.to_failure(e)
131
+ end
132
+
133
+ def handle_start_activity(task_token, start)
134
+ result = run_activity(task_token, start)
135
+
136
+ case result
137
+ when Temporalio::Api::Common::V1::Payload
138
+ worker.complete_activity_task_with_success(task_token, result)
139
+ when Temporalio::Api::Failure::V1::Failure
140
+ # only respond with a cancellation when it was requested, otherwise it's a regular failure
141
+ if result.canceled_failure_info && cancellations.include?(task_token)
142
+ worker.complete_activity_task_with_cancellation(task_token, result)
143
+ else
144
+ worker.complete_activity_task_with_failure(task_token, result)
145
+ end
146
+ end
147
+
148
+ running_activities.delete(task_token)
149
+ cancellations.delete(task_token)
150
+ end
151
+
152
+ def handle_cancel_activity(task_token, _cancel)
153
+ runner = running_activities.fetch(task_token) do
154
+ # TODO: Use logger instead when implemented
155
+ warn "Cannot find activity to cancel for token #{task_token}"
156
+ return # early escape
157
+ end
158
+
159
+ cancellations << task_token
160
+ runner&.cancel('Activity cancellation requested', by_request: true)
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,46 @@
1
+ require 'async'
2
+
3
+ module Temporalio
4
+ class Worker
5
+ # A shared async reactor.
6
+ #
7
+ # This class allows multiple workers to access the same Async reactor
8
+ # without forcing the SDK users to wrap their execution in an Async block. This
9
+ # is handled using a queue that is polled from within a running Async reactor,
10
+ # so all the blocks end up being executed within it.
11
+ #
12
+ # @api private
13
+ class Reactor
14
+ def initialize
15
+ @queue = Queue.new
16
+ @thread = nil
17
+ @mutex = Mutex.new
18
+ end
19
+
20
+ def async(&block)
21
+ ensure_reactor_thread
22
+ queue << block
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :queue, :mutex
28
+
29
+ def ensure_reactor_thread
30
+ mutex.synchronize do
31
+ @thread ||= Thread.new { run_loop }
32
+ end
33
+ end
34
+
35
+ def run_loop
36
+ reactor = Async::Reactor.new
37
+ reactor.run do |task|
38
+ loop do
39
+ block = queue.pop
40
+ task.async { |subtask| block.call(subtask) }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ require 'temporalio/errors'
2
+
3
+ module Temporalio
4
+ class Worker
5
+ # A class used to manage the lifecycle of running any number of workers.
6
+ #
7
+ # @api private
8
+ class Runner
9
+ def initialize(*workers)
10
+ if workers.empty?
11
+ raise ArgumentError, 'Must be initialized with at least one worker'
12
+ end
13
+
14
+ @workers = workers
15
+ @mutex = Mutex.new
16
+ @started = false
17
+ @shutdown = false
18
+ end
19
+
20
+ def run(&block)
21
+ @thread = Thread.current
22
+ @started = true
23
+ workers.each { |worker| worker.start(self) }
24
+
25
+ block ? block.call : sleep
26
+ rescue Temporalio::Error::WorkerShutdown
27
+ # Explicit shutdown requested, no need to raise
28
+ ensure
29
+ @shutdown = true
30
+ shutdown_workers
31
+ end
32
+
33
+ def shutdown(exception = Temporalio::Error::WorkerShutdown.new('Manual shutdown'))
34
+ mutex.synchronize do
35
+ return unless running?
36
+
37
+ @shutdown = true
38
+ end
39
+
40
+ # propagate shutdown to the running thread
41
+ thread&.raise(exception)
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :workers, :mutex, :thread
47
+
48
+ def running?
49
+ @started && !@shutdown
50
+ end
51
+
52
+ def shutdown_workers
53
+ # Protect shutdown from any outside raises
54
+ Thread.handle_interrupt(StandardError => :never) do
55
+ workers.map do |worker|
56
+ # Shut down each worker (and wait for it) concurrently in separate threads
57
+ Thread.new { worker.shutdown }
58
+ end.each(&:join)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end