DefStream 0.1.20__tar.gz → 0.1.21__tar.gz

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 (278) hide show
  1. {defstream-0.1.20 → defstream-0.1.21}/DefStream.egg-info/PKG-INFO +1 -1
  2. {defstream-0.1.20 → defstream-0.1.21}/DefStream.egg-info/SOURCES.txt +28 -0
  3. {defstream-0.1.20 → defstream-0.1.21}/PKG-INFO +1 -1
  4. {defstream-0.1.20 → defstream-0.1.21}/docs/LifecycleHooks.en.md +2 -0
  5. {defstream-0.1.20 → defstream-0.1.21}/docs/LifecycleHooks.md +2 -0
  6. {defstream-0.1.20 → defstream-0.1.21}/docs/Operations.en.md +2 -0
  7. {defstream-0.1.20 → defstream-0.1.21}/docs/Operations.md +2 -0
  8. {defstream-0.1.20 → defstream-0.1.21}/docs/RabbitMQ.en.md +44 -0
  9. {defstream-0.1.20 → defstream-0.1.21}/docs/RabbitMQ.md +44 -0
  10. defstream-0.1.21/issues/feature/46/CHANGELOG.md +26 -0
  11. defstream-0.1.21/issues/feature/46/IMPLEMENTS.md +35 -0
  12. defstream-0.1.21/issues/feature/46/PLAN.md +111 -0
  13. defstream-0.1.21/issues/feature/46/README.md +75 -0
  14. defstream-0.1.21/issues/feature/46/REQUIREMENTS.md +119 -0
  15. defstream-0.1.21/issues/feature/46/TASKS.md +26 -0
  16. defstream-0.1.21/issues/feature/46/implements/1793274693488.md +9 -0
  17. defstream-0.1.21/issues/feature/46/implements/1793275218996.md +7 -0
  18. defstream-0.1.21/issues/feature/46/implements/1793275899489.md +7 -0
  19. defstream-0.1.21/issues/feature/46/implements/1793276075771.md +5 -0
  20. defstream-0.1.21/issues/feature/46/implements/1793276518340.md +7 -0
  21. defstream-0.1.21/issues/feature/46/implements/1793277364116.md +12 -0
  22. defstream-0.1.21/issues/feature/46/implements/1793278267422.md +10 -0
  23. defstream-0.1.21/issues/feature/46/implements/1793278764701.md +9 -0
  24. defstream-0.1.21/issues/feature/46/implements/1793279849390.md +7 -0
  25. defstream-0.1.21/issues/feature/46/tasks/1793272872217.md +7 -0
  26. defstream-0.1.21/issues/feature/46/tasks/1793274121654.md +11 -0
  27. defstream-0.1.21/issues/feature/46/tasks/1793274231988.md +7 -0
  28. defstream-0.1.21/issues/feature/46/tasks/1793275218995.md +11 -0
  29. defstream-0.1.21/issues/feature/46/tasks/1793275501812.md +27 -0
  30. defstream-0.1.21/issues/feature/46/tasks/1793275955973.md +13 -0
  31. defstream-0.1.21/issues/feature/46/tasks/1793276099601.md +7 -0
  32. defstream-0.1.21/issues/feature/46/tasks/1793276518341.md +16 -0
  33. defstream-0.1.21/issues/feature/46/tasks/1793277424063.md +24 -0
  34. defstream-0.1.21/issues/feature/46/tasks/1793278267423.md +18 -0
  35. defstream-0.1.21/issues/feature/46/tasks/1793278764702.md +21 -0
  36. defstream-0.1.21/issues/feature/46/tasks/1793279849391.md +10 -0
  37. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Connection.py +5 -4
  38. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Consumer.py +60 -4
  39. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Protocol.py +9 -0
  40. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/__init__.py +2 -0
  41. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Executor.py +187 -33
  42. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Pool.py +41 -4
  43. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Worker.py +200 -50
  44. {defstream-0.1.20 → defstream-0.1.21}/tests/test_protocol_contracts.py +19 -0
  45. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_logging.py +1 -0
  46. defstream-0.1.21/tests/test_worker_recovery.py +842 -0
  47. {defstream-0.1.20 → defstream-0.1.21}/.github/ISSUE_TEMPLATE/01.feature.yml +0 -0
  48. {defstream-0.1.20 → defstream-0.1.21}/.github/ISSUE_TEMPLATE/02.bug.yml +0 -0
  49. {defstream-0.1.20 → defstream-0.1.21}/.github/ISSUE_TEMPLATE/03.fix.yml +0 -0
  50. {defstream-0.1.20 → defstream-0.1.21}/.github/ISSUE_TEMPLATE/04.task.yml +0 -0
  51. {defstream-0.1.20 → defstream-0.1.21}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  52. {defstream-0.1.20 → defstream-0.1.21}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  53. {defstream-0.1.20 → defstream-0.1.21}/.github/workflows/ci.yml +0 -0
  54. {defstream-0.1.20 → defstream-0.1.21}/.gitignore +0 -0
  55. {defstream-0.1.20 → defstream-0.1.21}/DefStream.egg-info/dependency_links.txt +0 -0
  56. {defstream-0.1.20 → defstream-0.1.21}/DefStream.egg-info/requires.txt +0 -0
  57. {defstream-0.1.20 → defstream-0.1.21}/DefStream.egg-info/top_level.txt +0 -0
  58. {defstream-0.1.20 → defstream-0.1.21}/LICENSE +0 -0
  59. {defstream-0.1.20 → defstream-0.1.21}/README.en.md +0 -0
  60. {defstream-0.1.20 → defstream-0.1.21}/README.md +0 -0
  61. {defstream-0.1.20 → defstream-0.1.21}/README.setup.md +0 -0
  62. {defstream-0.1.20 → defstream-0.1.21}/docs/Contracts.en.md +0 -0
  63. {defstream-0.1.20 → defstream-0.1.21}/docs/Contracts.md +0 -0
  64. {defstream-0.1.20 → defstream-0.1.21}/docs/GettingStarted.en.md +0 -0
  65. {defstream-0.1.20 → defstream-0.1.21}/docs/GettingStarted.md +0 -0
  66. {defstream-0.1.20 → defstream-0.1.21}/docs/README.en.md +0 -0
  67. {defstream-0.1.20 → defstream-0.1.21}/docs/README.md +0 -0
  68. {defstream-0.1.20 → defstream-0.1.21}/docs/Redis.en.md +0 -0
  69. {defstream-0.1.20 → defstream-0.1.21}/docs/Redis.md +0 -0
  70. {defstream-0.1.20 → defstream-0.1.21}/docs/Tasks.en.md +0 -0
  71. {defstream-0.1.20 → defstream-0.1.21}/docs/Tasks.md +0 -0
  72. {defstream-0.1.20 → defstream-0.1.21}/instructions/CODE.instructions.md +0 -0
  73. {defstream-0.1.20 → defstream-0.1.21}/instructions/DOCS.instructions.md +0 -0
  74. {defstream-0.1.20 → defstream-0.1.21}/instructions/README.instructions.md +0 -0
  75. {defstream-0.1.20 → defstream-0.1.21}/issues/bug/16/IMPLEMENTS.md +0 -0
  76. {defstream-0.1.20 → defstream-0.1.21}/issues/bug/16/ISSUE.md +0 -0
  77. {defstream-0.1.20 → defstream-0.1.21}/issues/bug/16/README.md +0 -0
  78. {defstream-0.1.20 → defstream-0.1.21}/issues/bug/16/REQUIREMENTS.md +0 -0
  79. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/1/IMPLEMENTS.md +0 -0
  80. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/1/README.md +0 -0
  81. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/1/REQUIREMENTS.md +0 -0
  82. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/1/TASK.md +0 -0
  83. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/1/ci.refs.json +0 -0
  84. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/CHANGELOG.md +0 -0
  85. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/IMPLEMENTS.md +0 -0
  86. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/PLAN.md +0 -0
  87. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/README.md +0 -0
  88. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/REQUIREMENTS.md +0 -0
  89. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/TASKS.md +0 -0
  90. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/implements/1793271568985.md +0 -0
  91. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/implements/1793272077065.md +0 -0
  92. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/tasks/1793271236067.md +0 -0
  93. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/tasks/1793271384432.md +0 -0
  94. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/tasks/1793271506089.md +0 -0
  95. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/tasks/1793271819682.md +0 -0
  96. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/47/tasks/1793272077066.md +0 -0
  97. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/8/IMPLEMENTS.md +0 -0
  98. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/8/README.md +0 -0
  99. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/8/REQUIREMENTS.md +0 -0
  100. {defstream-0.1.20 → defstream-0.1.21}/issues/feature/8/ci.refs.json +0 -0
  101. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/10/IMPLEMENTS.md +0 -0
  102. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/10/README.md +0 -0
  103. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/10/REQUIREMENTS.md +0 -0
  104. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/13/README.md +0 -0
  105. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/21/IMPLEMENTS.md +0 -0
  106. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/21/ISSUE.md +0 -0
  107. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/21/README.md +0 -0
  108. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/21/REQUIREMENTS.md +0 -0
  109. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/24/IMPLEMENTS.md +0 -0
  110. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/24/ISSUE.md +0 -0
  111. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/24/README.md +0 -0
  112. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/24/REQUIREMENTS.md +0 -0
  113. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/25/IMPLEMENTS.md +0 -0
  114. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/25/ISSUE.md +0 -0
  115. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/25/README.md +0 -0
  116. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/25/REQUIREMENTS.md +0 -0
  117. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/25/TASK.md +0 -0
  118. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/26/IMPLEMENTS.md +0 -0
  119. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/26/ISSUE.md +0 -0
  120. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/26/README.md +0 -0
  121. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/26/REQUIREMENTS.md +0 -0
  122. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/29/ISSUE.md +0 -0
  123. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/29/README.md +0 -0
  124. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/29/REQUIREMENTS.md +0 -0
  125. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/29/TASK.md +0 -0
  126. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/3/IMPLEMENTS.md +0 -0
  127. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/3/README.md +0 -0
  128. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/3/REQUIREMENTS.md +0 -0
  129. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/3/ci.refs.json +0 -0
  130. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/CHANGELOG.md +0 -0
  131. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/IMPLEMENTS.md +0 -0
  132. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/PLAN.md +0 -0
  133. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/README.md +0 -0
  134. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/REQUIREMENTS.md +0 -0
  135. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/TASKS.md +0 -0
  136. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/implements/1793257519898.md +0 -0
  137. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/implements/1793257826069.md +0 -0
  138. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/tasks/1793256929057.md +0 -0
  139. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/tasks/1793256990072.md +0 -0
  140. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/tasks/1793257216492.md +0 -0
  141. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/tasks/1793257519899.md +0 -0
  142. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/41/tasks/1793257826068.md +0 -0
  143. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/CHANGELOG.md +0 -0
  144. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/IMPLEMENTS.md +0 -0
  145. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/PLAN.md +0 -0
  146. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/README.md +0 -0
  147. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/REQUIREMENTS.md +0 -0
  148. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/TASKS.md +0 -0
  149. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/implements/1793260818660.md +0 -0
  150. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/implements/1793261447018.md +0 -0
  151. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/tasks/1793260818661.md +0 -0
  152. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/tasks/1793261447019.md +0 -0
  153. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/43/tasks/1793262314955.md +0 -0
  154. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/6/IMPLEMENTS.md +0 -0
  155. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/6/README.md +0 -0
  156. {defstream-0.1.20 → defstream-0.1.21}/issues/fix/6/REQUIREMENTS.md +0 -0
  157. {defstream-0.1.20 → defstream-0.1.21}/issues/task/18/IMPLEMENTS.md +0 -0
  158. {defstream-0.1.20 → defstream-0.1.21}/issues/task/18/README.md +0 -0
  159. {defstream-0.1.20 → defstream-0.1.21}/issues/task/18/REQUIREMENTS.md +0 -0
  160. {defstream-0.1.20 → defstream-0.1.21}/issues/task/32/IMPLEMENTS.md +0 -0
  161. {defstream-0.1.20 → defstream-0.1.21}/issues/task/32/ISSUE.md +0 -0
  162. {defstream-0.1.20 → defstream-0.1.21}/issues/task/32/PLAN.md +0 -0
  163. {defstream-0.1.20 → defstream-0.1.21}/issues/task/32/README.md +0 -0
  164. {defstream-0.1.20 → defstream-0.1.21}/issues/task/32/REQUIREMENTS.md +0 -0
  165. {defstream-0.1.20 → defstream-0.1.21}/issues/task/32/TASK.md +0 -0
  166. {defstream-0.1.20 → defstream-0.1.21}/issues/task/34/CHANGELOG.md +0 -0
  167. {defstream-0.1.20 → defstream-0.1.21}/issues/task/34/IMPLEMENTS.md +0 -0
  168. {defstream-0.1.20 → defstream-0.1.21}/issues/task/34/PLAN.md +0 -0
  169. {defstream-0.1.20 → defstream-0.1.21}/issues/task/34/README.md +0 -0
  170. {defstream-0.1.20 → defstream-0.1.21}/issues/task/34/REQUIREMENTS.md +0 -0
  171. {defstream-0.1.20 → defstream-0.1.21}/issues/task/36/CHANGELOG.md +0 -0
  172. {defstream-0.1.20 → defstream-0.1.21}/issues/task/36/IMPLEMENTS.md +0 -0
  173. {defstream-0.1.20 → defstream-0.1.21}/issues/task/36/PLAN.md +0 -0
  174. {defstream-0.1.20 → defstream-0.1.21}/issues/task/36/README.md +0 -0
  175. {defstream-0.1.20 → defstream-0.1.21}/issues/task/36/REQUIREMENTS.md +0 -0
  176. {defstream-0.1.20 → defstream-0.1.21}/samples/Broker/RabbitMQ/Sample.RPC.py +0 -0
  177. {defstream-0.1.20 → defstream-0.1.21}/samples/Broker/RabbitMQ/Sample.Stream.py +0 -0
  178. {defstream-0.1.20 → defstream-0.1.21}/samples/Broker/RabbitMQ/Sample.py +0 -0
  179. {defstream-0.1.20 → defstream-0.1.21}/samples/Broker/Redis/Sample.Stream.py +0 -0
  180. {defstream-0.1.20 → defstream-0.1.21}/samples/Broker/Redis/Sample.py +0 -0
  181. {defstream-0.1.20 → defstream-0.1.21}/samples/Broker/Sample.py +0 -0
  182. {defstream-0.1.20 → defstream-0.1.21}/samples/Worker/RabbitMQ/Client.py +0 -0
  183. {defstream-0.1.20 → defstream-0.1.21}/samples/Worker/RabbitMQ/Tasks.py +0 -0
  184. {defstream-0.1.20 → defstream-0.1.21}/samples/Worker/RabbitMQ/Worker.py +0 -0
  185. {defstream-0.1.20 → defstream-0.1.21}/samples/Worker/Redis/Sample.py +0 -0
  186. {defstream-0.1.20 → defstream-0.1.21}/samples/Worker/Sample.py +0 -0
  187. {defstream-0.1.20 → defstream-0.1.21}/setup.cfg +0 -0
  188. {defstream-0.1.20 → defstream-0.1.21}/setup.py +0 -0
  189. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Configuration.py +0 -0
  190. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Connection.py +0 -0
  191. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Consumer.py +0 -0
  192. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Errors.py +0 -0
  193. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Event.py +0 -0
  194. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Configuration.py +0 -0
  195. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Event.py +0 -0
  196. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Exchange.py +0 -0
  197. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Queue.py +0 -0
  198. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Serializer.py +0 -0
  199. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Stream.py +0 -0
  200. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/Subscription.py +0 -0
  201. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/RabbitMQ/__init__.py +0 -0
  202. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Configuration.py +0 -0
  203. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Connection.py +0 -0
  204. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Consumer.py +0 -0
  205. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Event.py +0 -0
  206. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Queue.py +0 -0
  207. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Serializer.py +0 -0
  208. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/Subscription.py +0 -0
  209. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/Redis/__init__.py +0 -0
  210. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Implements/__init__.py +0 -0
  211. {defstream-0.1.20 → defstream-0.1.21}/src/Broker/Priority.py +0 -0
  212. {defstream-0.1.20 → defstream-0.1.21}/src/Descriptor/DataObject.py +0 -0
  213. {defstream-0.1.20 → defstream-0.1.21}/src/Descriptor/Types.py +0 -0
  214. {defstream-0.1.20 → defstream-0.1.21}/src/Descriptor/Value.py +0 -0
  215. {defstream-0.1.20 → defstream-0.1.21}/src/Descriptor/__init__.py +0 -0
  216. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extension.py +0 -0
  217. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Metrics.py +0 -0
  218. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Ping.py +0 -0
  219. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Play.html +0 -0
  220. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Play.py +0 -0
  221. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Specifications.py +0 -0
  222. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Status.html +0 -0
  223. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/Status.py +0 -0
  224. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Extensions/__init__.py +0 -0
  225. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Inspector.py +0 -0
  226. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/Response.py +0 -0
  227. {defstream-0.1.20 → defstream-0.1.21}/src/Inspector/__init__.py +0 -0
  228. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Pattern.py +0 -0
  229. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Array.py +0 -0
  230. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Common.py +0 -0
  231. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Compare.py +0 -0
  232. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Condition.py +0 -0
  233. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/DataObject.py +0 -0
  234. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/DateTime.py +0 -0
  235. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Number.py +0 -0
  236. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Object.py +0 -0
  237. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Size.py +0 -0
  238. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/String.py +0 -0
  239. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/Type.py +0 -0
  240. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Patterns/__init__.py +0 -0
  241. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Validate.py +0 -0
  242. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/Validator.py +0 -0
  243. {defstream-0.1.20 → defstream-0.1.21}/src/Validator/__init__.py +0 -0
  244. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Message.py +0 -0
  245. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Run.py +0 -0
  246. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Specification.py +0 -0
  247. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/Startup.py +0 -0
  248. {defstream-0.1.20 → defstream-0.1.21}/src/Worker/__init__.py +0 -0
  249. {defstream-0.1.20 → defstream-0.1.21}/src/__init__.py +0 -0
  250. {defstream-0.1.20 → defstream-0.1.21}/tests/_support.py +0 -0
  251. {defstream-0.1.20 → defstream-0.1.21}/tests/test_broker_packaging.py +0 -0
  252. {defstream-0.1.20 → defstream-0.1.21}/tests/test_descriptor.py +0 -0
  253. {defstream-0.1.20 → defstream-0.1.21}/tests/test_inspector.py +0 -0
  254. {defstream-0.1.20 → defstream-0.1.21}/tests/test_inspector_logging.py +0 -0
  255. {defstream-0.1.20 → defstream-0.1.21}/tests/test_inspector_play.py +0 -0
  256. {defstream-0.1.20 → defstream-0.1.21}/tests/test_lifecycle_hooks.py +0 -0
  257. {defstream-0.1.20 → defstream-0.1.21}/tests/test_logging.py +0 -0
  258. {defstream-0.1.20 → defstream-0.1.21}/tests/test_pool_callbacks.py +0 -0
  259. {defstream-0.1.20 → defstream-0.1.21}/tests/test_process_signals.py +0 -0
  260. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_broker_methods.py +0 -0
  261. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_integration.py +0 -0
  262. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_priorities.py +0 -0
  263. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_rpc.py +0 -0
  264. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_rpc_integration.py +0 -0
  265. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_timeouts.py +0 -0
  266. {defstream-0.1.20 → defstream-0.1.21}/tests/test_rabbitmq_worker.py +0 -0
  267. {defstream-0.1.20 → defstream-0.1.21}/tests/test_redis.py +0 -0
  268. {defstream-0.1.20 → defstream-0.1.21}/tests/test_redis_integration.py +0 -0
  269. {defstream-0.1.20 → defstream-0.1.21}/tests/test_redis_logging.py +0 -0
  270. {defstream-0.1.20 → defstream-0.1.21}/tests/test_runner_hooks.py +0 -0
  271. {defstream-0.1.20 → defstream-0.1.21}/tests/test_specifications.py +0 -0
  272. {defstream-0.1.20 → defstream-0.1.21}/tests/test_status.py +0 -0
  273. {defstream-0.1.20 → defstream-0.1.21}/tests/test_status_ui.cjs +0 -0
  274. {defstream-0.1.20 → defstream-0.1.21}/tests/test_task_messages.py +0 -0
  275. {defstream-0.1.20 → defstream-0.1.21}/tests/test_task_priorities.py +0 -0
  276. {defstream-0.1.20 → defstream-0.1.21}/tests/test_validator_patterns.py +0 -0
  277. {defstream-0.1.20 → defstream-0.1.21}/tests/test_worker.py +0 -0
  278. {defstream-0.1.20 → defstream-0.1.21}/tests/test_worker_startup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.20
