Skip to content

Commit c457668

Browse files
author
Chris Rossi
authored
fix: fix exception handling bug in tasklets (#520)
Fixes #519
1 parent d955c80 commit c457668

File tree

5 files changed

+73
-5
lines changed

5 files changed

+73
-5
lines changed

packages/google-cloud-ndb/google/cloud/ndb/tasklets.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,14 @@ def _advance_tasklet(self, send_value=None, error=None):
309309
traceback = error.__traceback__
310310
except AttributeError: # pragma: NO PY3 COVER # pragma: NO BRANCH # noqa: E501
311311
traceback = None
312-
self.generator.throw(type(error), error, traceback)
313312

314-
# send_value will be None if this is the first time
315-
yielded = self.generator.send(send_value)
313+
yielded = self.generator.throw(
314+
type(error), error, traceback
315+
)
316+
317+
else:
318+
# send_value will be None if this is the first time
319+
yielded = self.generator.send(send_value)
316320

317321
# Context may have changed in tasklet
318322
self.context = context_module.get_context()

packages/google-cloud-ndb/noxfile.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ALL_INTERPRETERS = ("2.7", "3.6", "3.7")
2929
PY3_INTERPRETERS = ("3.6", "3.7")
3030
MAJOR_INTERPRETERS = ("2.7", "3.7")
31+
BLACK_VERSION = "black==19.10b0"
3132

3233

3334
def get_path(*names):
@@ -100,15 +101,15 @@ def lint(session):
100101
Returns a failure if the linters find linting errors or sufficiently
101102
serious code quality issues.
102103
"""
103-
session.install("flake8", "black")
104+
session.install("flake8", BLACK_VERSION)
104105
run_black(session, use_check=True)
105106
session.run("flake8", "google", "tests")
106107

107108

108109
@nox.session(py=DEFAULT_INTERPRETER)
109110
def blacken(session):
110111
# Install all dependencies.
111-
session.install("black")
112+
session.install(BLACK_VERSION)
112113
# Run ``black``.
113114
run_black(session)
114115

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from google.cloud.ndb import _retry
2626
from google.cloud.ndb import tasklets
2727

28+
from . import utils
29+
2830

2931
class Test_retry:
3032
@staticmethod
@@ -79,6 +81,40 @@ def test_transient_error(core_retry, sleep):
7981

8082
sleep.assert_called_once_with(0)
8183

84+
@staticmethod
85+
@pytest.mark.usefixtures("in_context")
86+
@mock.patch("google.cloud.ndb.tasklets.sleep")
87+
@mock.patch("google.cloud.ndb._retry.core_retry")
88+
def test_transient_error_callback_is_tasklet(core_retry, sleep):
89+
"""Regression test for #519
90+
91+
https://github.com/googleapis/python-ndb/issues/519
92+
"""
93+
core_retry.exponential_sleep_generator.return_value = itertools.count()
94+
core_retry.if_transient_error.return_value = True
95+
96+
sleep_future = tasklets.Future("sleep")
97+
sleep.return_value = sleep_future
98+
99+
callback = mock.Mock(
100+
side_effect=[
101+
utils.future_exception(Exception("Spurious error.")),
102+
utils.future_result("foo"),
103+
]
104+
)
105+
retry = _retry.retry_async(callback)
106+
future = retry()
107+
108+
# This is the important check for the bug in #519. We need to make sure
109+
# that we're waiting for the sleep future to complete before moving on.
110+
assert future.running()
111+
112+
# Finish sleeping
113+
sleep_future.set_result(None)
114+
assert future.result() == "foo"
115+
116+
sleep.assert_called_once_with(0)
117+
82118
@staticmethod
83119
@pytest.mark.usefixtures("in_context")
84120
@mock.patch("google.cloud.ndb.tasklets.sleep")

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,26 @@ def generator_function(dependency):
371371
with pytest.raises(Exception):
372372
future.result()
373373

374+
@staticmethod
375+
def test__advance_tasklet_dependency_raises_with_try_except(in_context):
376+
def generator_function(dependency, error_handler):
377+
try:
378+
yield dependency
379+
except Exception:
380+
result = yield error_handler
381+
raise tasklets.Return(result)
382+
383+
error = Exception("Spurious error.")
384+
dependency = tasklets.Future()
385+
error_handler = tasklets.Future()
386+
generator = generator_function(dependency, error_handler)
387+
future = tasklets._TaskletFuture(generator, in_context)
388+
future._advance_tasklet()
389+
dependency.set_exception(error)
390+
assert future.running()
391+
error_handler.set_result("hi mom!")
392+
assert future.result() == "hi mom!"
393+
374394
@staticmethod
375395
def test__advance_tasklet_yields_rpc(in_context):
376396
def generator_function(dependency):

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ def future_result(result):
3535
return future
3636

3737

38+
def future_exception(exception):
39+
"""Return a future with the given result."""
40+
future = tasklets.Future()
41+
future.set_exception(exception)
42+
return future
43+
44+
3845
def future_results(*results):
3946
"""Return a sequence of futures for the given results."""
4047
return [future_result(result) for result in results]

0 commit comments

Comments
 (0)