[13.x] Memoize credentials in SqsConnector#59866
Merged
taylorotwell merged 1 commit intoApr 26, 2026
Merged
Conversation
When a queue config sets `credentials.provider = ecs` (or `instance`), SqsConnector::resolveCredentialProvider returned a raw EcsCredentialProvider or InstanceProfileProvider. The AWS SDK's ClientResolver short-circuits any callable passed as `credentials` (no automatic memoize wrap), and the signer middleware invokes the provider on every signed request — so every SQS API call triggered a fresh HTTP fetch to the EKS Pod Identity Agent / EC2 metadata endpoint. Wrap the resolved provider in CredentialProvider::memoize so credentials are cached in-process for the lifetime of the worker, with the SDK's standard 60-second pre-expiry refresh window. This matches what the SDK's own defaultProvider() does and stops queue workers from saturating the Pod Identity Agent's rate limiter under steady-state polling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kieranbrown
added a commit
to kieranbrown/cloud-managed-queues-demo
that referenced
this pull request
Apr 26, 2026
Tests the SqsConnector memoize fix that wraps the resolved ECS / instance credentials provider in CredentialProvider::memoize() so credentials are cached in-process instead of fetched per signed request. Upstream PR: laravel/framework#59866
jonagoldman
pushed a commit
to deplox/laravel-framework
that referenced
this pull request
Apr 30, 2026
When a queue config sets `credentials.provider = ecs` (or `instance`), SqsConnector::resolveCredentialProvider returned a raw EcsCredentialProvider or InstanceProfileProvider. The AWS SDK's ClientResolver short-circuits any callable passed as `credentials` (no automatic memoize wrap), and the signer middleware invokes the provider on every signed request — so every SQS API call triggered a fresh HTTP fetch to the EKS Pod Identity Agent / EC2 metadata endpoint. Wrap the resolved provider in CredentialProvider::memoize so credentials are cached in-process for the lifetime of the worker, with the SDK's standard 60-second pre-expiry refresh window. This matches what the SDK's own defaultProvider() does and stops queue workers from saturating the Pod Identity Agent's rate limiter under steady-state polling. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 a queue config sets
credentials.provider = ecs(orinstance) — as Laravel Cloud does for managed queue workers viaIlluminate\Foundation\Cloud::configureManagedQueues()—SqsConnector::resolveCredentialProvider()returned a rawAws\Credentials\EcsCredentialProvider/Aws\Credentials\InstanceProfileProviderinstance.The AWS SDK's
Aws\ClientResolver::_apply_credentials()short-circuits any callable passed ascredentials(the SDK only auto-wraps inmemoize()for its own internaldefaultProvider()path), and the signer middleware (Aws\Middleware::signer) invokes the credentials provider on every signed request. Combined with the fact thatEcsCredentialProvider/InstanceProfileProviderissue a fresh HTTP GET to the EKS Pod Identity Agent / EC2 metadata endpoint on every__invoke(), the result is that every single SQS API call (ReceiveMessage,DeleteMessage,ChangeMessageVisibility, …) triggers an HTTP fetch to the credentials endpoint.For a long-lived
queue:workworker on Laravel Cloud doing 60 long-polls/minute × N workers per node, this saturates the EKS Pod Identity Agent's built-in rate limiter and starts producing HTTP 429 responses, which the SDK currently does not retry (separate fix in aws/aws-sdk-php#3279).What this PR does
Wraps the resolved provider in
Aws\Credentials\CredentialProvider::memoize()so credentials are cached in-process for the lifetime of the worker, with the SDK's standard 60-second pre-expiry refresh window. This matches what the SDK's ownCredentialProvider::defaultProvider()does for the auto-discovered chain (which also wrapsecsCredentials()inmemoize()), so it's not a behavioral departure from SDK norms — it just brings the explicit-provider path into line with the implicit one.The
matchstatement was lifted into a local variable somemoize()wraps both branches (and any future ones) without duplication.Benefit to end users
instance(EC2 IMDS) branch.credentials.provider = ecsorcredentials.provider = instance.Backwards compatibility
resolveCredentialProvider()remainscallable|null.CredentialProvider::memoize()is part of the AWS SDK's public, documented API and has been since the SDK's early 3.x releases.Aws\Credentials\Credentialsalways carries anExpiration, somemoize()never falls into the$isConstant = truebranch and refreshes correctly.static $resultcache (verified independently — PHP scopesstaticperClosureinstance, not per literal). No cross-worker state, no shared registry, no risk of cred leakage.Test plan
tests/Queue/suite passes locally (207 tests, 558 assertions).SqsConnectorTestexists in the codebase — the connector has historically been tested via integration only. Happy to add a structural regression test (asserting the returned callable is aClosurerather than a raw provider instance) if maintainers want one.