DefStream 0.1.20__tar.gz → 0.1.22__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.
- {defstream-0.1.20 → defstream-0.1.22}/DefStream.egg-info/PKG-INFO +1 -1
- {defstream-0.1.20 → defstream-0.1.22}/DefStream.egg-info/SOURCES.txt +36 -0
- {defstream-0.1.20 → defstream-0.1.22}/PKG-INFO +1 -1
- {defstream-0.1.20 → defstream-0.1.22}/docs/LifecycleHooks.en.md +2 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/LifecycleHooks.md +2 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Operations.en.md +2 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Operations.md +2 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/RabbitMQ.en.md +47 -1
- {defstream-0.1.20 → defstream-0.1.22}/docs/RabbitMQ.md +46 -1
- defstream-0.1.22/issues/feature/46/CHANGELOG.md +26 -0
- defstream-0.1.22/issues/feature/46/IMPLEMENTS.md +35 -0
- defstream-0.1.22/issues/feature/46/PLAN.md +111 -0
- defstream-0.1.22/issues/feature/46/README.md +75 -0
- defstream-0.1.22/issues/feature/46/REQUIREMENTS.md +119 -0
- defstream-0.1.22/issues/feature/46/TASKS.md +26 -0
- defstream-0.1.22/issues/feature/46/implements/1793274693488.md +9 -0
- defstream-0.1.22/issues/feature/46/implements/1793275218996.md +7 -0
- defstream-0.1.22/issues/feature/46/implements/1793275899489.md +7 -0
- defstream-0.1.22/issues/feature/46/implements/1793276075771.md +5 -0
- defstream-0.1.22/issues/feature/46/implements/1793276518340.md +7 -0
- defstream-0.1.22/issues/feature/46/implements/1793277364116.md +12 -0
- defstream-0.1.22/issues/feature/46/implements/1793278267422.md +10 -0
- defstream-0.1.22/issues/feature/46/implements/1793278764701.md +9 -0
- defstream-0.1.22/issues/feature/46/implements/1793279849390.md +7 -0
- defstream-0.1.22/issues/feature/46/tasks/1793272872217.md +7 -0
- defstream-0.1.22/issues/feature/46/tasks/1793274121654.md +11 -0
- defstream-0.1.22/issues/feature/46/tasks/1793274231988.md +7 -0
- defstream-0.1.22/issues/feature/46/tasks/1793275218995.md +11 -0
- defstream-0.1.22/issues/feature/46/tasks/1793275501812.md +27 -0
- defstream-0.1.22/issues/feature/46/tasks/1793275955973.md +13 -0
- defstream-0.1.22/issues/feature/46/tasks/1793276099601.md +7 -0
- defstream-0.1.22/issues/feature/46/tasks/1793276518341.md +16 -0
- defstream-0.1.22/issues/feature/46/tasks/1793277424063.md +24 -0
- defstream-0.1.22/issues/feature/46/tasks/1793278267423.md +18 -0
- defstream-0.1.22/issues/feature/46/tasks/1793278764702.md +21 -0
- defstream-0.1.22/issues/feature/46/tasks/1793279849391.md +10 -0
- defstream-0.1.22/issues/fix/50/CHANGELOG.md +5 -0
- defstream-0.1.22/issues/fix/50/IMPLEMENTS.md +8 -0
- defstream-0.1.22/issues/fix/50/PLAN.md +15 -0
- defstream-0.1.22/issues/fix/50/README.md +36 -0
- defstream-0.1.22/issues/fix/50/REQUIREMENTS.md +23 -0
- defstream-0.1.22/issues/fix/50/TASKS.md +8 -0
- defstream-0.1.22/issues/fix/50/implements/1793282630353.md +7 -0
- defstream-0.1.22/issues/fix/50/tasks/1793282630354.md +12 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Connection.py +5 -4
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Consumer.py +60 -4
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Queue.py +1 -1
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Protocol.py +9 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/__init__.py +2 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Executor.py +187 -33
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Pool.py +41 -4
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Worker.py +200 -50
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_protocol_contracts.py +19 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_logging.py +1 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_rpc.py +37 -1
- defstream-0.1.22/tests/test_worker_recovery.py +842 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/ISSUE_TEMPLATE/01.feature.yml +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/ISSUE_TEMPLATE/02.bug.yml +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/ISSUE_TEMPLATE/03.fix.yml +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/ISSUE_TEMPLATE/04.task.yml +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.github/workflows/ci.yml +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/.gitignore +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/DefStream.egg-info/dependency_links.txt +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/DefStream.egg-info/requires.txt +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/DefStream.egg-info/top_level.txt +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/LICENSE +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/README.en.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/README.setup.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Contracts.en.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Contracts.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/GettingStarted.en.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/GettingStarted.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/README.en.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Redis.en.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Redis.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Tasks.en.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/docs/Tasks.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/instructions/CODE.instructions.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/instructions/DOCS.instructions.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/instructions/README.instructions.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/bug/16/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/bug/16/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/bug/16/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/bug/16/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/1/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/1/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/1/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/1/TASK.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/1/ci.refs.json +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/CHANGELOG.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/PLAN.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/TASKS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/implements/1793271568985.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/implements/1793272077065.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/tasks/1793271236067.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/tasks/1793271384432.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/tasks/1793271506089.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/tasks/1793271819682.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/47/tasks/1793272077066.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/8/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/8/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/8/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/feature/8/ci.refs.json +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/10/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/10/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/10/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/13/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/21/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/21/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/21/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/21/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/24/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/24/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/24/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/24/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/25/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/25/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/25/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/25/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/25/TASK.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/26/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/26/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/26/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/26/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/29/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/29/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/29/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/29/TASK.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/3/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/3/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/3/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/3/ci.refs.json +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/CHANGELOG.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/PLAN.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/TASKS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/implements/1793257519898.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/implements/1793257826069.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/tasks/1793256929057.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/tasks/1793256990072.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/tasks/1793257216492.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/tasks/1793257519899.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/41/tasks/1793257826068.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/CHANGELOG.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/PLAN.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/TASKS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/implements/1793260818660.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/implements/1793261447018.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/tasks/1793260818661.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/tasks/1793261447019.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/43/tasks/1793262314955.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/6/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/6/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/fix/6/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/18/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/18/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/18/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/32/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/32/ISSUE.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/32/PLAN.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/32/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/32/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/32/TASK.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/34/CHANGELOG.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/34/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/34/PLAN.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/34/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/34/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/36/CHANGELOG.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/36/IMPLEMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/36/PLAN.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/36/README.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/issues/task/36/REQUIREMENTS.md +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Broker/RabbitMQ/Sample.RPC.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Broker/RabbitMQ/Sample.Stream.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Broker/RabbitMQ/Sample.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Broker/Redis/Sample.Stream.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Broker/Redis/Sample.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Broker/Sample.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Worker/RabbitMQ/Client.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Worker/RabbitMQ/Tasks.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Worker/RabbitMQ/Worker.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Worker/Redis/Sample.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/samples/Worker/Sample.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/setup.cfg +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/setup.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Configuration.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Connection.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Consumer.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Errors.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Event.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Configuration.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Event.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Exchange.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Serializer.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Stream.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/Subscription.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/RabbitMQ/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Configuration.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Connection.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Consumer.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Event.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Queue.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Serializer.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/Subscription.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/Redis/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Implements/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Broker/Priority.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Descriptor/DataObject.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Descriptor/Types.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Descriptor/Value.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Descriptor/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extension.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Metrics.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Ping.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Play.html +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Play.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Specifications.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Status.html +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/Status.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Extensions/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Inspector.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/Response.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Inspector/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Pattern.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Array.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Common.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Compare.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Condition.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/DataObject.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/DateTime.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Number.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Object.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Size.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/String.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/Type.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Patterns/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Validate.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/Validator.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Validator/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Message.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Run.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Specification.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/Startup.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/Worker/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/src/__init__.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/_support.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_broker_packaging.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_descriptor.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_inspector.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_inspector_logging.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_inspector_play.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_lifecycle_hooks.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_logging.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_pool_callbacks.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_process_signals.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_broker_methods.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_integration.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_priorities.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_rpc_integration.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_timeouts.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_rabbitmq_worker.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_redis.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_redis_integration.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_redis_logging.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_runner_hooks.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_specifications.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_status.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_status_ui.cjs +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_task_messages.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_task_priorities.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_validator_patterns.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_worker.py +0 -0
- {defstream-0.1.20 → defstream-0.1.22}/tests/test_worker_startup.py +0 -0
|
@@ -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
|
|
@@ -114,6 +141,14 @@ issues/fix/43/implements/1793261447018.md
|
|
|
114
141
|
issues/fix/43/tasks/1793260818661.md
|
|
115
142
|
issues/fix/43/tasks/1793261447019.md
|
|
116
143
|
issues/fix/43/tasks/1793262314955.md
|
|
144
|
+
issues/fix/50/CHANGELOG.md
|
|
145
|
+
issues/fix/50/IMPLEMENTS.md
|
|
146
|
+
issues/fix/50/PLAN.md
|
|
147
|
+
issues/fix/50/README.md
|
|
148
|
+
issues/fix/50/REQUIREMENTS.md
|
|
149
|
+
issues/fix/50/TASKS.md
|
|
150
|
+
issues/fix/50/implements/1793282630353.md
|
|
151
|
+
issues/fix/50/tasks/1793282630354.md
|
|
117
152
|
issues/fix/6/IMPLEMENTS.md
|
|
118
153
|
issues/fix/6/README.md
|
|
119
154
|
issues/fix/6/REQUIREMENTS.md
|
|
@@ -245,4 +280,5 @@ tests/test_task_messages.py
|
|
|
245
280
|
tests/test_task_priorities.py
|
|
246
281
|
tests/test_validator_patterns.py
|
|
247
282
|
tests/test_worker.py
|
|
283
|
+
tests/test_worker_recovery.py
|
|
248
284
|
tests/test_worker_startup.py
|
|
@@ -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.
|
|
@@ -281,7 +325,9 @@ print(result)
|
|
|
281
325
|
|
|
282
326
|
- `timeout` is in **seconds**. Missing replies raise `TimeoutError`.
|
|
283
327
|
This does not cancel delivered work or strictly bound total connection and cleanup time.
|
|
284
|
-
-
|
|
328
|
+
- Queue-based RPC uses a dedicated connection and creates a temporary reply queue named
|
|
329
|
+
`{request queue}.{uuid4().hex}`, which is cleaned up on exit. Exchange RPC reply queue
|
|
330
|
+
names are not affected by this rule.
|
|
285
331
|
- Server rejection raises `DefStream.Broker.RemoteError`.
|
|
286
332
|
Requeue can retry without sending a final error response.
|
|
287
333
|
- Reply publication and request ACK are not atomic. Handle duplicate execution with RPC too.
|
|
@@ -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를 사용합니다.
|
|
@@ -271,7 +315,8 @@ print(result)
|
|
|
271
315
|
|
|
272
316
|
- `timeout`은 **초** 단위이며 응답이 없으면 `TimeoutError`가 발생합니다.
|
|
273
317
|
이미 전달된 서버 작업을 취소하거나 전체 연결·정리 시간을 엄격히 제한하는 값은 아닙니다.
|
|
274
|
-
-
|
|
318
|
+
- 요청 큐 RPC는 전용 연결을 사용하고 `{요청 큐}.{uuid4().hex}` 형식의 임시 응답 큐를 생성합니다.
|
|
319
|
+
큐는 종료 시 정리됩니다. Exchange RPC의 응답 큐 이름은 이 규칙의 영향을 받지 않습니다.
|
|
275
320
|
- 서버가 요청을 거부하면 `DefStream.Broker.RemoteError`가 발생합니다.
|
|
276
321
|
재큐잉은 최종 오류 응답 없이 재시도할 수 있습니다.
|
|
277
322
|
- 응답 발행과 요청 ACK는 원자적이지 않습니다. RPC에서도 중복 실행에 대비하세요.
|
|
@@ -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)
|