Repository navigation
Do not fail a transient run on a throttled DescribeCluster - #106
Closed
Ian Streeter (istreeter) wants to merge 1 commit into
Closed
Ian Streeter (istreeter) wants to merge 1 commit into
Ian Streeter (istreeter) wants to merge 1 commit into
Conversation
Production runs were reported as failed after a single ThrottlingException. None had failed: clusters had come up, or terminated cleanly, and we exited anyway — in the launch cases leaving a cluster running its steps unwatched. The waiters swallow API errors and keep polling, so the throttled call was never a poll. It was the describe around one: the existence check before it, or the re-describe after it, since waiter.Wait discards its response and the status had to be fetched again. WaitForOutput hands back the response that satisfied the acceptor, so that second call is gone from the happy path entirely; what remains covers only a waiter that errored or timed out, and a TERMINATED that EMR has not yet attached a reason to. When the launch cannot be read at all we now terminate before giving up, rather than abandoning a billing cluster. EMR reads retry on a budget chosen by the error. A throttle is waited out for minutes, because these reads are how a run learns whether it succeeded and each is issued while a cluster is already up or once it has gone. Everything else gets the few seconds it always had — enough that EMR briefly not recognising a jobflow ID it just issued is survivable, while a genuinely bad --emr-cluster is still reported in seconds instead of sitting in a waiter. The same treatment covers ListSteps and DescribeStep, which sit on the transient success path and could equally turn a completed run into a failed one. Where a run still cannot be classified, the error says the cluster could not be described rather than only that it had no reason, so throttling is visible as the cause. To reduce the pressure in the first place, waiters poll at 30-90s while a cluster launches and 60-300s during a transient job, rather than a fixed 30s: smithy only jitters when the bounds differ, so every runner had been polling on the same beat against a budget shared by the whole account. The client also moves to adaptive retry mode. Its raised attempt count is passed per read rather than set on the client, because the client is shared with RunJobFlow, which has no idempotency token — retrying a launch whose response was merely lost would create a second cluster and rerun the playbook. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ian Streeter (istreeter)
force-pushed
the
fix/describe-throttling
branch
from
August 6, 2026 23:43
3688cbf to
18e7cd4
Compare
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.
Production runs were reported as failed after a single ThrottlingException. None had failed: clusters had come up, or terminated cleanly, and we exited anyway — in the launch cases leaving a cluster running its steps unwatched.
The waiters swallow API errors and keep polling, so the throttled call was never a poll. It was the describe around one: the existence check before it, or the re-describe after it, since waiter.Wait discards its response and the status had to be fetched again. WaitForOutput hands back the response that satisfied the acceptor, so that second call is gone from the happy path entirely; what remains covers only a waiter that errored or timed out, and a TERMINATED that EMR has not yet attached a reason to. If that cannot get through, the status the waiter saw beats reporting an error. When the launch cannot be read at all we now terminate before giving up, rather than abandoning a billing cluster.
EMR reads retry on a budget chosen by the error. A throttle is waited out for minutes, because these reads are how a run learns whether it succeeded and each is issued while a cluster is already up or once it has gone. Everything else gets the few seconds it always had — enough that EMR briefly not recognising a jobflow ID it just issued is survivable, while a genuinely bad --emr-cluster is still reported in seconds instead of sitting in a waiter. The same treatment covers ListSteps and DescribeStep, which sit on the transient success path and could equally turn a completed run into a failed one.
To reduce the pressure in the first place, waiters poll at 30-90s while a cluster launches and 60-300s during a transient job, rather than a fixed 30s: smithy only jitters when the bounds differ, so every runner had been polling on the same beat against a budget shared by the whole account.
The client also moves to adaptive retry mode. Its raised attempt count is passed per read rather than set on the client, because the client is shared with RunJobFlow, which has no idempotency token — retrying a launch whose response was merely lost would create a second cluster and rerun the playbook.