3
+ Version: 0.1.21
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
@@ -44,6 +44,33 @@ issues/feature/1/README.md
44
44
  issues/feature/1/REQUIREMENTS.md
45
45
  issues/feature/1/TASK.md
46
46
  issues/feature/1/ci.refs.json
47
+ issues/feature/46/CHANGELOG.md
48
+ issues/feature/46/IMPLEMENTS.md
49
+ issues/feature/46/PLAN.md
50
+ issues/feature/46/README.md
51
+ issues/feature/46/REQUIREMENTS.md
52
+ issues/feature/46/TASKS.md
53
+ issues/feature/46/implements/1793274693488.md
54
+ issues/feature/46/implements/1793275218996.md
55
+ issues/feature/46/implements/1793275899489.md
56
+ issues/feature/46/implements/1793276075771.md
57
+ issues/feature/46/implements/1793276518340.md
58
+ issues/feature/46/implements/1793277364116.md
59
+ issues/feature/46/implements/1793278267422.md
60
+ issues/feature/46/implements/1793278764701.md
61
+ issues/feature/46/implements/1793279849390.md
62
+ issues/feature/46/tasks/1793272872217.md
63
+ issues/feature/46/tasks/1793274121654.md
64
+ issues/feature/46/tasks/1793274231988.md
65
+ issues/feature/46/tasks/1793275218995.md
66
+ issues/feature/46/tasks/1793275501812.md
67
+ issues/feature/46/tasks/1793275955973.md
68
+ issues/feature/46/tasks/1793276099601.md
69
+ issues/feature/46/tasks/1793276518341.md
70
+ issues/feature/46/tasks/1793277424063.md
71
+ issues/feature/46/tasks/1793278267423.md
72
+ issues/feature/46/tasks/1793278764702.md
73
+ issues/feature/46/tasks/1793279849391.md
47
74
  issues/feature/47/CHANGELOG.md
