Skip to content

Commit 43fea19

Browse files
author
Chris Rossi
authored
Include a couple of GRPC transient errors in retry. (#52)
This is a fix for #51.
1 parent b3da65b commit 43fea19

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

packages/google-cloud-ndb/src/google/cloud/ndb/_retry.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Retry functions."""
1616

1717
import functools
18+
import grpc
1819
import itertools
1920

2021
from google.api_core import retry as core_retry
@@ -65,7 +66,7 @@ def retry_wrapper(*args, **kwargs):
6566
except Exception as e:
6667
# `e` is removed from locals at end of block
6768
error = e # See: https://goo.gl/5J8BMK
68-
if not core_retry.if_transient_error(error):
69+
if not is_transient_error(error):
6970
raise
7071

7172
yield tasklets.sleep(sleep_time)
@@ -78,3 +79,23 @@ def retry_wrapper(*args, **kwargs):
7879
)
7980

8081
return retry_wrapper
82+
83+
84+
TRANSIENT_CODES = (grpc.StatusCode.UNAVAILABLE, grpc.StatusCode.INTERNAL)
85+
86+
87+
def is_transient_error(error):
88+
"""Determine whether an error is transient.
89+
90+
Returns:
91+
bool: True if error is transient, else False.
92+
"""
93+
if core_retry.if_transient_error(error):
94+
return True
95+
96+
method = getattr(error, "code", None)
97+
if method is not None:
98+
code = method()
99+
return code in TRANSIENT_CODES
100+
101+
return False

packages/google-cloud-ndb/tests/unit/test__retry.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from unittest import mock
1818

19+
import grpc
1920
import pytest
2021

2122
from google.api_core import exceptions as core_exceptions
@@ -125,3 +126,51 @@ def callback():
125126
assert error_context.value.cause is error
126127
assert sleep.call_count == 5
127128
assert sleep.call_args[0][0] == 4
129+
130+
131+
class Test_is_transient_error:
132+
@staticmethod
133+
@mock.patch("google.cloud.ndb._retry.core_retry")
134+
def test_core_says_yes(core_retry):
135+
error = object()
136+
core_retry.if_transient_error.return_value = True
137+
assert _retry.is_transient_error(error) is True
138+
core_retry.if_transient_error.assert_called_once_with(error)
139+
140+
@staticmethod
141+
@mock.patch("google.cloud.ndb._retry.core_retry")
142+
def test_core_says_no_we_say_no(core_retry):
143+
error = object()
144+
core_retry.if_transient_error.return_value = False
145+
assert _retry.is_transient_error(error) is False
146+
core_retry.if_transient_error.assert_called_once_with(error)
147+
148+
@staticmethod
149+
@mock.patch("google.cloud.ndb._retry.core_retry")
150+
def test_unavailable(core_retry):
151+
error = mock.Mock(
152+
code=mock.Mock(return_value=grpc.StatusCode.UNAVAILABLE)
153+
)
154+
core_retry.if_transient_error.return_value = False
155+
assert _retry.is_transient_error(error) is True
156+
core_retry.if_transient_error.assert_called_once_with(error)
157+
158+
@staticmethod
159+
@mock.patch("google.cloud.ndb._retry.core_retry")
160+
def test_internal(core_retry):
161+
error = mock.Mock(
162+
code=mock.Mock(return_value=grpc.StatusCode.INTERNAL)
163+
)
164+
core_retry.if_transient_error.return_value = False
165+
assert _retry.is_transient_error(error) is True
166+
core_retry.if_transient_error.assert_called_once_with(error)
167+
168+
@staticmethod
169+
@mock.patch("google.cloud.ndb._retry.core_retry")
170+
def test_unauthenticated(core_retry):
171+
error = mock.Mock(
172+
code=mock.Mock(return_value=grpc.StatusCode.UNAUTHENTICATED)
173+
)
174+
core_retry.if_transient_error.return_value = False
175+
assert _retry.is_transient_error(error) is False
176+
core_retry.if_transient_error.assert_called_once_with(error)

0 commit comments

Comments
 (0)