Skip to content

Add support for setting per-attempt-timeout #539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 5, 2025

Conversation

pete-woods
Copy link
Contributor

@pete-woods pete-woods commented Apr 14, 2025

🔧 Changes

  • Add support for setting per-attempt request timeout in retry strategies.
  • Introduced PerAttemptTimeout field in RetryStrategy to prevent hanging requests.

📚 References

  • Currently, only complete HTTP responses can be retried.
  • This limitation may cause micro-outages when requests hang indefinitely.
  • PerAttemptTimeout mitigates this by bounding the time spent per retry attempt.

Strategy Usage Example

WithRetryStrategy(RetryOptions{
	MaxRetries: 10,
	Statuses: []int{
		http.StatusTooManyRequests,
		http.StatusInternalServerError,
		http.StatusBadGateway,
		http.StatusServiceUnavailable,
		http.StatusGatewayTimeout,
	},
	PerAttemptTimeout: 5 * time.Second,
})

✅ Retry Strategy Examples

Authentication Client

auth, err := authentication.New(
	"your-tenant.auth0.com",
	authentication.WithClientID("your-client-id"),
	authentication.WithClientSecret("your-client-secret"),
	authentication.WithRetryStrategy(authentication.RetryStrategy{
		MaxRetries:        2,
		Statuses:          []int{http.StatusTooManyRequests, http.StatusServiceUnavailable},
		PerAttemptTimeout: 10 * time.Second,
	}),
)

Management Client

mgmt, err := management.New(
	"your-tenant.auth0.com",
	management.WithClientCredentials("your-client-id", "your-client-secret"),
	management.WithRetryStrategy(management.RetryStrategy{
		MaxRetries:        3,
		Statuses:          []int{http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusBadGateway},
		PerAttemptTimeout: 15 * time.Second,
	}),
)

📌 Key Considerations

  • Customize MaxRetries, Statuses, and PerAttemptTimeout per use case.
  • Use WithNoRetries() to disable retrying.
  • Deprecated WithRetries(...) remains functional for simpler needs.

🔬 Testing

  • Added unit tests covering the new PerAttemptTimeout logic.
  • Ensured all retry scenarios remain backward compatible.

📝 Checklist

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

@pete-woods pete-woods requested a review from a team as a code owner April 14, 2025 10:19
@pete-woods
Copy link
Contributor Author

@developerkunal not sure what the process to request a review is, so sorry for @-ing you

@developerkunal
Copy link
Contributor

Hi @pete-woods,

Thank you for your contribution!

I’ll take a look — I think this is a great addition. I just need to review the approach, and if everything looks good, I’ll approve and merge it.

@pete-woods
Copy link
Contributor Author

pete-woods commented Apr 15, 2025

I just realised this won't work as the client is in an internal namespace.

Okay, revised it slightly so it will work.

@pete-woods pete-woods force-pushed the retry-per-request branch 4 times, most recently from 2bedcd9 to ec46c8a Compare April 22, 2025 10:46
@pete-woods
Copy link
Contributor Author

Hi @developerkunal! Did you get a chance to have a look over my approach here? Thanks!

Comment on lines 96 to 106
func WithRetryStrategy(retryStrategy RetryStrategy) Option {
return func(a *Authentication) {
a.retryStrategy = client.RetryOptions(retryStrategy)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @pete-woods ,

Could we update the field mapping in both management and authentication?

Suggested change
func WithRetryStrategy(retryStrategy RetryStrategy) Option {
return func(a *Authentication) {
a.retryStrategy = client.RetryOptions(retryStrategy)
}
}
func WithRetryStrategy(retryStrategy RetryStrategy) Option {
return func(a *Authentication) {
a.retryStrategy = client.RetryOptions{
MaxRetries: retryStrategy.MaxRetries,
Statuses: retryStrategy.Statuses,
PerAttemptTimeout: retryStrategy.PerAttemptTimeout,
}
}
}

Reasoning:
Explicit field mapping is safer and clearer. It properly handles the fact that authentication.RetryStrategy and internal/client.RetryOptions are distinct types, and avoids relying on direct type casting (client.RetryOptions(retryStrategy)), which can be unsafe or misleading when structs evolve independently.

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to do this, but I actually disagree in this case. If you add extra fields to the client struct, you actually want this code to fail to compile, and be prompted to add the extra fields to the public API.

If I do what you propose, when new fields are added to the client struct, this code will continue to compile and you might not notice that it's impossible to set the new fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thoughtful reply—that’s a valid perspective, and I agree that compile-time breakage can be a useful signal during development.

That said, since this SDK is public-facing and intended for broader community use, I lean toward explicit field mapping for a few reasons:

  • It makes the coupling between public and internal types more intentional.
  • It improves clarity for contributors unfamiliar with internal implementation details.
  • It prevents silent behavioral changes if internal structs evolve with semantics not meant for external exposure.
  • It opens the door to validation, defaulting, or transformation logic between layers (if needed later).

Even though it means we might miss a compile-time nudge when fields are added, I think the tradeoff is worth it in an open-source SDK where clarity and safety are priorities.

Let’s move forward with the explicit mapping—thanks again for the great discussion and your contributions!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@pete-woods pete-woods force-pushed the retry-per-request branch 3 times, most recently from e5a11e1 to 5fda310 Compare April 24, 2025 08:19
@pete-woods
Copy link
Contributor Author

@developerkunal Good morning! Are there any other things you'd like me to revise? Thanks!

@codecov-commenter
Copy link

codecov-commenter commented Apr 29, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 95.84%. Comparing base (10fd762) to head (97684cd).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #539   +/-   ##
=======================================
  Coverage   95.84%   95.84%           
=======================================
  Files          60       60           
  Lines       11683    11702   +19     
=======================================
+ Hits        11197    11216   +19     
  Misses        366      366           
  Partials      120      120           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@developerkunal
Copy link
Contributor

Hi @pete-woods,

Could you please run make generate and include the generated getters in this PR?

Thanks!

@pete-woods
Copy link
Contributor Author

Absolutely - will sort that right away

@pete-woods
Copy link
Contributor Author

@developerkunal that's done now :)

@pete-woods pete-woods force-pushed the retry-per-request branch 2 times, most recently from f930a70 to 8c47344 Compare April 30, 2025 08:59
@pete-woods
Copy link
Contributor Author

@developerkunal sorry to nag, would be really great to get this merged if there's no other issues

@duedares-rvj
Copy link
Member

@pete-woods Hello 👋
That's alright mate. 😅
The PR is going through a final review, and it's pending on me to approve.
Please allow me sometime.

Thank you :)

duedares-rvj
duedares-rvj previously approved these changes May 5, 2025
@duedares-rvj
Copy link
Member

Hey @pete-woods
Thanks for the contribution.

@developerkunal Can you help with verifying the commit? Good to merge post that.

@developerkunal developerkunal merged commit b501420 into auth0:main May 5, 2025
6 checks passed
@developerkunal developerkunal mentioned this pull request May 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants