|
15 | 15 | """Helpers for applying Google Cloud Firestore changes in a transaction.""" |
16 | 16 |
|
17 | 17 |
|
18 | | -import asyncio |
19 | | -import random |
20 | 18 | from typing import Any, AsyncGenerator, Callable, Coroutine |
21 | 19 |
|
22 | 20 | from google.api_core import exceptions, gapic_v1 |
23 | 21 | from google.api_core import retry_async as retries |
24 | 22 |
|
25 | | -from google.cloud.firestore_v1 import _helpers, async_batch, types |
| 23 | +from google.cloud.firestore_v1 import _helpers, async_batch |
26 | 24 | from google.cloud.firestore_v1.async_document import ( |
27 | 25 | AsyncDocumentReference, |
28 | 26 | DocumentSnapshot, |
|
33 | 31 | _CANT_COMMIT, |
34 | 32 | _CANT_ROLLBACK, |
35 | 33 | _EXCEED_ATTEMPTS_TEMPLATE, |
36 | | - _INITIAL_SLEEP, |
37 | | - _MAX_SLEEP, |
38 | | - _MULTIPLIER, |
39 | 34 | _WRITE_READ_ONLY, |
40 | 35 | MAX_ATTEMPTS, |
41 | 36 | BaseTransaction, |
42 | 37 | _BaseTransactional, |
43 | 38 | ) |
44 | 39 |
|
45 | | -# Types needed only for Type Hints |
46 | | -from google.cloud.firestore_v1.client import Client |
47 | | - |
48 | 40 |
|
49 | 41 | class AsyncTransaction(async_batch.AsyncWriteBatch, BaseTransaction): |
50 | 42 | """Accumulate read-and-write operations to be sent in a transaction. |
@@ -140,8 +132,13 @@ async def _commit(self) -> list: |
140 | 132 | if not self.in_progress: |
141 | 133 | raise ValueError(_CANT_COMMIT) |
142 | 134 |
|
143 | | - commit_response = await _commit_with_retry( |
144 | | - self._client, self._write_pbs, self._id |
| 135 | + commit_response = await self._client._firestore_api.commit( |
| 136 | + request={ |
| 137 | + "database": self._client._database_string, |
| 138 | + "writes": self._write_pbs, |
| 139 | + "transaction": self._id, |
| 140 | + }, |
| 141 | + metadata=self._client._rpc_metadata, |
145 | 142 | ) |
146 | 143 |
|
147 | 144 | self._clean_up() |
@@ -313,76 +310,3 @@ def async_transactional( |
313 | 310 | the wrapped callable. |
314 | 311 | """ |
315 | 312 | return _AsyncTransactional(to_wrap) |
316 | | - |
317 | | - |
318 | | -# TODO(crwilcox): this was 'coroutine' from pytype merge-pyi... |
319 | | -async def _commit_with_retry( |
320 | | - client: Client, write_pbs: list, transaction_id: bytes |
321 | | -) -> types.CommitResponse: |
322 | | - """Call ``Commit`` on the GAPIC client with retry / sleep. |
323 | | -
|
324 | | - Retries the ``Commit`` RPC on Unavailable. Usually this RPC-level |
325 | | - retry is handled by the underlying GAPICd client, but in this case it |
326 | | - doesn't because ``Commit`` is not always idempotent. But here we know it |
327 | | - is "idempotent"-like because it has a transaction ID. We also need to do |
328 | | - our own retry to special-case the ``INVALID_ARGUMENT`` error. |
329 | | -
|
330 | | - Args: |
331 | | - client (:class:`~google.cloud.firestore_v1.client.Client`): |
332 | | - A client with GAPIC client and configuration details. |
333 | | - write_pbs (List[:class:`google.cloud.proto.firestore.v1.write.Write`, ...]): |
334 | | - A ``Write`` protobuf instance to be committed. |
335 | | - transaction_id (bytes): |
336 | | - ID of an existing transaction that this commit will run in. |
337 | | -
|
338 | | - Returns: |
339 | | - :class:`google.cloud.firestore_v1.types.CommitResponse`: |
340 | | - The protobuf response from ``Commit``. |
341 | | -
|
342 | | - Raises: |
343 | | - ~google.api_core.exceptions.GoogleAPICallError: If a non-retryable |
344 | | - exception is encountered. |
345 | | - """ |
346 | | - current_sleep = _INITIAL_SLEEP |
347 | | - while True: |
348 | | - try: |
349 | | - return await client._firestore_api.commit( |
350 | | - request={ |
351 | | - "database": client._database_string, |
352 | | - "writes": write_pbs, |
353 | | - "transaction": transaction_id, |
354 | | - }, |
355 | | - metadata=client._rpc_metadata, |
356 | | - ) |
357 | | - except exceptions.ServiceUnavailable: |
358 | | - # Retry |
359 | | - pass |
360 | | - |
361 | | - current_sleep = await _sleep(current_sleep) |
362 | | - |
363 | | - |
364 | | -async def _sleep( |
365 | | - current_sleep: float, max_sleep: float = _MAX_SLEEP, multiplier: float = _MULTIPLIER |
366 | | -) -> float: |
367 | | - """Sleep and produce a new sleep time. |
368 | | -
|
369 | | - .. _Exponential Backoff And Jitter: https://www.awsarchitectureblog.com/\ |
370 | | - 2015/03/backoff.html |
371 | | -
|
372 | | - Select a duration between zero and ``current_sleep``. It might seem |
373 | | - counterintuitive to have so much jitter, but |
374 | | - `Exponential Backoff And Jitter`_ argues that "full jitter" is |
375 | | - the best strategy. |
376 | | -
|
377 | | - Args: |
378 | | - current_sleep (float): The current "max" for sleep interval. |
379 | | - max_sleep (Optional[float]): Eventual "max" sleep time |
380 | | - multiplier (Optional[float]): Multiplier for exponential backoff. |
381 | | -
|
382 | | - Returns: |
383 | | - float: Newly doubled ``current_sleep`` or ``max_sleep`` (whichever |
384 | | - is smaller) |
385 | | - """ |
386 | | - actual_sleep = random.uniform(0.0, current_sleep) |
387 | | - await asyncio.sleep(actual_sleep) |
388 | | - return min(multiplier * current_sleep, max_sleep) |
0 commit comments