fail after 20 "recoverable" exception in iterator

it turns out that when iterating over results sometimes (always?) errors
that are recoverable when running a query are not recoverable, so we've
been ending up in infinite loops
This commit is contained in:
Noah Levitt 2018-09-27 12:56:30 -07:00
parent a9f764fb45
commit c5b1b0a620
2 changed files with 35 additions and 29 deletions

View File

@ -39,6 +39,33 @@ class RethinkerWrapper(object):
def __repr__(self):
return '<RethinkerWrapper{}>'.format(repr(self.wrapped))
def _result_iter(self, conn, result):
error_count = 0
try:
yield # empty yield, see comment below
while True:
try:
yield next(result)
except StopIteration:
break
except r.ReqlOpFailedError as e:
if e.args and re.match(
'^Cannot perform.*replica.*', e.args[0]):
if error_count < 20:
error_count += 1
self.logger.warn(
'will keep trying after potentially '
'recoverable error (%s/20): %s',
error_count, e)
time.sleep(0.5)
else:
raise
else:
raise
finally:
result.close()
conn.close()
def run(self, db=None):
self.wrapped.run # raise AttributeError early
while True:
@ -48,32 +75,7 @@ class RethinkerWrapper(object):
result = self.wrapped.run(conn, db=db or self.rr.dbname)
if hasattr(result, '__next__'):
is_iter = True
def gen():
try:
yield # empty yield, see comment below
while True:
try:
x = next(result)
yield x
except StopIteration:
break
except r.ReqlOpFailedError as e:
if e.args and re.match(
'^Cannot perform.*replica.*',
e.args[0]):
self.logger.error(
'will keep trying after '
'potentially recoverable '
'error: %s', e)
time.sleep(0.5)
else:
raise
finally:
result.close()
conn.close()
g = gen()
g = self._result_iter(conn, result)
# Start executing the generator, leaving off after the
# empty yield. If we didn't do this, and the caller never
# started the generator, the finally block would never run
@ -87,7 +89,7 @@ class RethinkerWrapper(object):
except r.ReqlOpFailedError as e:
if e.args and re.match(
'^Cannot perform.*replica.*', e.args[0]):
self.logger.error(
self.logger.warn(
'will keep trying after potentially recoverable '
'error: %s', e)
time.sleep(0.5)

View File

@ -174,7 +174,7 @@ class MockRethinker(doublethink.Rethinker):
count = {'value': 0}
def next_(*args, **kwargs):
count['value'] += 1
if count['value'] <= 2:
if count['value'] % 2 == 1:
raise e
else:
return mock.MagicMock()
@ -209,5 +209,9 @@ def test_error_handling():
next(it) # exception here
it = rr.table('recoverable_err_in_iterator').run() # no exception yet
next(it) # no exception
# next(it)
for i in range(20):
next(it) # no exception
with pytest.raises(r.ReqlOpFailedError):
next(it) # out of retries