29
29
30
30
import asyncio
31
31
import logging
32
- import time
33
-
32
+ import datetime
34
33
34
+ from google .api_core import datetime_helpers
35
35
from google .api_core import exceptions
36
36
37
37
_LOGGER = logging .getLogger (__name__ )
@@ -78,7 +78,35 @@ def __init__(
78
78
self .sleep_generator = iter (sleep_generator )
79
79
self .on_error = on_error
80
80
self .timeout = timeout
81
- self .remaining_timeout_budget = timeout if timeout else None
81
+ self .timeout_task = None
82
+ if self .timeout is not None :
83
+ self .deadline = datetime_helpers .utcnow () + datetime .timedelta (
84
+ seconds = self .timeout
85
+ )
86
+ else :
87
+ self .deadline = None
88
+
89
+ def _check_timeout (
90
+ self , current_time : float , source_exception : Optional [Exception ] = None
91
+ ) -> None :
92
+ """
93
+ Helper function to check if the timeout has been exceeded, and raise a RetryError if so.
94
+
95
+ Args:
96
+ - current_time: the timestamp to check against the deadline
97
+ - source_exception: the exception that triggered the timeout check, if any
98
+ Raises:
99
+ - RetryError if the deadline has been exceeded
100
+ """
101
+ if (
102
+ self .deadline is not None
103
+ and self .timeout is not None
104
+ and self .deadline < current_time
105
+ ):
106
+ raise exceptions .RetryError (
107
+ "Timeout of {:.1f}s exceeded" .format (self .timeout ),
108
+ source_exception ,
109
+ ) from source_exception
82
110
83
111
async def _ensure_active_target (self ) -> AsyncIterator [T ]:
84
112
"""
@@ -114,15 +142,12 @@ async def _handle_exception(self, exc) -> None:
114
142
next_sleep = next (self .sleep_generator )
115
143
except StopIteration :
116
144
raise ValueError ("Sleep generator stopped yielding sleep values" )
117
- # if time budget is exceeded, raise RetryError
118
- if self .remaining_timeout_budget is not None and self .timeout is not None :
119
- if self .remaining_timeout_budget <= next_sleep :
120
- raise exceptions .RetryError (
121
- "Timeout of {:.1f}s exceeded" .format (self .timeout ),
122
- exc ,
123
- ) from exc
124
- else :
125
- self .remaining_timeout_budget -= next_sleep
145
+ # if deadline is exceeded, raise RetryError
146
+ if self .deadline is not None :
147
+ next_attempt = datetime_helpers .utcnow () + datetime .timedelta (
148
+ seconds = next_sleep
149
+ )
150
+ self ._check_timeout (next_attempt , exc )
126
151
_LOGGER .debug (
127
152
"Retrying due to {}, sleeping {:.1f}s ..." .format (exc , next_sleep )
128
153
)
@@ -131,18 +156,6 @@ async def _handle_exception(self, exc) -> None:
131
156
self .active_target = None
132
157
await self ._ensure_active_target ()
133
158
134
- def _subtract_time_from_budget (self , start_timestamp : float ) -> None :
135
- """
136
- Subtract the time elapsed since start_timestamp from the remaining
137
- timeout budget.
138
-
139
- Args:
140
- - start_timestamp: The timestamp at which the last operation
141
- started.
142
- """
143
- if self .remaining_timeout_budget is not None :
144
- self .remaining_timeout_budget -= time .monotonic () - start_timestamp
145
-
146
159
async def _iteration_helper (self , iteration_routine : Awaitable ) -> T :
147
160
"""
148
161
Helper function for sharing logic between __anext__ and asend.
@@ -154,28 +167,13 @@ async def _iteration_helper(self, iteration_routine: Awaitable) -> T:
154
167
- The next value from the active_target iterator.
155
168
"""
156
169
# check for expired timeouts before attempting to iterate
157
- if (
158
- self .remaining_timeout_budget is not None
159
- and self .remaining_timeout_budget <= 0
160
- and self .timeout is not None
161
- ):
162
- raise exceptions .RetryError (
163
- "Timeout of {:.1f}s exceeded" .format (self .timeout ),
164
- None ,
165
- )
170
+ self ._check_timeout (datetime_helpers .utcnow ())
166
171
try :
167
- # start the timer for the current operation
168
- start_timestamp = time .monotonic ()
169
172
# grab the next value from the active_target
170
173
# Note: interrupting with asyncio.wait_for is expensive,
171
174
# so we only check for timeouts at the start of each iteration
172
- next_val = await iteration_routine
173
- # subtract the time spent waiting for the next value from the
174
- # remaining timeout budget
175
- self ._subtract_time_from_budget (start_timestamp )
176
- return next_val
175
+ return await iteration_routine
177
176
except (Exception , asyncio .CancelledError ) as exc :
178
- self ._subtract_time_from_budget (start_timestamp )
179
177
await self ._handle_exception (exc )
180
178
# if retryable exception was handled, find the next value to return
181
179
return await self .__anext__ ()
0 commit comments