48
75
  issues/feature/47/IMPLEMENTS.md
49
76
  issues/feature/47/PLAN.md
@@ -245,4 +272,5 @@ tests/test_task_messages.py
245
272
  tests/test_task_priorities.py
246
273
  tests/test_validator_patterns.py
247
274
  tests/test_worker.py
275
+ tests/test_worker_recovery.py
248
276
  tests/test_worker_startup.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.20
3
+ Version: 0.1.21
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
@@ -20,6 +20,8 @@ All hook interfaces are available from `DefStream.Worker`. Each hook option acce
20
20
 
21
21
  Set task hooks on the `Run` class with `setups=`, `completes=`, and `errors=`. Pool constructors accept `setups=` and `cleanups=`; the Worker constructor uses the same option names.
22
22
 
23
+ RabbitMQ consumption recovery preserves the Pool object and replaces its internal executor. `PoolSetup` runs again when a new execution unit starts. A retired ThreadPool runs `PoolCleanup` after its running task finishes; forcibly terminated ProcessPool processes do not guarantee task/pool cleanup. `WorkerSetup` runs only at initial startup and `WorkerCleanup` only at final shutdown, not on each recovery. See [consumption recovery](./RabbitMQ.en.md#worker-consumption-recovery) for limitations.
24
+
23
25
  ```python
24
26
  from signal import SIGINT, signal
25
27
 
@@ -20,6 +20,8 @@ DefStream의 훅은 작업, 실행 풀의 각 스레드·프로세스, Worker의
20
20
 
21
21
  작업 훅은 `Run` 클래스 정의에 `setups=`, `completes=`, `errors=`로 지정합니다. 풀 생성자는 `setups=`, `cleanups=`를 받고, Worker 생성자도 같은 옵션 이름을 사용합니다.
22
22
 
23
+ RabbitMQ 소비 장애 복구에서는 Pool 객체를 유지하고 내부 실행기를 교체합니다. 새 실행 단위가 시작될 때 `PoolSetup`이 다시 실행됩니다. 이전 ThreadPool의 `PoolCleanup`은 실행 중 작업이 끝난 뒤 실행되지만, 강제 종료한 ProcessPool 프로세스의 작업·풀 cleanup은 보장하지 않습니다. `WorkerSetup`은 최초 시작 때만, `WorkerCleanup`은 최종 종료 때만 실행하며 복구마다 반복하지 않습니다. 자세한 제한은 [소비 장애 복구](./RabbitMQ.md#worker-소비-장애-복구)를 참고하세요.
24
+
23
25
  ```python
24
26
  from signal import SIGINT, signal
25
27
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  Inspector tools, logging, and broker selection.
6
6
 
7
+ During RabbitMQ consumption recovery, the Worker clears its readiness state: Ping returns 503 until all subscriptions are restored and the consumption loop resumes. Unfinished Inspector contexts from the previous generation are closed as discarded, and old callbacks cannot change the new generation's metrics. See [RabbitMQ consumption recovery](./RabbitMQ.en.md#worker-consumption-recovery) for settings, retired-thread behavior, and redelivery limitations.
8
+
7
9
  ## Health checks and Play
8
10
 
9
11
  Inspector is optional and uses a Python standard-library HTTP server.
@@ -4,6 +4,8 @@
4
4
 
5
5
  Inspector, 로깅 및 브로커 선택 시 고려사항입니다.
6
6
 
7
+ RabbitMQ Worker는 소비 장애 복구 중 준비 상태를 해제하므로 Ping은 503을 반환하고, 전체 구독 복원 후 소비 루프를 다시 시작하면 준비 상태를 복원합니다. 이전 실행 세대의 미완료 Inspector context는 폐기 상태로 마무리하고, 이전 콜백이 새 실행 세대의 지표를 변경하지 못하게 합니다. 복구 설정과 잔존 스레드·메시지 재전달의 제한은 [RabbitMQ 소비 장애 복구](./RabbitMQ.md#worker-소비-장애-복구)를 참고하세요.
8
+
7
9
  ## 상태 확인과 Play
8
10
 
9
11
  선택 기능인 Inspector는 Python 표준 라이브러리 HTTP 서버를 사용합니다.
@@ -4,6 +4,50 @@
4
4
 
5
5
  Connections, queues, exchanges, workers, and RPC.
6
6
 
7
+ ## Worker consumption recovery
8
+
9
+ After a RabbitMQ consumption connection or channel failure, the Worker recovers its executor and connection in the same process, without a separate supervisor. This constructor excerpt uses an already configured `broker` and a DefStream `ThreadPool` or `ProcessPool` named `pool`; it is not a standalone program.
10
+
11
+ ```python
12
+ from DefStream.Worker import Worker
13
+
14
+
15
+ worker = Worker(
16
+ broker,
17
+ pool,
18
+ recovery_attempts=5,
19
+ recovery_interval=60,
20
+ )
21
+ ```
22
+
23
+ - `recovery_attempts` is the maximum number of recovery attempts after a failure, defaulting to `5`. Set it to `0` to propagate failure without recovery. Only non-negative integers are accepted; `bool` is rejected.
24
+ - `recovery_interval` is the fixed delay in seconds between failed recovery attempts, defaulting to `60`. Only finite non-negative `int` and `float` values are accepted; `bool` is rejected.
25
+ - The first attempt is immediate. Restoring the connection and every subscription resets the budget for the next failure. A `stop()` request interrupts the retry delay. Repeated short-lived recoveries can therefore continue indefinitely, with a new budget for each failure.
26
+ - Initial Worker connection/subscription errors propagate immediately. Recovery applies after the consumption loop starts, not to task-code errors, normal stop requests, or standalone Queue/Exchange RPC calls.
27
+
28
+ ```mermaid
29
+ flowchart TD
30
+ A[Consumption connection or channel failure] --> B[Invalidate previous task generation]
31
+ B --> C[Keep Pool object · replace internal executor]
32
+ C --> D[Recreate connection · Consumer · QoS · all subscriptions]
33
+ D -->|Success| E[Resume consumption · reset budget]
34
+ D -->|Recoverable failure within budget| F[Wait fixed interval]
35
+ F --> D
36
+ D -->|Unrecoverable or exhausted| G[Propagate Worker failure]
37
+ ```
38
+
39
+ Recovery covers connection-layer failures such as `StreamLostError`, broker connection/channel closure codes `320` and `541`, ACK-timeout closure code `406`, and consumption ending without an explicit stop request. Authentication, access-denial and incompatible-protocol errors, missing queues, and other `PRECONDITION_FAILED` errors are not blindly retried. ACK-timeout logs include the broker reason and affected subscription queue list; a shared channel's queue list does not identify which task caused the timeout.
40
+
41
+ The supplied Pool object is preserved. Previous ProcessPool task processes are forcibly terminated, so task/pool cleanup hooks are not guaranteed. Running ThreadPool tasks continue; the previous executor receives a shutdown request so its threads exit after those tasks finish. Unstarted tasks are discarded, and old success/failure callbacks cannot settle deliveries, publish RPC replies, or modify the new generation's state. A base `Pool` supplied with a custom executor does not support this recovery and raises an explicit error.
42
+
43
+ Recoverable connection errors while cleaning up the previous Consumer or connection are logged without preventing a new connection attempt. Non-recoverable cleanup errors propagate. A shutdown request arriving immediately before consumption starts prevents entry into the consumption loop.
44
+
45
+ Unexpected ProcessPool worker exits are detected even during graceful shutdown. Surviving task processes are terminated and reaped before unfinished tasks are reported as failed. Custom executors remain unsupported for recovery, but consumption failure requests `shutdown(wait=False)` so the original failure can propagate. This request does not forcibly terminate running tasks in a custom executor.
46
+
47
+ A `PoolCleanup` failure during graceful shutdown is distinguished from a process crash. Other processes may finish their running tasks before the cleanup error propagates from shutdown. Unexpected exits are checked even while results keep arriving, and normal-exit classification does not depend on result-queue notification order. An explicit Pool stop during restart remains effective after restart finishes.
48
+
49
+ The broker may redeliver unacknowledged messages from the closed channel. Previous threads may overlap with redelivered tasks, and effects such as completed database updates are not undone; message-processing idempotency remains the caller's responsibility. A thread whose task never finishes can retain resources and delay Python process exit. Exhaustion propagates Worker failure without waiting for previous tasks, but does not guarantee forced process termination.
50
+
7
51
  ## RabbitMQ usage
8
52
 
9
53
  Requires Python 3.9 or newer and a reachable RabbitMQ server; uses Pika.
@@ -4,6 +4,50 @@
4
4
 
5
5
  RabbitMQ 연결, 큐·Exchange, Worker와 RPC 사용법입니다.
6
6
 
7
+ ## Worker 소비 장애 복구
8
+
9
+ Worker는 RabbitMQ 소비 연결·채널 장애 후 같은 프로세스 안에서 실행기와 연결을 복구합니다. 별도 supervisor는 필요하지 않습니다. 다음은 이미 구성한 `broker`와 DefStream `ThreadPool` 또는 `ProcessPool`인 `pool`을 사용하는 생성자 설정 발췌입니다.
10
+
11
+ ```python
12
+ from DefStream.Worker import Worker
13
+
14
+
15
+ worker = Worker(
16
+ broker,
17
+ pool,
18
+ recovery_attempts=5,
19
+ recovery_interval=60,
20
+ )
21
+ ```
22
+
23
+ - `recovery_attempts`: 장애 이후 복구 시도의 최대 횟수입니다. 기본값은 `5`이고, `0`이면 복구하지 않고 실패를 전파합니다. 음수가 아닌 정수만 허용하며 `bool`은 거부합니다.
24
+ - `recovery_interval`: 실패한 복구 시도 사이의 고정 대기 시간이며 단위는 초입니다. 기본값은 `60`입니다. 유한한 음수 아닌 `int`·`float`만 허용하며 `bool`은 거부합니다.
25
+ - 첫 복구는 즉시 시도합니다. 연결과 전체 구독 복원에 성공하면 다음 장애는 새 횟수 한도로 처리합니다. 대기 중 `stop()` 요청은 대기를 중단합니다. 연결이 잠시 복원됐다가 다시 끊기는 장애가 반복되면 장애마다 한도가 초기화되어 복구가 계속될 수 있습니다.
26
+ - 최초 Worker 시작 중 연결·구독 오류는 즉시 전파합니다. 복구 정책은 소비 루프가 시작된 뒤의 장애에 적용하며, 작업 코드 오류·정상 stop·독립적인 Queue/Exchange RPC 호출을 재시도하지 않습니다.
27
+
28
+ ```mermaid
29
+ flowchart TD
30
+ A[소비 연결·채널 장애] --> B[이전 작업 세대 무효화]
31
+ B --> C[Pool 객체 유지 · 내부 실행기 교체]
32
+ C --> D[연결 · Consumer · QoS · 전체 구독 재생성]
33
+ D -->|성공| E[소비 재개 · 횟수 초기화]
34
+ D -->|복구 가능한 실패 · 한도 이내| F[고정 간격 대기]
35
+ F --> D
36
+ D -->|복구 불가 또는 한도 초과| G[Worker 실패 전파]
37
+ ```
38
+
39
+ `StreamLostError` 등 연결 계층 장애와 연결·채널의 브로커 종료 코드 `320`·`541`, ACK timeout으로 인한 `406`, 명시적 stop 없는 소비 루프 종료를 복구 대상으로 처리합니다. 인증·접근 권한·프로토콜 불일치, 큐 부재와 일반적인 `PRECONDITION_FAILED` 등은 무조건 재시도하지 않습니다. ACK timeout 로그는 브로커 종료 사유와 영향받는 구독 큐 목록을 제공합니다. 공유 채널의 큐 목록만으로 어느 작업이 timeout을 유발했는지 단정할 수는 없습니다.
40
+
41
+ 복구 시 사용자가 전달한 Pool 객체는 그대로 유지됩니다. ProcessPool의 이전 작업 프로세스는 강제 종료하며, 이때 작업·풀 cleanup 훅의 실행을 보장하지 않습니다. ThreadPool의 실행 중 작업은 강제 종료하지 않고 계속 실행되며, 이전 실행기에 종료를 요청하여 작업 완료 후 스레드를 정리합니다. 이전 미실행 작업은 폐기하고 이전 완료·실패 콜백은 새 정산·RPC 응답·상태에 영향을 주지 못하게 격리합니다. 사용자 지정 executor를 전달한 기본 `Pool`은 이 복구를 지원하지 않으며 명시적 오류가 발생합니다.
42
+
43
+ 복구 중 이전 Consumer·연결을 정리하다 복구 가능한 연결 오류가 발생하면 로그를 남기고 새 연결 생성을 계속합니다. 복구 불가능한 정리 오류는 전파합니다. 종료 요청이 소비 시작 직전에 도착해도 새 소비를 시작하지 않습니다.
44
+
45
+ ProcessPool 실행 프로세스가 비정상 종료하면 정상 종료를 기다리는 도중에도 감지합니다. 남은 작업 프로세스를 종료·회수한 후 미완료 작업을 실패 처리합니다. 사용자 지정 executor는 복구 대상이 아니지만 소비 실패로 종료할 때 `shutdown(wait=False)`를 요청하여 원래 실패를 전파합니다. 이 요청이 사용자 지정 executor의 실행 중 작업을 강제로 중단한다는 뜻은 아닙니다.
46
+
47
+ 정상 종료 중 `PoolCleanup`이 실패한 경우는 프로세스 crash와 구분합니다. 다른 프로세스의 실행 중 작업은 완료하도록 기다리고 종료 시 cleanup 오류를 전파합니다. 결과가 계속 도착해도 비정상 종료 검사는 계속하며, 정상 종료 여부는 결과 큐의 알림 도착 순서에 의존하지 않습니다. Pool 재시작 도중 명시적으로 Pool을 중지하면 재시작이 끝나도 중지 상태를 유지합니다.
48
+
49
+ 닫힌 채널의 미확인 메시지는 브로커가 재전달할 수 있습니다. 이전 스레드와 재전달 작업의 중복 실행, 이미 발생한 DB 변경 등의 부작용은 제거되지 않으므로 처리의 멱등성은 사용자 책임입니다. 종료되지 않는 이전 스레드는 자원을 점유하고 Python 프로세스 종료도 지연시킬 수 있습니다. 한도 초과 시 Worker는 이전 작업 완료를 기다리지 않고 실패를 전파하지만 프로세스 강제 종료를 보장하지는 않습니다.
50
+
7
51
  ## RabbitMQ 사용 방법
8
52
 
9
53
  Python 3.9 이상과 접속 가능한 RabbitMQ 서버가 필요하며 Pika를 사용합니다.
@@ -0,0 +1,26 @@
1
+ # RabbitMQ Worker 소비 장애 복구
2
+
3
+ RabbitMQ 소비 채널·연결 장애 후 모든 큐의 소비가 멈춘 채 Worker가 작업 종료를 기다리던 상황을 개선했습니다. Worker가 같은 프로세스 안에서 내부 실행기와 브로커 연결·구독을 다시 구성합니다. 별도 supervisor는 필요하지 않습니다.
4
+
5
+ ## 복구 설정
6
+
7
+ Worker 생성자의 `recovery_attempts` 기본값은 5회이고, `recovery_interval` 기본값은 60초입니다. 첫 복구는 즉시 수행하고 실패한 시도 사이에만 기다립니다. 전체 구독 복구에 성공하면 다음 장애의 횟수 한도를 초기화합니다. `recovery_attempts=0`이면 복구 없이 실패를 전파합니다.
8
+
9
+ ACK timeout, 연결 유실 등 복구 가능한 소비 장애를 처리하며 인증·권한 오류나 작업 코드 예외를 무조건 재시도하지 않습니다. 초기 연결·구독 오류는 기존처럼 전파하고, 정상 stop과 작업 완료 후 ACK 계약은 유지합니다.
10
+
11
+ ## 작업과 종료 시 주의사항
12
+
13
+ - 전달한 Pool 객체는 유지하지만 내부 실행기는 교체됩니다. ProcessPool의 이전 작업 프로세스는 종료하며 해당 작업의 cleanup 완료는 보장하지 않습니다.
14
+ - ThreadPool의 이전 작업은 계속 실행되고 완료 후 스레드를 정리합니다. 종료되지 않는 작업은 자원을 점유하거나 프로세스 종료를 지연할 수 있습니다.
15
+ - 이전 작업의 콜백은 새 Consumer의 ACK·RPC 응답과 새 실행 상태에 영향을 주지 않습니다. 그러나 메시지 재전달에 의한 중복 실행과 이미 발생한 외부 부작용은 남으므로 멱등성을 보장해야 합니다.
16
+ - 복구 한도 초과·비복구 장애는 Worker 실패로 전파합니다. Worker 프로세스의 강제 종료를 보장하지 않습니다.
17
+ - DefStream ThreadPool·ProcessPool을 지원하며 사용자 지정 executor를 전달한 Pool의 복구는 지원하지 않습니다.
18
+ - ProcessPool의 종료 대기 중 작업 프로세스가 비정상 종료해도 종료가 무기한 멈추지 않도록 감지하며, 생존 프로세스 회수 후 미완료 작업을 실패 처리합니다.
19
+ - 소비 시작 직전 종료 요청 누락과, 복구 가능한 이전 연결 정리 오류 때문에 재연결을 시도하지 못하던 문제를 수정했습니다.
20
+ - 사용자 지정 executor의 소비 실패 종료는 비대기 shutdown을 요청합니다. 실행기 재생성은 여전히 지원하지 않으며 실행 중인 사용자 작업의 강제 종료를 보장하지 않습니다.
21
+ - 정상 종료 중 PoolCleanup 오류 때문에 다른 프로세스의 작업까지 중단되거나 정상 프로세스 종료를 crash로 오인하던 문제를 수정했습니다.
22
+ - 결과가 계속 수신되는 동안에도 프로세스 crash를 감지하며, Pool 재시작과 중지가 겹쳐도 중지 요청을 유지합니다.
23
+ - Linux에서 ProcessPool 자식 프로세스가 공유 Event를 복원하지 못해 작업 실행이 실패하던 문제를 수정했습니다.
24
+ - 폐기된 ThreadPool의 cleanup 오류가 다른 작업의 종료를 기다리는 동안 같은 traceback을 반복 기록하던 문제를 수정했습니다. 서로 다른 실행 스레드의 오류는 각각 기록합니다.
25
+
26
+ 설정 예시와 지원 범위는 [RabbitMQ 매뉴얼](../../../docs/RabbitMQ.md#worker-소비-장애-복구)을 참고하세요.
@@ -0,0 +1,35 @@
1
+ # Worker 내부 소비 복구 구현 결과
2
+
3
+ ## 동작과 변경 이유
4
+
5
+ 이전에는 RabbitMQ 소비 루프가 끝나면 Worker가 최종 정리로 진입하고 `Pool.stop()`의 무기한 대기 때문에 소비만 멈춘 채 프로세스가 남을 수 있었습니다. 이제 RabbitMQ 소비 장애를 정상 stop과 구분하고 Worker 안에서 실행기·연결·구독을 복구합니다.
6
+
7
+ - `src/Worker/Worker.py`: keyword-only `recovery_attempts=5`, `recovery_interval=60`을 추가했습니다. 첫 복구는 즉시 시도하며 실패한 시도 사이에만 대기합니다. 전체 구독 복구 성공 시 다음 장애의 한도를 초기화합니다. 복구 중 준비 상태를 해제하고 최종 실패는 이전 작업 완료를 기다리지 않고 전파합니다.
8
+ - `src/Worker/Pool.py`: 기존 Pool 객체를 유지하는 `restart()`와 장애용 `stop(abort=True)`를 추가했습니다. 이전 Future·슬롯을 별도 세대로 유지해 늦은 콜백이 새 실행기를 오염시키지 않게 했습니다.
9
+ - `src/Worker/Executor.py`: 공개 multiprocessing Process API로 실행 프로세스를 소유·추적하여 사설 executor 속성 없이 강제 종료합니다. ThreadPool은 이전 작업을 기다리지 않고 폐기 경로를 반환하되 작업 완료 후 실행 스레드·monitor가 정리됩니다. setup·cleanup 오류는 부모 Future와 종료 경로로 전파합니다.
10
+ - `src/Broker/Protocol.py`의 `Recoverable`을 공개하고 Worker가 `isinstance(consumer, Recoverable)`로 지원 여부를 판별합니다. 별도 지원 플래그와 기본 Consumer의 `recoverable()`은 제거하여 비지원 Consumer의 기존 동작을 유지했습니다. RabbitMQ `Consumer.py`에 예상치 못한 소비 루프 종료, 주기적 이벤트 깨우기, ACK timeout 진단과 오류 분류를 추가했습니다. 연결 재생성은 Worker가 기존 브로커의 close/connect를 호출하므로 Connection·Event의 구현 변경은 필요하지 않았습니다.
11
+ - 이전 작업은 세대 검사로 정산·RPC 응답·새 pending·Inspector 상태에서 격리합니다. 이전 미완료 Inspector context는 폐기 상태로 마무리합니다.
12
+
13
+ ## 유지한 계약과 제한
14
+
15
+ 정상 stop의 graceful drain, 정상 ACK·오류 정책·RPC, 명시적 큐 선언, Redis 동작은 유지합니다. 사용자 지정 executor를 사용하는 Pool은 복구를 지원하지 않으며 명시적 오류를 냅니다. 초기 연결·구독 오류는 즉시 전파합니다.
16
+
17
+ 기존 ThreadPool 작업은 강제 종료하지 않습니다. 완료 후 정리되지만 무한 대기 작업은 남을 수 있고, 재전달 작업과의 중복 실행 및 외부 부작용은 사용자 측 멱등성으로 다뤄야 합니다. Worker 실패 전파가 Python 프로세스의 강제 종료를 보장하지는 않습니다. 강제 종료한 ProcessPool 작업의 cleanup 실행도 보장하지 않습니다.
18
+
19
+ ## 문서 및 검증
20
+
21
+ 한국어·영어 RabbitMQ·생명주기·운영 매뉴얼을 갱신했습니다. 최종 기능 회귀는 294개 중 279개 통과·15개 생략이며, 패키징 환경 검사와 실제 브로커 검증의 제한은 [최종 검증 기록](./tasks/1793275501812.md)에 정리했습니다. 커밋·푸시·PR 생성은 수행하지 않았습니다.
22
+
23
+ 후속 PR 리뷰 수정 뒤 기능 회귀는 302개 중 287개 통과·15개 생략입니다. Python 3.9·3.10 대상 회귀도 각각 36개 통과했으며 실제 프로세스 crash·콜백 순서와 Pika 타이머 대기 경로를 확인했습니다. 자세한 범위와 제한은 [PR 리뷰 수정 검증](./tasks/1793277424063.md)을 참고하세요.
24
+
25
+ ## 구현 기록
26
+
27
+ - [실행기 세대 교체와 Worker 복구](./implements/1793274693488.md)
28
+ - [훅 오류 전파 및 매뉴얼 반영](./implements/1793275218996.md)
29
+ - [복구 지원 Protocol 전환](./implements/1793275899489.md)
30
+ - [복구 가능 판별 메서드 이름 변경](./implements/1793276075771.md)
31
+ - [Python 3.9·3.10 테스트 패치 대상 수정](./implements/1793276518340.md)
32
+ - [PR 리뷰의 종료·복구 경계 결함 수정](./implements/1793277364116.md)
33
+ - [후속 PR 리뷰의 프로세스 종료 및 Pool 경쟁 수정](./implements/1793278267422.md)
34
+ - [Linux spawn 공유 Event 수명 보존](./implements/1793278764701.md)
35
+ - [폐기된 ThreadPool의 중복 오류 로그 방지](./implements/1793279849390.md)
@@ -0,0 +1,111 @@
1
+ # Worker 내부 실행기 및 RabbitMQ 연결 복구 계획
2
+
3
+ 작업 대상과 원문은 [이슈 기록](./README.md), 확정 동작과 인수 기준은 [요구사항 명세](./REQUIREMENTS.md)를 따릅니다. 아래 수행 단계에 실제 상태를 반영했습니다. 구현 설명은 [구현 종합](./IMPLEMENTS.md), 검증 결과와 환경 제한은 [최종 검증 기록](./tasks/1793275501812.md)을 참고하세요.
4
+
5
+ ## 구현 기준과 선행 결정
6
+
7
+ - 별도 supervisor 없이 Worker 안에서 복구합니다. 전달받은 Pool 객체는 유지하고 내부 실행기와 브로커 연결·Consumer를 교체합니다.
8
+ - 최대 복구 시도 횟수와 고정 간격은 설정 가능하게 하고 기본값은 5회·60초로 둡니다. backoff나 메시지 재시도 기능은 추가하지 않습니다.
9
+ - 장애 난 실행 세대를 먼저 격리합니다. ProcessPool의 이전 프로세스는 종료하고, ThreadPool의 이전 작업은 기다리지 않되 완료 후 이전 실행 환경이 정리되게 합니다.
10
+ - 정상 종료와 장애 복구를 분리하고 기존 ACK·실패 정책·RPC 및 Redis 소비 계약을 유지합니다.
11
+ - 복구 지원은 `Recoverable` Protocol의 런타임 구조 검사로 구분하며 별도 지원 플래그를 사용하지 않습니다. 기본 Consumer·Redis의 비지원 및 상속 없는 구현의 구조적 지원을 검증합니다.
12
+ - Python 지원 범위는 현재 패키지의 3.9 이상을 유지합니다. 최신 Python에만 있는 프로세스 종료 API를 전제로 구현하거나 사설 executor 속성 조작을 검증 없이 채택하지 않습니다.
13
+
14
+ 다음 세부 계약을 구체화해 요구사항에 반영했습니다. 사용자 확인대로 첫 시도는 즉시 수행하고 실패한 시도 사이에만 대기하며, 연결·전체 구독 복구 성공 시 다음 장애의 횟수를 초기화합니다. 공개 설정은 Worker의 `recovery_attempts`·`recovery_interval`이며, 지원 실행기와 오류 분류는 요구사항에 명시했습니다.
15
+
16
+ 1. 복구 설정의 공개 위치·이름·입력 검증과 기존 생성자 호출 호환성
17
+ 2. 최초 복구 시도 전 대기 여부, 복구 성공 후 후속 장애의 횟수 집계·초기화 기준
18
+ 3. 브로커 종료 사유·예외의 복구 가능 분류와 사용자 작업 예외를 구분하는 경계
19
+ 4. 지원 Python 범위에서의 프로세스 종료 수단, 사용자 지정 Pool·executor의 지원 계약
20
+
21
+ ## 수행 단계와 진행 상태
22
+
23
+ CI 후속 수정 완료: Linux spawn 자식이 Event를 복원하기 전에 부모의 세마포어가 제거되지 않도록 프로세스별 draining Event를 보관합니다. 수정 전 실패를 Windows의 참조 수명 테스트와 Linux 실제 프로세스로 재현하고 Linux 기능 회귀·Windows 대상 테스트로 검증했습니다.
24
+
25
+ 후속 리뷰 4건 보완 완료: 프로세스별 공유 완료 신호로 정상 종료 메시지 수신 경쟁을 제거하고 결과 수신 중에도 crash를 검사합니다. graceful cleanup 실패는 다른 작업 drain 이후 전파하며 Pool의 stop 요청은 restart 설치 단계에서 재확인합니다. 실제 프로세스 cleanup·제어된 큐 경계·동시 stop/restart 테스트와 구버전·전체 기능 회귀로 검증했습니다.
26
+
27
+ PR 리뷰 보완 완료: 종료 중 프로세스 장애·생존 프로세스 회수, 소비 시작 직전 stop 경쟁, 복구 가능한 정리 오류 이후 재연결, 사용자 지정 executor의 비대기 종료를 수정했습니다. 실제 프로세스 종료 및 Pika 소비 루프의 타이머 구동 테스트를 추가하고 Python 3.9·3.10과 기능 회귀로 검증했습니다. 기존 복구 정책과 ThreadPool 잔존 제한은 유지합니다. 결과와 미실행 범위는 [PR 리뷰 수정 검증](./tasks/1793277424063.md)을 참고하세요.
28
+
29
+ ### 1. 세부 계약 및 기준 동작 확인
30
+
31
+ 상태: 완료
32
+
33
+ - 위 선행 결정을 확정하고 R-02·R-07·R-09의 구체적인 계약을 보완합니다.
34
+ - 기존 Consumer의 종료 감지와 연결 재사용, Pool·Executor의 시작·종료·콜백, Worker의 pending·Inspector·cleanup 흐름을 대조합니다.
35
+ - 관련 기존 테스트에서 필요한 최소 기준 검증을 실행합니다. 전체 테스트를 선행 조건으로 삼지 않습니다.
36
+
37
+ ### 2. Pool 객체를 유지하는 실행기 생명주기 구현
38
+
39
+ 상태: 완료 / 선행: 1단계
40
+
41
+ 대상: [Pool.py](../../../src/Worker/Pool.py), [Executor.py](../../../src/Worker/Executor.py)
42
+
43
+ - 정상 종료와 별도로 장애 실행 세대를 폐기하고 내부 실행기를 재구성하는 경로를 만듭니다.
44
+ - 세대마다 Future·작업 슬롯·결과 큐·monitor·manager 및 worker 상태를 독립시켜 이전 결과가 새 상태에 섞이지 않게 합니다.
45
+ - 이전 미실행 작업을 폐기합니다. ProcessPool은 기존 자식 프로세스를 종료·회수하고, ThreadPool은 새 작업 수신을 중단한 뒤 실행 중 작업 완료 후 종료되게 합니다.
46
+ - 이전 ThreadPool 결과 수집과 자원 정리를 새 실행기 상태에 의존하지 않게 합니다. 현재 사용자 Future가 이미 실행 상태로 표시되는 점을 고려해 단순 `Future.cancel()`만으로 작업 폐기를 구현하지 않습니다.
47
+ - setup·cleanup 훅의 실행 단위를 확인합니다. 강제 종료한 프로세스에서 cleanup 완료를 보장한다고 표현하지 않습니다.
48
+
49
+ 대응: R-06·R-09, AC-03·AC-10·AC-11
50
+
51
+ ### 3. RabbitMQ 소비 장애 감지 및 연결 교체 경로 구현
52
+
53
+ 상태: 완료 / 선행: 1단계
54
+
55
+ 대상: [Consumer.py](../../../src/Broker/Implements/RabbitMQ/Consumer.py), [Connection.py](../../../src/Broker/Implements/RabbitMQ/Connection.py), 필요한 경우 [Event.py](../../../src/Broker/Implements/RabbitMQ/Event.py)
56
+
57
+ - 소비 루프 대기 중에도 채널 종료를 감지하고, 정상 stop과 예상치 못한 종료를 구분합니다.
58
+ - ACK timeout의 종료 사유를 일반 연결 장애와 구분해 기록합니다. 브로커가 제공하는 큐·consumer 식별 정보와 로컬 구독 정보를 대조하고 특정할 수 없는 원인을 임의의 큐로 단정하지 않습니다.
59
+ - 연결이 열린 채 채널만 닫힌 경우에도 복구 시 기존 연결을 재사용하지 않도록 명시적으로 교체합니다. 이전 응답 채널·publisher 참조도 새 연결에 맞게 정리합니다.
60
+ - 장애를 Worker 복구 경계로 전달하고, 작업 오류·직렬화 오류 등과 구분합니다. 구독 복원은 기존 명시적 큐 선언 계약을 유지하며 큐 자동 삭제·재생성을 하지 않습니다.
61
+
62
+ 대응: R-01·R-04·R-07·R-08, AC-04·AC-05
63
+
64
+ ### 4. Worker 복구 순서 및 이전 콜백 격리 구현
65
+
66
+ 상태: 완료 / 선행: 2·3단계
67
+
68
+ 대상: [Worker.py](../../../src/Worker/Worker.py), 1단계에서 정한 복구 설정의 공개 위치
69
+
70
+ - 소비 실패 시 이전 실행 세대를 무효화하고 이전 풀 작업을 정리한 뒤 내부 실행기·연결·Consumer를 새로 구성합니다. 기존 Run의 handler·QoS·구독을 복원하고 전체 등록이 성공한 뒤 소비를 재개합니다.
71
+ - 설정된 횟수와 고정 간격으로 복구하며 대기 중 stop 요청도 처리합니다. 부분 복구 실패의 자원을 정리하고 확정된 집계 기준을 적용합니다.
72
+ - 예약된 이전 완료·실패 콜백도 실행 시점에 세대를 확인하도록 하여 정산·RPC 응답 및 새 pending·Inspector 상태 변경을 차단합니다.
73
+ - 복구 가능성이 남아 있는 장애를 Worker 최종 종료로 처리하지 않습니다. 반대로 비복구 장애·한도 초과 시에는 무한 drain 없이 실패를 전파합니다.
74
+ - 준비·복구 중·최종 종료 상태와 기존 Inspector 표시를 일치시킵니다. 정상 stop은 기존 graceful shutdown 경로를 유지합니다.
75
+
76
+ 대응: R-02~R-07·R-09·R-10, AC-01~AC-03·AC-06·AC-07·AC-10·AC-11
77
+
78
+ ### 5. 대상 검증 및 사용자 매뉴얼 갱신
79
+
80
+ 상태: 대상·기능 회귀 및 매뉴얼 완료, 패키징 환경 검증 실패·실제 브로커 검증 미실행 / 선행: 4단계
81
+
82
+ - 아래 검증 계획에 따라 테스트하고 실패 원인을 이번 변경과 기존 문제로 구분합니다.
83
+ - [RabbitMQ.md](../../../docs/RabbitMQ.md)와 [RabbitMQ.en.md](../../../docs/RabbitMQ.en.md)에 설정·복구 순서·한도 초과·중복 실행 제한을 반영합니다. 공개 Pool 생명주기와 운영 설명에 영향이 있으면 [LifecycleHooks.md](../../../docs/LifecycleHooks.md)·[Operations.md](../../../docs/Operations.md) 및 대응 영어판도 갱신합니다.
84
+ - 흐름 이해에 필요한 복구 Mermaid 다이어그램과 실제 공개 API를 사용하는 예시를 한국어·영어로 맞춥니다. 루트 README 요약의 변경 필요성도 확인합니다.
85
+ - 단계별 구현은 `implements/`, 검증·확인은 `tasks/`에 기록하고 종합 결과를 정리합니다. 실제 구현된 사용자 영향만 변경 로그에 반영합니다.
86
+
87
+ 대응: R-11, AC-07~AC-09 및 전체 인수 기준
88
+
89
+ ## 검증 계획
90
+
91
+ | 검증 대상 | 방법과 통과 기준 |
92
+ | --- | --- |
93
+ | 설정·복구 횟수·간격 | AC-01: 기본 5회·60초와 사용자 지정 값을 검증합니다. 제어 가능한 시간 수단으로 정확한 시도 수·대기 간격을 확인하고, 대기 중 stop·부분 실패·한도 초과·복구 후 재장애를 포함합니다. |
94
+ | 종료 감지·로그 | AC-04·AC-05: 정산이 전혀 발생하지 않는 대기 상태의 채널 종료, ACK timeout과 일반 연결 종료를 각각 재현하는 테스트를 추가합니다. |
95
+ | 실행기 교체 | AC-10·AC-11: Pool 객체 동일성과 내부 실행기 교체를 확인합니다. 실제 ProcessPool의 이전 프로세스 종료, 실제 ThreadPool의 이전 작업이 실행 중인 동안 새 작업 처리 및 이전 작업 완료 후 자원 회수를 제한된 테스트로 확인합니다. |
96
+ | 이전 콜백 격리 | AC-03: 복구 직전·도중·이후의 성공·실패·예약 콜백과 RPC를 검증합니다. 새 정산·응답 호출 및 새 상태 오염이 없어야 합니다. |
97
+ | 정상·실패 종료 | AC-02·AC-06: 정상 stop의 drain을 유지하고 장애 종료는 이전 ThreadPool 완료를 기다리지 않고 실패를 전파해야 합니다. 종료되지 않는 스레드를 테스트에 남기지 않도록 테스트 해제 신호와 종료 상한을 둡니다. |
98
+ | 회귀·문서 | AC-07·AC-09: 아래 기존 테스트 중 변경 경로를 실행하고 공개 타입·호출 호환성, 양쪽 언어 문서의 설정·예시·링크를 확인합니다. |
99
+ | 브로커 통합 | AC-08: 사용할 수 있는 테스트 브로커와 부작용을 확인한 경우에만 실제 연결·채널 장애 후 복구를 검증합니다. 버전·장애 조건·실제 범위를 기록하고 미실행 검증은 제한사항으로 남깁니다. |
100
+
101
+ 기존 검증 위치는 [test_pool_callbacks.py](../../../tests/test_pool_callbacks.py), [test_worker.py](../../../tests/test_worker.py), [test_worker_startup.py](../../../tests/test_worker_startup.py), [test_rabbitmq_worker.py](../../../tests/test_rabbitmq_worker.py), [test_rabbitmq_logging.py](../../../tests/test_rabbitmq_logging.py), [test_rabbitmq_rpc.py](../../../tests/test_rabbitmq_rpc.py), [test_inspector.py](../../../tests/test_inspector.py), [test_redis.py](../../../tests/test_redis.py)입니다. 실제 브로커 검증에는 [test_rabbitmq_integration.py](../../../tests/test_rabbitmq_integration.py)를 참고합니다.
102
+
103
+ 기존 테스트 실행 방식과 CI를 따라 변경을 덮는 최소 선택자를 묶어 실행하고, 결과상 필요할 때 범위를 확대합니다. Python 수정 전 공통 Python 지침을 읽고, 소스·로그 문구는 프로젝트의 영어 정책을 적용합니다. 문서와 사용자 설명은 한국어로 작성합니다.
104
+
105
+ ## 전환·실패 시 처리와 완료 기준
106
+
107
+ - 큐 스키마나 운영 데이터를 변경하지 않습니다. 외부 supervisor·배포 정책 변경도 수행하지 않습니다.
108
+ - 새 설정을 생략한 기존 호출의 호환성을 유지하되, 소비 장애 시 실행기·연결 복구가 발생하는 의도된 동작 변화와 ThreadPool 제한은 매뉴얼에 명시합니다.
109
+ - 복구 중 만들어진 일부 자원도 다음 시도나 최종 실패 전에 정리합니다. 실패를 정상 동작으로 대체하거나 이전의 고장 난 실행기를 재사용하지 않습니다.
110
+ - 되돌림이 필요하면 해당 코드·설정 변경을 되돌려 기존 동작으로 복원합니다. 이미 실행된 작업의 외부 효과나 메시지 재전달을 되돌리는 데이터 복구는 제공하지 않습니다.
111
+ - 완료 시 AC-01~AC-11의 실제 충족 여부, 문서·코드 일치, 이전 세대 자원과 콜백의 잔존 영향, 미실행 검증을 확인합니다. 커밋·푸시·PR 생성은 별도 요청 없이 수행하지 않습니다.
@@ -0,0 +1,75 @@
1
+ # RabbitMQ Consumer 소비 채널 종료 시 최대 횟수 내 복구와 한도 초과 시 프로세스 종료
2
+
3
+ ## 이슈와 작업 상태
4
+
5
+ - 원본: GitHub Issues [#46](https://github.com/yong5eon/DefStream/issues/46)
6
+ - 제목: RabbitMQ Consumer 소비 채널 종료 시 최대 횟수 내 복구와 한도 초과 시 프로세스 종료
7
+ - 상태: Open
8
+ - 유형·레이블: 기능 (`feature`), `rabbitmq` (원본에 적용됨)
9
+ - 작업 대상: `yong5eon/DefStream`, 작업 브랜치 `feature/46`
10
+ - 분기 기준: 깨끗한 로컬 `master`의 `85174f0`
11
+ - 병합 대상: `master`
12
+ - 로컬 진행 상태: 구현·매뉴얼·기능 회귀 완료, 패키징 환경 검사 실패 및 실제 브로커 검증 미실행
13
+ - 댓글: 없음
14
+
15
+ ## 관련 이슈
16
+
17
+ - [#47 Subscription 작업별 consumer timeout 지원](https://github.com/yong5eon/DefStream/issues/47): ACK timeout 자체를 다루는 별도 이슈
18
+
19
+ ## 작업 문서
20
+
21
+ - [요구사항 명세](./REQUIREMENTS.md)
22
+ - [작업 계획](./PLAN.md)
23
+ - [구현 종합](./IMPLEMENTS.md)
24
+ - [작업 종합](./TASKS.md)
25
+ - [변경 로그](./CHANGELOG.md)
26
+ - [최신 구현 기록](./implements/1793279849390.md)
27
+ - [최신 작업 기록](./tasks/1793279849391.md)
28
+
29
+ ## 이슈 원문
30
+
31
+ ### 해결하려는 문제
32
+
33
+ - RabbitMQ `Consumer`의 소비 채널이 브로커에 의해 닫히면 소비가 그대로 끝나고 다시 시작되지 않음
34
+ - `Worker`는 모든 `Run`을 하나의 `Consumer`와 소비 채널로 구독하므로, 한 큐의 문제로 채널이 닫혀도 **모든 큐의 소비가 중단**됨
35
+ - 소비가 끝난 뒤 `finally`의 `Pool.stop()`이 `Executor.shutdown(wait=True)`로 실행 중인 작업을 기한 없이 기다리므로, 프로세스는 종료되지 않고 소비만 멈춘 상태로 남음
36
+ - 그 사이 완료된 작업은 정산할 수 없어 `Cannot confirm delivery after Consumer exit` 로그만 반복됨
37
+
38
+ 실제 발생 사례(RabbitMQ 3.13.7)
39
+
40
+ 1. 장시간 작업이 전역 `consumer_timeout`(3시간)을 넘김
41
+ 2. 브로커가 `PRECONDITION_FAILED - delivery acknowledgement on channel 1 timed out`으로 채널 종료
42
+ 3. 해당 채널의 모든 미확인 메시지는 브로커가 Ready로 재큐잉
43
+ - 작업 예외가 아니라서 `on_error=Reject()` 등 오류 정책이 적용되지 않음
44
+ 4. 이후 `nack failed ... ChannelClosedByBroker` → `Consumption loop ended` → `Consumer exited with unconfirmed deliveries`
45
+ 5. 프로세스는 살아 있으나 모든 소비가 중단되고 헬스 체크는 계속 503 반환
46
+
47
+ ### 제안하는 해결 방법
48
+
49
+ - 복구 가능한 브로커·연결 계층 예외로 소비가 끝나면 지정한 최대 횟수 안에서 소비를 복구
50
+ - 대상 예: `ChannelClosedByBroker`, `ConnectionClosedByBroker`, `StreamLostError`
51
+ - `stop()` 호출에 의한 정상 종료와 구분
52
+ - 복구 순서는 연결·채널 재생성 → `basic_qos` → 기존 구독 재등록
53
+ - 최대 횟수를 넘거나 복구할 수 없는 예외이면 소비 실패를 전파하여 프로세스가 자연스럽게 종료되도록 처리
54
+ - 종료 시 실행 중인 작업의 대기 시간에 상한 필요
55
+ - 닫힌 채널의 delivery는 어차피 정산할 수 없으므로 기한 없이 기다리지 않음
56
+ - 이전 채널의 delivery tag는 새 채널에서 정산하지 않고 폐기
57
+ - 새 채널에 이전 tag로 `ack`·`nack`하면 `PRECONDITION_FAILED - unknown delivery tag`로 새 채널도 닫힘
58
+ - 채널 세대를 구분하여 이전 세대의 완료·실패 정산은 경고 로그 후 무시
59
+ - ACK timeout(`PRECONDITION_FAILED` + `acknowledgement ... timed out`)으로 닫힌 경우는 일반 연결 끊김과 구분하여 경고
60
+ - 해당 작업이 timeout을 넘겼다는 사실과 큐를 로그로 확인할 수 있어야 함
61
+ - 채널 종료 감지는 정산 시점의 예외에 의존하지 않음
62
+ - 실측 결과 `process_data_events()` 대기 중에는 브로커가 채널을 닫아도 예외가 발생하지 않아 종료를 놓칠 수 있음
63
+ - `on_close` 콜백 또는 `channel.is_closed` 확인 필요
64
+
65
+ ### 고려한 대안
66
+
67
+ - 컨테이너 재시작 정책만으로 복구
68
+ - 현재는 프로세스가 종료되지 않아 재시작 정책이 동작하지 않음
69
+ - 복구 한도를 넘었을 때 프로세스를 종료시키는 부분은 이 방식과 함께 사용 가능
70
+ - ACK timeout 자체를 피하는 방법은 작업별 consumer timeout 지원 이슈에서 별도로 다룸
71
+
72
+ ### 추가 정보
73
+
74
+ - 복구 후 같은 메시지가 재전달되면 이전 작업 스레드가 아직 실행 중이어서 중복 실행될 수 있음. 메시지 처리의 멱등성은 사용하는 쪽에서 보장해야 함
75
+ - 참고: [RabbitMQ Consumers - Delivery Acknowledgement Timeout](https://www.rabbitmq.com/docs/consumers#acknowledgement-timeout)
@@ -0,0 +1,119 @@
1
+ # RabbitMQ Consumer 복구 및 종료 요구사항
2
+
3
+ ## 목적
4
+
5
+ RabbitMQ 브로커가 소비 채널을 닫았을 때 소비가 영구 중단된 채 Worker 프로세스만 남는 상황을 방지합니다. 복구 가능한 소비 연결·채널 장애는 정해진 한도 안에서 복구하고, 복구할 수 없는 장애나 한도 초과는 숨기지 않고 실패로 드러내야 합니다.
6
+
7
+ 이슈 원문과 작업 브랜치·병합 대상은 [이슈 기록](./README.md)을 기준으로 합니다. 원문의 해결 제안과 이후 사용자 확정을 구분하며, 아래 요구사항은 사용자와 합의한 Worker 내부 복구 방식을 반영합니다. API와 오류 분류는 [구체화한 구현 계약](#구체화한-구현-계약)에 정리했습니다.
8
+
9
+ ## 분석과 근거
10
+
11
+ ### 이슈에서 요청한 결과
12
+
13
+ - 브로커·연결 계층의 복구 가능한 장애가 발생하면 횟수 제한 안에서 소비를 재개합니다.
14
+ - `stop()`에 의한 정상 종료는 장애 복구와 구분합니다.
15
+ - 연결·채널을 복구한 뒤 QoS와 기존 구독을 다시 적용합니다.
16
+ - 채널이 닫힌 동안 전달된 이벤트의 delivery tag를 새 채널에서 정산하지 않습니다.
17
+ - 복구 한도를 넘거나 복구할 수 없는 장애는 Worker 실패로 전파하고, 작업 종료가 무기한 지연되지 않게 합니다.
18
+ - ACK timeout으로 인한 채널 종료는 일반 연결 장애와 구분해 큐와 timeout 초과 사실을 로그로 확인할 수 있게 합니다.
19
+
20
+ 이슈에 적힌 RabbitMQ 3.13.7의 장애 사례와 `process_data_events()` 동작은 이슈 작성자의 실측 보고입니다. 이번 문서 작성 과정에서 실제 브로커로 재현한 결과는 아닙니다.
21
+
22
+ ### 현재 코드와 요구사항의 차이
23
+
24
+ - [RabbitMQ Consumer](../../../src/Broker/Implements/RabbitMQ/Consumer.py)는 생성 시 채널과 QoS를 설정하고, `subs()` 호출 때 현재 채널에 구독을 등록합니다. `run()`은 `start_consuming()`을 실행하고 예외를 다시 던지지만, 연결 재생성이나 재구독은 하지 않습니다. `stop()`은 명시적 종료 요청으로 소비를 중지합니다.
25
+ - [Worker](../../../src/Worker/Worker.py)는 등록된 여러 `Run`을 하나의 Consumer에 구독합니다. 소비 루프가 끝나면 `finally`에서 pending delivery를 기록하고 작업 풀을 정리하므로, 공유 채널 장애가 여러 큐의 소비에 영향을 줄 수 있습니다.
26
+ - [RabbitMQ Event](../../../src/Broker/Implements/RabbitMQ/Event.py)는 전달 당시의 채널과 delivery tag를 보관하여 `ack()`, `nack()`, `reject()`에 사용합니다. 따라서 채널 교체 후 이전 이벤트의 정산을 새 채널에 보내지 않도록 세대를 구분해야 합니다.
27
+ - [Pool](../../../src/Worker/Pool.py)의 `stop()`은 `shutdown(wait=True)`를 호출합니다. [Executor](../../../src/Worker/Executor.py)도 대기 옵션이 참이면 내부 monitor와 manager의 종료를 기다립니다. 소비 실패 후 이 경로에서 실행 중인 작업을 무기한 기다릴 수 있다는 이슈의 문제 제기와 일치합니다.
28
+ - [RabbitMQ Worker 테스트](../../../tests/test_rabbitmq_worker.py)와 [Worker 테스트](../../../tests/test_worker.py)는 구독 등록·정산·Worker 동작을 검증하는 기존 회귀 테스트 위치입니다. 장애 복구, 채널 세대 분리, 실패 종료에 대한 인수 검증은 추가로 필요합니다.
29
+
30
+ ### 범위 해석
31
+
32
+ - 복구 대상은 소비 연결·채널의 장애이며, 사용자 작업에서 발생한 예외를 다시 실행하는 기능은 아닙니다.
33
+ - 복구가 성공하면 기존 큐와 handler 등록을 보존하고 소비를 재개해야 합니다. 채널 복구를 이유로 큐를 삭제하거나 메시지를 수동 정산하지 않습니다.
34
+ - 채널 종료 시 브로커가 미확인 메시지를 재전달할 수 있으므로 복구 중 원래 작업과 재전달 작업이 겹칠 가능성이 있습니다. 이슈는 메시지 처리의 멱등성을 사용하는 쪽의 책임으로 둡니다. 이 기능은 exactly-once 처리나 중복 실행 방지를 약속하지 않습니다.
35
+ - 원문의 제안 이후 사용자는 별도 supervisor 없이 Worker 안에서 실행 풀과 연결을 복구하는 방향을 확정했습니다. 원문은 보존하되, 구현 방향은 아래 합의를 따릅니다.
36
+
37
+ ### 사용자와 확정한 복구 방식
38
+
39
+ 1. 소비 연결·채널 장애를 감지하면 이전 실행 세대의 작업과 정산 경로를 분리하고 소비를 중단합니다.
40
+ 2. 사용자가 전달한 Pool 객체는 유지하면서 내부 실행기에 종료를 요청합니다. ProcessPool의 기존 작업 프로세스는 종료하고, ThreadPool의 실행 중 작업은 완료를 기다리지 않습니다.
41
+ 3. Pool의 내부 실행기를 새로 구성하고, 브로커 연결부터 다시 생성한 뒤 소비 채널·QoS·기존 구독을 복원합니다. 닫힌 채널만 새로 만드는 복구에 한정하지 않습니다.
42
+ 4. 복구 시도 한도와 시도 간 고정 간격을 설정할 수 있어야 하며 기본값은 각각 5회와 1분입니다. 별도 backoff나 외부 supervisor는 도입하지 않습니다.
43
+
44
+ 구현 시작 전 추가 확인에 따라 첫 복구는 즉시 시도하며, 실패한 시도 사이에만 고정 간격을 적용합니다. 연결과 전체 구독 복구가 성공하면 다음 장애의 시도 횟수를 초기화합니다. 따라서 짧은 복구 성공과 장애가 반복되는 경우 Worker 전체 생애의 누적 복구 횟수를 제한하지는 않습니다.
45
+
46
+ ThreadPool의 이전 작업은 강제 종료하지 않습니다. 이전 실행기는 새 작업을 받지 않고, 이미 실행 중인 작업이 끝난 뒤 해당 스레드와 실행 환경이 정리되어야 합니다. 작업 자체가 끝나지 않으면 스레드도 남을 수 있습니다. 그동안 이전 작업과 재전달 작업의 중복 실행, 외부 부작용, 자원 점유 및 프로세스 종료 지연 가능성은 수용하는 제한입니다.
47
+
48
+ ## 확정 요구사항
49
+
50
+ ### 포함 범위
51
+
52
+ | ID | 요구사항 |
53
+ | --- | --- |
54
+ | R-01 | RabbitMQ 소비 중 연결·채널 장애를 감지해야 합니다. 감지는 ACK·NACK 시점의 예외에만 의존하지 않아야 하며, 소비 이벤트 루프가 대기 중인 경우에도 채널 종료를 놓치지 않아야 합니다. |
55
+ | R-02 | 복구 가능한 브로커·연결 계층 장애는 설정 가능한 최대 복구 시도 횟수 안에서만 복구해야 합니다. 기본값은 5회이며, 복구 시도 간 고정 대기 간격도 설정 가능해야 하고 기본값은 1분이어야 합니다. |
56
+ | R-03 | `Worker.stop()` 등 명시적 정상 종료는 장애 복구 시도로 처리하지 않아야 합니다. 정상 종료 시 기존 graceful shutdown 동작은 유지해야 합니다. |
57
+ | R-04 | Worker 안에서 Pool의 내부 실행기와 브로커 연결부터 복구하고, 새 소비 채널에 기존 QoS와 모든 등록 큐·handler를 다시 적용한 뒤 소비를 재개해야 합니다. 일부 구독만 복구된 상태를 성공으로 처리해서는 안 됩니다. 별도 supervisor 프로세스나 Worker 프로세스 재시작을 복구의 전제로 두지 않아야 합니다. |
58
+ | R-05 | 복구에 성공하더라도 기존 메시지의 ACK·NACK·REJECT 정책, QoS 및 RPC 응답 동작을 변경하지 않아야 합니다. 사용자 작업 오류를 소비 연결 장애로 취급하거나 작업 재시도를 추가하지 않아야 합니다. |
59
+ | R-06 | 이전 채널·실행기의 완료·실패 콜백은 이전 delivery tag로 정산하거나 새 Consumer를 통해 RPC 응답을 보내서는 안 됩니다. 폐기 시 경고를 남기고, 이전 콜백이 새 실행기의 Future·슬롯, Worker의 새 pending·Inspector 상태를 변경하지 않도록 실행 세대를 격리해야 합니다. 이전 세대의 상태도 해당 작업 완료·폐기에 맞춰 정리해야 합니다. |
60
+ | R-07 | 이슈의 `ChannelClosedByBroker`, `ConnectionClosedByBroker`, `StreamLostError` 예시를 포함해 소비 장애의 원인과 복구 가능 여부를 분류해야 합니다. 작업 코드 예외를 복구 대상으로 혼동하지 않아야 하며, 복구할 수 없는 예외와 시도 한도 초과는 조용히 무시하거나 정상 종료처럼 보이지 않게 Worker 실패로 전파해야 합니다. |
61
+ | R-08 | ACK timeout으로 채널이 닫힌 경우 일반 연결 장애와 구분 가능한 경고를 남겨야 합니다. 로그에는 확인 가능한 범위에서 원인, 관련 큐, ACK timeout 초과 사실이 포함되어야 합니다. |
62
+ | R-09 | 사용자가 전달한 Pool 객체는 유지하고 내부 실행기만 종료·재생성할 수 있어야 합니다. 장애 시 기존 실행기의 미실행 작업은 새 실행기에 이월하지 않고 폐기하며, ProcessPool의 기존 작업 프로세스는 종료해야 합니다. ThreadPool은 실행 중 작업의 완료를 기다리지 않고 복구를 진행하되, 이전 실행기에 종료를 요청하여 작업 완료 후 스레드와 관련 자원을 정리해야 합니다. 정상 종료의 기존 작업 대기 계약은 유지해야 합니다. |
63
+ | R-10 | 복구 불가 또는 한도 초과 시 이전 작업의 완료를 무기한 기다리지 않고 Worker 실행 경로에 실패를 전파해야 합니다. 소비 중단을 건강한 상태나 성공으로 보고해서는 안 됩니다. 다만 끝나지 않는 이전 ThreadPool 작업 때문에 Python 프로세스 종료가 지연될 수 있으며, 이번 범위에서는 그 프로세스의 강제 종료를 보장하지 않습니다. |
64
+ | R-11 | 새 동작의 사용자 설정·운영 방법이 확정되면 한국어·영어 RabbitMQ 매뉴얼에 해당 설정, 실패·재전달 동작과 제한사항을 반영해야 합니다. 매뉴얼 갱신 필요 여부와 대상은 구현 시 확정된 공개 계약에 맞춰 정합니다. |
65
+
66
+ ### 제외 범위
67
+
68
+ - 작업 코드 예외에 대한 재시도, retry queue, 지연 재시도 및 중복 제거
69
+ - exactly-once 처리 또는 메시지 처리의 멱등성 보장
70
+ - 메시지 처리 중 ACK 연장, consumer timeout 설정 자체의 추가 또는 변경. consumer timeout 설정은 [관련 이슈 #47](https://github.com/yong5eon/DefStream/issues/47)에서 별도로 다룹니다.
71
+ - `Run`마다 연결·채널을 분리하거나 ThreadPool 작업을 별도 프로세스로 격리하는 구조 변경
72
+ - 운영 환경의 RabbitMQ 설정·큐·컨테이너 재시작 정책 변경
73
+ - 별도 supervisor, 외부 프로세스 재시작 정책 및 backoff 도입
74
+ - 실행 중인 Python 스레드 또는 Worker 프로세스의 강제 종료
75
+
76
+ ## 인수 기준
77
+
78
+ | ID | 검증 기준 |
79
+ | --- | --- |
80
+ | AC-01 | 기본 설정에서 복구 시도가 최대 5회이며 시도 간 간격이 고정 60초임을 확인합니다. 사용자 지정 횟수·간격도 적용되며, 성공 시 새 연결·소비 채널에 기존 QoS와 모든 구독·handler가 복원되어 소비가 재개됩니다. |
81
+ | AC-02 | `stop()`에 의한 정상 종료는 재연결·재구독하지 않으며, 기존 graceful shutdown과 ACK·NACK·REJECT 및 RPC 회귀 테스트가 유지됩니다. |
82
+ | AC-03 | 이전 실행 세대의 성공·실패 콜백이 복구 전후 어느 시점에 발생해도 새 채널의 정산·RPC 응답 API를 호출하지 않고 새 Future·슬롯·pending·Inspector 상태를 변경하지 않습니다. 폐기 경고와 이전 세대 상태의 정리를 확인합니다. |
83
+ | AC-04 | 소비 이벤트 루프가 대기 중 채널이 닫히는 경우에도 종료를 감지하고 복구 또는 실패 처리를 시작합니다. ACK·NACK 시점에 발생하는 오류에만 의존하지 않음을 테스트로 확인합니다. |
84
+ | AC-05 | ACK timeout 장애 로그에서 일반 연결 장애와 원인을 구분할 수 있고, 관련 큐와 timeout 초과 사실을 확인할 수 있습니다. |
85
+ | AC-06 | 비복구 장애 및 복구 한도 초과가 Worker 실패로 전파됩니다. Worker의 실패 전파가 이전 ThreadPool 작업 완료를 기다리지 않으며, Python 프로세스 자체의 강제 종료 보장과 구분됨을 검증합니다. |
86
+ | AC-07 | 기존 RabbitMQ Consumer·Worker 테스트와 새 장애·복구 테스트가 통과하고, Redis 소비 및 정상 Worker 종료 동작에 회귀가 없습니다. |
87
+ | AC-08 | 실제 브로커 검증을 수행한 경우에만 그 결과를 Mock 테스트와 구분해 기록합니다. 실행하지 않은 RabbitMQ 장애 주입이나 프로세스 종료 검증은 통과한 것으로 보고하지 않습니다. |
88
+ | AC-09 | 공개 설정이나 운영 동작이 바뀌면 한국어·영어 매뉴얼의 설명과 예제가 서로 일치하고 구현된 계약을 정확히 반영합니다. |
89
+ | AC-10 | 복구 전후 Pool 객체의 동일성이 유지되고 내부 실행기는 교체됩니다. ProcessPool의 이전 작업 프로세스 종료와 ThreadPool의 이전 작업 완료 후 스레드·관련 자원 정리를 각각 검증합니다. 이전 미실행 작업은 실행되지 않아야 합니다. |
90
+ | AC-11 | 종료되지 않은 이전 ThreadPool 작업이 있는 동안에도 새 실행기가 작업을 받을 수 있습니다. 같은 메시지의 중복 실행 가능성을 수용하되, 이전 작업이 끝난 뒤 새 실행기를 종료하거나 오염시키지 않음을 확인합니다. |
91
+
92
+ ## 구체화한 구현 계약
93
+
94
+ 사용자 확인 및 현재 API·지원 Python 범위 분석에 따라 다음 계약으로 구현했습니다.
95
+
96
+ | 항목 | 확인할 내용 |
97
+ | --- | --- |
98
+ | 복구 설정 | Worker의 keyword-only `recovery_attempts=5`, `recovery_interval=60`으로 제공합니다. 횟수는 음수 아닌 정수이고 `0`이면 복구하지 않습니다. 간격은 초 단위의 유한한 음수 아닌 수이며 두 값 모두 `bool`은 거부합니다. 첫 시도는 즉시 실행하고 전체 구독 복원 성공 시 다음 장애의 한도를 초기화합니다. |
99
+ | 복구 대상 | 연결 계층 AMQP 오류, 브로커 연결·채널 종료 코드 `320`·`541`, ACK timeout `406`, 명시적 stop 없는 소비 루프 종료를 복구합니다. 인증·권한·프로토콜 불일치, 큐 부재·다른 `406`, 사용자 handler·decode 오류는 재시도하지 않습니다. 최초 연결·구독 단계의 오류는 기존처럼 전파합니다. |
100
+ | 실행기 생명주기 | DefStream ThreadPool·ProcessPool과 DefStream Executor를 사용하는 Pool을 지원합니다. 사용자 지정 executor의 복구는 명시적 오류로 거부합니다. Python 3.9 이상에서 공개 multiprocessing Process API로 소유 프로세스를 추적·종료하며, 실행 단위의 setup·cleanup 오류 전파를 유지합니다. 강제 종료한 프로세스의 cleanup 완료는 보장하지 않습니다. |
101
+
102
+ 복구 지원 여부는 `@runtime_checkable`을 적용한 `Recoverable` Protocol의 `is_recoverable(error: BaseException) -> bool` 계약으로 판별합니다. 별도 지원 플래그나 기본 Consumer의 기본 구현은 두지 않습니다. 현재 브로커 구현 중 RabbitMQ Consumer가 이 계약을 제공하며 Redis 등 미지원 Consumer의 기존 실패·종료 경로는 유지합니다. ACK timeout 로그의 구독 큐 목록은 공유 채널에 영향받는 큐들이며, 브로커 사유에 정보가 없다면 특정 작업이 원인이라고 단정하지 않습니다.
103
+
104
+ PR 리뷰에 따른 기존 계약의 보완 사항은 다음과 같습니다.
105
+
106
+ - ProcessPool은 종료 대기 중 비정상 종료도 감지하고, 생존 프로세스를 회수한 뒤 미완료 Future를 실패 처리합니다. 정상 종료 알림을 보낸 프로세스는 비정상 종료로 분류하지 않습니다.
107
+ - 소비 시작 직전의 stop 요청을 놓치지 않아야 합니다.
108
+ - 복구 가능한 Consumer·브로커 정리 오류는 로그를 남기되 재연결 시도 자체를 차단하지 않습니다. 복구 불가능한 오류는 전파합니다.
109
+ - 사용자 지정 executor의 재생성은 지원하지 않지만 실패 종료에서는 `shutdown(wait=False)`를 요청합니다. 비복구 오류·복구 비활성화의 원래 예외를 종료 미지원 오류로 대체하지 않습니다.
110
+ - 정상 종료 중 cleanup 오류는 다른 프로세스의 정상 drain을 중단하지 않고 종료 시 전파합니다. 프로세스 crash 감지는 결과 큐 timeout이나 정상 종료 메시지의 수신 순서에 의존하지 않습니다.
111
+ - Pool의 stop과 restart가 겹쳐도 stop이 반환한 뒤 재시작이 Pool을 다시 열어서는 안 됩니다.
112
+
113
+ ## 검증 범위와 제한
114
+
115
+ - 단위 테스트에서 복구 횟수, 예외 분류, 채널·연결 재생성, QoS와 구독 복원, 정상 `stop()` 구분, 오래된 delivery tag 차단 및 로그를 검증합니다.
116
+ - Worker 복구 테스트에서 Pool 객체 유지·내부 실행기 교체, 설정한 복구 횟수·간격과 이전 콜백 격리를 검증합니다. 대기 간격은 제어 가능한 시간 수단으로 검증하며 실제로 여러 분을 기다리는 테스트를 강제하지 않습니다.
117
+ - 실제 실행기를 사용하는 제한된 테스트로 ProcessPool의 이전 프로세스 종료와 ThreadPool의 이전 작업 완료 후 자원 정리를 확인합니다. 복구 실패의 전파와 프로세스 종료 보장은 구분합니다.
118
+ - 실제 RabbitMQ 장애 주입은 별도의 통합 검증으로 구분합니다. 브로커 버전·장애 조건·실제 실행 여부가 확인되지 않으면 운영 동작을 보장한다고 보고하지 않습니다.
119
+ - 재전달 메시지와 이전 작업의 중복 실행 가능성은 남습니다. 호출자 측 멱등성은 본 요구사항의 검증 대상이 아닙니다.