Title: async retries timeout race condition · Issue #528 · googleapis/python-api-core · GitHub
Open Graph Title: async retries timeout race condition · Issue #528 · googleapis/python-api-core
X Title: async retries timeout race condition · Issue #528 · googleapis/python-api-core
Description: Problem Async retries use an asyncio.wait_for line to break out early when the deadline is hit. The problem is, in practice, retries typically wrap gapic grpc calls, which implement their own timeout logic. This means there is a natural ...
Open Graph Description: Problem Async retries use an asyncio.wait_for line to break out early when the deadline is hit. The problem is, in practice, retries typically wrap gapic grpc calls, which implement their own timeo...
X Description: Problem Async retries use an asyncio.wait_for line to break out early when the deadline is hit. The problem is, in practice, retries typically wrap gapic grpc calls, which implement their own timeo...
Opengraph URL: https://github.com/googleapis/python-api-core/issues/528
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"async retries timeout race condition","articleBody":"### Problem\r\n\r\nAsync retries use an [`asyncio.wait_for` line](https://github.com/googleapis/python-api-core/blob/bdebd6331f9c0d3d1a8ceaf274f07d2ed75bfe92/google/api_core/retry_async.py#L121) to break out early when the deadline is hit. The problem is, in practice, retries typically wrap gapic grpc calls, which implement their own timeout logic. This means there is a natural race condition when the timeout is reached, where one of two things can happen:\r\n- the `wait_for` line in the wrapper notices the timeout first:\r\n - the wrapper emitts a `RetryError`\r\n - a CancelledError is sent down to the wrapped function, which the wrapped function may have to account for properly\r\n- The wrapped grpc call times out on its own:\r\n - a `DeadlineExceeded` error is passed to the wrapper\r\n - This may result in the wrapper raising a `RetryError`, if DeadlineExceeded is in the list of retryable errors, otherwise it will be raised directly through the wrapper. \r\n\r\nOn top of the race condition, there are a couple other issues with wait_for:\r\n- `wait_for` creates a new task, which can add significant performance overhead\r\n- This behaviour differs from the sync retry function, which only checks for timeouts after the wrapped function attempt is complete\r\n\r\n---\r\n### Solutions:\r\n\r\nWe can add a new `interrupt_on_timeout` argument to the async retry function, and only enable wait_for when the flag is True. To avoid breaking changes, we can have the flag default to True\r\n\r\nBut personally, I think we should consider just removing the wait_for entirely\r\n\r\n---\r\n\r\n### Streaming Retries:\r\n\r\n[Streaming retries](https://github.com/googleapis/python-api-core/pull/495) add an extra complication, since there's the added possibility to check for expired timeouts after each yield in the stream, on top of the option to break the stream mid-yield. We we could add another flag to control for to control these timeout options. \r\n\r\nBut adding any extra checks in between each yield can significantly harm performance, especially if we add a wait_for to interrupt mid-yield, like async_retry currently does. For this reason. I suggest that we keep these timeout options out of streaming retries for now, and only keep the async_retries wait_for for compatibility reasons.\r\n\r\nIn general, I think the retry wrappers should expect the wrapped function to handle timeout intrruptions on their own, and the wrappers should only handle timeouts between attempts\r\n","author":{"url":"https://github.com/daniel-sanche","@type":"Person","name":"daniel-sanche"},"datePublished":"2023-09-07T20:56:35.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/528/python-api-core/issues/528"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:581a3873-c2ae-17ad-c57b-928e32994467 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | C0B2:49C5:C4152:11E810:6A4DFA83 |
| html-safe-nonce | 3a25ee8a73bafa9f4654104243c5cb9a10beb363c807c1fa362cc2cdadf1c619 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDMEIyOjQ5QzU6QzQxNTI6MTFFODEwOjZBNERGQTgzIiwidmlzaXRvcl9pZCI6IjkyMTI3Mzc3OTQxMDI2NTU2MTkiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | ad7418adb569b3233271d2e24e594678219e1b350c453e78858099229215fa17 |
| hovercard-subject-tag | issue:1886554507 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/googleapis/python-api-core/528/issue_layout |
| twitter:image | https://opengraph.githubassets.com/389763d4c94a1238dc4ef8cf586803623e9344aa45cb4e72bbadeea06cf150c2/googleapis/python-api-core/issues/528 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/389763d4c94a1238dc4ef8cf586803623e9344aa45cb4e72bbadeea06cf150c2/googleapis/python-api-core/issues/528 |
| og:image:alt | Problem Async retries use an asyncio.wait_for line to break out early when the deadline is hit. The problem is, in practice, retries typically wrap gapic grpc calls, which implement their own timeo... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | daniel-sanche |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5818716c93c6a2925b815402541a32814e43a7b1261c322b0c2df75224289566 |
| turbo-cache-control | no-preview |
| go-import | github.com/googleapis/python-api-core git https://github.com/googleapis/python-api-core.git |
| octolytics-dimension-user_id | 16785467 |
| octolytics-dimension-user_login | googleapis |
| octolytics-dimension-repository_id | 226992456 |
| octolytics-dimension-repository_nwo | googleapis/python-api-core |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 226992456 |
| octolytics-dimension-repository_network_root_nwo | googleapis/python-api-core |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 4314b1df11fa8a565684f3a72dc971e3785da365 |
| ui-target | canary-1 |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width