Gracefully shut down st2 engines (workflow_engine, scheduler, rulesengine, actionrunner) on RabbitMQ failure to prevent stuck running tasks - #6398
Open
guzzijones wants to merge 24 commits into
Conversation
update base and workflows do not catch base exception when closing channel. if there is a channel it should close parse user keys correctly in api model unused variable fix abandoned test fix orquesta testing race fix fix a couple more tests that needed to mock the workfow service request.next_tasks
Contributor
Author
|
@nzlosh not sure if you want this in 3.10. I just set the milestone so we could see the green check mark for now. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When RabbitMQ becomes unreachable, several StackStorm engine services (most notably
st2workflowengine, but alsost2scheduler,st2rulesengine, andst2actionrunner) do not exit their process. Instead, the internal KombuConsumerMixinretries the broker connection infinitely, and the consumer/dispatcher greenlets die silently. The pod/process stays "up" from Kubernetes' perspective (liveness passes, PID 1 is alive), but the service is effectively dead — it holds no AMQP subscription, publishes no state transitions, and cannot process further work.The visible symptom in production is that workflows/tasks are left stuck in the
runningstate indefinitely, because:try/except: LOG.exception(...)), and the DB status update was left in an inconsistent state relative to what other services believed.Root causes identified
ConsumerMixin.on_connection_error. Consumers never give up, never raise, so the parent thread cannot detect the failure and the service cannot exit.st2workflowengine,st2scheduler, andst2rulesengine, themain()returns / blocks in a way that a dead consumer greenlet does not propagate an error up to the process, so the pod is not restarted by Kubernetes.st2common/persistence/base.py,Access.add_or_updateandAccess.updateperformed the DB write, then swallowed any exception frompublish_create/publish_update/ trigger dispatch. This left the DB "ahead" of the message bus and produced tasks that no downstream service ever saw.messagingconfig hadconnection_retries/connection_retry_waitbut nothing that enforced a maximum for the Kombu consumer loop or the publisherensure()retry.REQUESTEDstate whose scheduling-queue message had been lost during a prior broker outage.What this branch changes
1. New
ConnectionRetryMixin(st2common/st2common/transport/connection_retry_mixin.py)A mixin for Kombu
ConsumerMixin-based classes. It overrideson_connection_errorto count attempts and re-raise the exception aftermessaging.connection_retry_max_attempts, breaking the infinite retry loop.on_connection_revivedresets the counter.0= retry forever (backwards-compatible opt-out).2. New messaging config options (
st2common/config.py,conf/st2.conf.sample)messaging.connection_retry_max_attempts(default10)messaging.connection_retry_interval_start(default1)messaging.connection_retry_interval_step(default1)messaging.connection_retry_interval_max(default30)These are now passed into the Kombu
Connectionastransport_options(seest2common/transport/utils.py) so both initial connect and reconnect during publish honour a real ceiling.3. Graceful process exit when consumer/worker threads die
st2actions/cmd/workflow_engine.py,st2actions/cmd/scheduler.py, andst2reactor/cmd/rulesengine.pynow poll their consumer / handler / entrypoint greenlets. If any of them die,.wait()is called to re-raise the underlying exception, the service deregisters from the service registry, callsshutdown(), and returns a non-zero exit code. Kubernetes then restarts the pod cleanly instead of leaving a half-dead process running.exceptpath now also callsderegister_service+engine.shutdown()before returning1.4. Publisher / consumer retry limits
st2common/transport/connection_retry_wrapper.pyrefactored to honourconnection_retry_max_attemptsonensure()-style publishes rather than looping forever.st2common/transport/consumers.pyandst2actions/worker.pycatch the raised broker exceptions (amqp_exceptions,kombu_exceptions) at the top level and let them propagate to shut the worker down.5. DB/RabbitMQ atomicity — rollback on publish failure
st2common/persistence/base.pyAccess.add_or_updateandAccess.updateno longer swallow publish/trigger-dispatch exceptions. If the RabbitMQ publish fails:set__rollback kwargs.This is the direct fix for the "stuck in running" symptom: either the state transition is committed and published, or neither happens.
6. Scheduler bootstrap recovery
st2actions/scheduler/handler.pygains_bootstrap_missing_scheduling_queue_items, invoked fromst2actions/cmd/scheduler.pyon startup. It scans forREQUESTEDLiveActions with no corresponding scheduling-queue entry (i.e. the enqueue message was lost during a prior RabbitMQ outage) and re-creates the queue item so the workflow can move forward after recovery. Covered byst2actions/tests/unit/test_scheduler_bootstrap_recovery.py.7. Misc fixes bundled in the same branch
st2common/services/triggerwatcher.py– uses the new retry mixin.st2api/controllers/v1/keyvalue.py+st2common/models/api/keyvalue.py– correctly parses user-scoped keys (unrelated bugfix caught during testing).st2common/transport/publishers.py,st2common/services/action.py– minor cleanup so channel close errors surface instead of being masked byexcept BaseException.